Accessing WCF Service via Azure Service Bus Relay with .NET Core

Check out my GitHub repository if you want to dig into the source code directly.

WCF (Windows Communication Foundation) has served us for a long time when it comes to talking to many LOB systems (SAP etc.). You might have love or hate (though if I have to guess, it would be on hate side) relationship with WCF but it works and at times we don’t have any other choice. If you happen to be in a situation where you are talking to legacy systems via WCF, you most likely have a built an API layer on top of it to serve your clients.

I was recently looking to consume WCF services using .NET Core (with the intention that I can migrate my web application to .NET Core). Very quickly I came up to this GitHub repository which was awesome. However, this .NET Core implementation is not in parity with the full framework yet.  I tested it and it worked just fine with standard bindings (http etc). Shane Boyer has a good blog post on this topic if you want to see how to use this.

As far as my needs (this particular scenario) are concerned, I couldn’t get it to work for my setup. This is how the setup looks for me. 

The web application (hosted in Azure Web App) talks to WCF Service (running on-premises, hosted in IIS) via Azure Service Bus Relay (in Azure)

So basically, there is no equivalent to basicHttpRelayBinding in .NET Core implementation yet and this is something my WCF service has to enable communication via Azure Service Bus Relay. 

After discussing with a friend of mine, I got a suggestion to try it out with SOAP instead and it seems to work fine! I know it sounds a bit ironic that on the one hand I’m moving to the latest (and greatest) framework for my web application but at the same time, I’m going one step back and using SOAP protocol to make it work – well such is life. I would very much appreciate if anyone can point me to the better solution. but until then, let’s continue forward.

So, in this blog post, I will go through how did I manage to call WCF service from my .NET Core Web Application. Again, here is the entire source code

So this is what I’m going to do: 

  • Create Azure Service Bus Relay (read here how to create this)
  • Create a WCF Service (with Azure Service Bus Relay Binding) and publish this locally to run it on IIS
  • ASP.NET Core Web Application calling to WCF Service using HTTP Post SOAP request.

Follow the link I have provided above to create the service bus relay. Let’s start with WCF Service. Here is the sample WCF project I build for this demo.  It’s a very basic WCF service which has Azure Service Bus Relay binding besides standard HTTP binding (check out the web.config for binding details). I have deployed this in IIS on my local machine. You can clone the repository and add the details of your relay if you want to try it out yourself. It exposes the following contract, which I’m interested in.

[ServiceContract]
    public interface ICustomerService
    {
        [OperationContract]
        string GetCustomerData(string customerId);
    }

When you browse this service from IIS you should see the service like this. At this point, a Service Bus WCF listener should also be registered in your namespace (you can check it in Azure Portal)

WCF Service deployed in IIS with Azure Service Relay Binding
WCF Relay Registered from WCF Service

Now, let’s start with the web application. I have created a vanilla ASP.NET Core Web API project (2.1). You can grab the source code from here (and clone it if you want to try it out yourself). Make sure you update settings in appsettings.json file for Azure Service Bus Relay. If you wish you publish this to Azure Web app – add these settings as Application Settings

After creating the project, I added following NuGet package

Nuget Packages: 

  • Microsoft.Azure.ServiceBus

In ValuesController, I have created a GetCustomerData method that takes in customerId as a string.  In here, I’ll make a call to WCF Service and return the response I receive from the service (the WCF service returns a dummy response)

[HttpGet("{customerId}")]
public async Task<string> GetCustomerData(string customerId)

Then moving forwards,  I generate the token for Azur Service Bus Relay:

Generating Token for Service Bus Relay

With this information, I construct the HTTP Post request like this:

We are basically making an HTTP Post Request to the Service Bus Relay endpoint over SOAP. For this, we need to set up a few headers

  • ServiceBusAuthorization – Relay Token retrieved earlier
  • SOAPAction – The Operation you want to call from WCF Service
  • Accept header – text/xml

Lastly, we need to set the body for the request – since this is a SOAP call, we need a SOAP message.

SOAP message body

Press F5 and run the web application, pass in cusotmerId parameter and you should get a response from the service:

Successful response from WCF Service

Have you tried to consume WCF Service from .NET Core application? How did it go for you? I would love to hear from you and would appreciate if you could suggest a better way of doing this as of today? I guess I don’t need to tell you that I don’t like using SOAP ūüôā

Cheers.

Accessing Azure Analysis Services Models using .NET Core

Azure Analysis Services is a fully managed platform as a service (PaaS) that provides enterprise-grade data models in the cloud. Use advanced mashup and modeling features to combine data from multiple data sources, define metrics, and secure your data in a single, trusted tabular semantic data model. The data model provides an easier and faster way for users to browse massive amounts of data for ad-hoc data analysis.

Refer to Microsoft official documentation to read more about Azure Analysis Services

Programming against Analysis services is nothing new and we have been doing it for a long time with the full .NET framework, the most common approach is using ADOMD.Net. In this blog post, I will go through the process of getting the same task done with .NET Core.¬† For this sample, I’m¬†using .NET Core 2.1.

Look at my GitHub repository for the entire source code for this blogpost

The important thing to note here is that there is no official nuget package from Microsoft for ADOMD.NET yet, but I found an unofficial package here (Unofficial.Microsoft.AnalysisServices.AdomdClientNetCore) and it seems to work for my quick test (you have to make a call if you want to use it in production or not). I couldn’t find any official word on this anywhere I looked for.¬†¬†Besides this nuget¬†package for .net core rest of the stuff should work same in full framework (with official nuget¬†for ADOMD.NET)

I have divided this into several steps so that it is easy to follow. So let’s get started!

Step 1: Create Azure Analysis Service resource

The very first thing we need is the Analysis Server and model in Azure. Follow this quick starter to create the server.

Next is to create a model which we will use to query. You can create a model with sample data (adventure works) right from within your Analysis Server.

Click ‘Manage’ in the blade and click ‘New Model’. Select ‘Sample data’ from the drop down and press ‘Add’. It should add the¬†model for you.¬†

Create a model Analysis Services
Model successfully created

Step 2: Create App Service Principal

There are many ways to access analysis services. Simplest is usig a connection string that has usrename and password. But this is not recommended approach and works only with full .Net framework but not .Net Core (I was pointed by bdebaere in his GitHub respo regarding this), so we want to authenticate with other OAuth flows. For this post, we will use token-based authentication. For this we will need an app principal (or Azure AD App)

  1. Sign in to Azure Portal.
  2. Navigate to Azure Active Directory -> App Registrations and Click New application registration.
  3. Register an app with the following settings:
    • Name: any name
    • Application type: Web app/API,
    • Sign-on URL:¬† https://westeurope.asazure.windows.net (Not really important here, you can provide any valid url).
  4. Once the app is created, navigate to ‘Keys’ and add a new key
    • provide the description and select duration and press Save button
    • after that you will be able to see the key it will appear only once¬†so take note of this key and we will use this later on
  5. Also take note of the Application Id from the main page of the application
Setting access key for Azure AD App

Step 3: Assign your user as Service Admin in order to connect from SSMS

Registering an app is not enough. We need to assign access to this app on  Analysis Service Model (adventureworks model that we created in previous step). In order to give this access, we will need SQL Server Management Studio. 

Before we could use that, we need a way to connect to this analysis services instance via SSMS. For this, we need to set up our account as Service Admin. Navigate to the Analysis Services resource that we created in the first step. Click ‘Analysis Services Admin’. Normally your subscription account is set as the admin (this is what I will be using) but you are free to set up any account you wish appropriate.¬†

Setting Sevice Admin for Analysis Service

Step 4: Grant permissions to app principal on the model

  1. Connect to Analysis Service using SSMS 2017 with your account that you assigned as Service Admin in the previous step
    • You will need the Server name (from Step 1)
  2. Select the database model and click on Roles or add a new Role
    • Choose any name¬†
    • Select ‘Read’ database permission for the role¬†
  3. Add the Service principal to any role in below format (search for the app name)
Connect to Azure Analysis Server with Service Admin account
Adding App principal as a member in newly defined role

This will add user with following convention:  app:<appid>@<tenantid>

appid: is the application id for your app you created in the Step 2.

tenantid – is the id of your subscription (you can find this in Properties of your Azure Active Directory)

This didn’t work for me when I tried¬†¬†to use Azure subscription with my personal account (hotmail) so I had to use my company account subscription to make this work.¬†

Step 4: Write the code to access data

Now we are all set up write our code that reads from the Model. Please refer to the entire source code in my GitHub respository

Important method here GetAccessToken.¬†I’m using ADAL.Net (nuget: Microsoft.IdentityModel.Clients.ActiveDirectory) to grab the token for the service principal from Azure AD.¬†

Method to acquire token from Azure AD to access analysis services

Once we have the token, we are good to access data from the model. Here I’m¬†using the unofficial NuGet package for ADOMD.NET that I mentioned previously.¬† The correct¬† Connection String format is:¬†

“Provider=MSOLAP;Data Source=<url of the Azure Analysis Server>;Initial Catalog=<modelname>;User ID=;Password=<access token here>;Persist Security Info=True;Impersonation Level=Impersonate“

User ID is left empty and Password is the access token which we get from Azure AD. 

Method to read data from Analysis Services Model adventureworks

If you run this, you will see the output in the console

Final output of the program

Have you tried to work with Azure Analysis services in .NET Core? How was your experience? I would be very interested in listening to your experience and challenges.

Cheers

Using HttpHandler to deal with cross-domain requests from AngularJS app for SharePoint on-premises

Scenario:

When you go to the newsfeed in your personal site (mysite newsfeed), you can write post and target it to different sites you are following or on your personal feed.

Recently, I had to built the same functionality for one of the projects. The ideas was to build the UI using SharePoint’s REST APIs and AngularJS. I am not using Office365 so i still have SharePoint on-prem.

Problem:

Its quite simple as you would expect to retrieve the list of sites a user is following. You simply call this REST endpoint ‚Äú../_api/social.following/my/followed(types=4)‚ÄĚ and you have the result, no brainer. But only when you dig into¬†the results, you get to know that even though the list is correct but not every site returned has newsfeed enabled. As a result, you also get those sites that have no newsfeed and these are the sites you don‚Äôt want to display. So, how do you eliminate those?

The problem is that there is no information in the JSON response that REST endpoint returns. The only option you have is to actually check for the SiteFeed feature on that web. So you get the site, open that web and see if this feature in enabled. Now this is all fine when you are on server side but if you are implementing your functionality using client side, you will end up dealing with cross-domain issues and you will get exception as it is treated as a cross-domain request by the browser.

Now if you were dealing with Office365, this could have been resolved using Microsoft Azure’s ACS to authenticate your app with SharePoint and all would have worked just fine. But that’s not the case when you are building your client app using jQuery or Angular and using REST APIs in SharePoint on-prem.

Solution:

So, after struggling quite a bit to somehow deal with this, I ended up creating the HttpHandler in which I retrieved the list of sites the current user is following. Then I iterated through that collection to check for the Site Feed feature and return the collection of sites only with site feed feature enabled and continue to building my AngularJS app as I was before hitting this issue.

I am not putting up the entire code or even complete code for my app here. I am adding some snippets demonstrating the solution. Here is the markup in my view. Its simply a drop down that lists the sites I’m following just like the one you get in SharePoint by default.

<!-- Here is the markup in the view (html file) of my angularjs app. I am putting just the markup for rendering the dropdown with required results and eliminating rest of the code for demonstration purpose -->
<!-- here, vm.followedSites is the model that im setting in my controller and select element is bound to that model -->
 <div>
      <select ng-model="vm.selectedValue" ng-options="value.Name for value in vm.followedSites">
          <option value="">everyone</option>
      </select>
</div>

Here is my controller that calls the handler to retrieve that data and set the model that my view is bound to.

/* to simplify things, i am demonstrating the underlying method that is calling in the httphandler to get the desired result 
 this method is inside my controller 
 */
 
 function loadFollowedSites(currentUser) {
            //construct the URL for httphandler to get the sites with newsfeed activated
            var url = "/_layouts/15/myapp/Handlers/ClientHandler.ashx?operation=getFollowedSitesWithNewsFeed&username="+ currentUser;
            
            //make request to the url and return the result back
            return $http.get(url).then(function (response) {
                return response.data;
            });
        }

And finally, here is the code that retrieves the list of sites i’m following in my HttpHandler

/**
Here is the code snippet from my HttpHandler
 
**/  
  public void ProcessRequest(HttpContext context)
  {
      context.Response.ContentType = "application/json";
      context.Response.ContentEncoding = Encoding.UTF8;
      
      string operation = context.Request.QueryString["operation"];
 
      if (!String.IsNullOrEmpty(operation))
      {
        /* Code removed */ 
        if (operation == "getFollowedSitesWithNewsFeed")
        {
            string userName = (context.Request.QueryString["username"] != null) ? context.Request.QueryString["username"] : String.Empty;
            List<SPSocialActor> newsFeedFollowedSites = GetFollowedSitesWithNewsFeed(context, userName);
            context.Response.Write(JsonSerializer.Serialize(newsFeedFollowedSites));
        }
        
        /* code remoed */
      }
 
  }
  
  //Retrieves the list of all the sites that specific user is following that has SiteFeed web feature enabled
  private List<SPSocialActor> GetFollowedSitesWithNewsFeed(HttpContext cxt, string username)
  {
      List<SPSocialActor> followedSitesWithNewsFeed = new List<SPSocialActor>();            
      SPServiceContext context = SPServiceContext.GetContext(cxt);
      UserProfileManager profileManager = new UserProfileManager(context);
 
      if (profileManager.UserExists(username))
      {
          UserProfile userProfile = profileManager.GetUserProfile(username);
          SPSocialFollowingManager followingManager = new SPSocialFollowingManager(userProfile, context);
 
          SPSocialActor[] followedSites = followingManager.GetFollowed(SPSocialActorTypes.Sites);
 
          foreach (var actor in followedSites)
          {
              try
              {
                  using (SPSite site = new SPSite(actor.Uri.AbsoluteUri))
                  {
                      SPWeb rootWeb = site.RootWeb;
                      
                      //Check for SiteFeed feature (web)
                      Guid featureGuid = new Guid("15a572c6-e545-4d32-897a-bab6f5846e18");
                      if (rootWeb.Features[featureGuid] != null)
                      {
                          followedSitesWithNewsFeed.Add(actor);
                      }
                  }
              }
              catch 
              { 
                //Handle exception
              }
          }
      }
      else
      {
        //Log - user doesn't exist
      }            
      
      
      return followedSitesWithNewsFeed;
  }

Please let me know if there’s something I have missed or if there is any better way of doing this. I will really appreciate it.

Thanks Cheers

The specified program requires a new version of Windows. (Exception from HRESULT:0x8007047E)

Scenario:
I have a custom list definition based on which I want to create some lists and associate OOB approval workflow with them for content approval.

In the schema.xml file for my custom list definition (based documents library i.e., basetype=1), I set ModeratedList=‚ÄúTrue‚ÄĚ attribute among others (see in the code snippet below)

<List xmlns:ows="Microsoft SharePoint" Title="TestList" Direction="$Resources:Direction;" Url="TestList" BaseType="1"
xmlns="http://schemas.microsoft.com/sharepoint/" EnableContentTypes="TRUE" EnableMinorVersions="TRUE" VersioningEnabled="TRUE" DraftVersionVisibility="2" ModeratedList="TRUE‚ÄĚ>

Now, I added a ListAdded event receiver that listens to my custom list definition,so whenever any list is created based on my custom list definition, the OOB approval workflow should be associated with the list.

Here is the code snippet that associates the OOB approval workflow with the list


///
/// Associate OOB Approval workflow with the list
///
///
public static void AssociateApprovalWorklowWithList(SPList list)
{
  //variables
  Guid listId = list.ID;
  Guid webId = list.ParentWeb.ID;
  Guid siteId = list.ParentWeb.Site.ID;
  SPSite site = null;
  SPWeb web = null;

  try
  {
    site = new SPSite(siteId);
    web = site.OpenWeb(webId);
    bool allowUnsafeCurrent = web.AllowUnsafeUpdates;
    web.AllowUnsafeUpdates = true;
    list = web.Lists[listId];

    //Get Approval Workflow Template Base Id
    Guid workflowBaseId = new Guid("8ad4d8f0-93a7-4941-9657-cf3706f00"+ web.Language.ToString("X"));

    //If workflow is already associated, don't re-associate
    if (list.WorkflowAssociations.GetAssociationByBaseID(workflowBaseId) != null)
       return;

    //check if workflows feature is activated, if not activate the feature
    SPFeature workflowsFeature = web.Site.Features[WorkflowId];

    if (workflowsFeature == null)
      web.Site.Features.Add(WorkflowId);

    // Get workflow history and task list
    SPList historyList = EnsureHistoryList(web);
    SPList taskList = EnsureTasksList(web);
    string workflowAssociationName = string.Format("{0} - Approval", list.Title);
    SPWorkflowTemplate workflowTemplate = web.WorkflowTemplates.GetTemplateByBaseID(workflowBaseId);
    if (workflowTemplate == null)
    {
     //Log exception
     return;
    }

    workflowTemplate.AllowManual = false;

    // Create workflow association
    SPWorkflowAssociation workflowAssociation = SPWorkflowAssociation.CreateListAssociation(workflowTemplate,
    workflowAssociationName, taskList, historyList);

    var associationDataXml = XElement.Parse(workflowAssociation.AssociationData);
    // Add workflow association to my list
    list.WorkflowAssociations.Add(workflowAssociation);

    //Set workflow association data
    workflowAssociation.AssociationData = Add_Association_Data(web, associationDataXml);

    // Enable workflow
    if (!workflowAssociation.Enabled)
        workflowAssociation.Enabled = true;

    if (list.DraftVersionVisibility != DraftVisibilityType.Approver)
       list.DraftVersionVisibility = DraftVisibilityType.Approver;

    list.WorkflowAssociations.Update(workflowAssociation);
    list.DefaultContentApprovalWorkflowId = workflowAssociation.Id;
    list.Update();
    web.AllowUnsafeUpdates = allowUnsafeCurrent;
  }
  catch (Exception ex)
  {
    //Log Exception
  }
  finally
  {
    //Dispose the objects
    if (web != null)
       web.Dispose();
    if (site != null)
       site.Dispose();
   }
}

For the explanation of retrieving the workflow template id (line#23), please refer to my previous post here Associating OOB Approval workflow with custom list for different Locale (LCIDs)

Problem:
Once I have deployed the solution and everything is hooked up, I create the list based on my custom list definition and the process ends with the following exception:

PastedGraphic-1

But when I go to the site, the list is created and workflow is associated properly. 

Solution:
After narrowing down the problem, I removed the ModeratedList attribute from List element in schema.xml file for custom list definition (shown in the first code snippet), enabled the content approval for the list in the code and redeployed the solution and everything worked !

Here is the code for enabling content approval through code (add this in the function defined above after setting DraftVersionVisibility property)


  if(!list.EnableModeration)
    list.EnableModeration = true;

Cheers

Associating OOB Approval workflow with custom list for different Locale (LCIDs)

Scenario: I have a custom web template that is used to provision the site. Among other things (branding etc), a custom documents library is provisioned whenever a new site is created (also based on my custom documents library template). Besides creating the custom library, I need to associate the OOB Approval workflow with the library that will be used as a publishing workflow. So if any user publishes the major version of the document it goes through the approval process.

Problem/Limitation: Associating the workflow with any list or library is fairly straight forward. Here are the steps:

  1. Fetch the workflow template from parent web where list exist
  2. Get the target list
  3. Create the workflow association
  4. Check if workflow is already associated with the list
  5. If workflow is not associated, add the workflow association to the list

Here is the code snippet for performing this task.

private void AssociateApprovalWorklowWithList(SPList list)
{
SPWeb web = list.ParentWeb;
SPSite site = web.Site;
try
{
bool allowUnsafeCurrent = web.AllowUnsafeUpdates;
web.AllowUnsafeUpdates = true;

//Associate the approval workflow with libraries
//based on Custom Documents template only
if (list != null && list.BaseTemplate.ToString() == "10050")
{
//Get Approval Workflow Template Base Id
Guid workflowBaseId = new Guid("8ad4d8f0-93a7-4941-9657-cf3706f00409");

//If workflow is already associated, don't re-associate
if (list.WorkflowAssociations.GetAssociationByBaseID(workflowBaseId) != null)
return;

// Get workflow history and task list
SPList historyList = EnsureHistoryList(web);
SPList taskList = EnsureTasksList(web);

//set the name of workflow association
string workflowAssociationName = string.Format("{0} - Approval", list.Title);

//Get workflow template by Base Id
SPWorkflowTemplate workflowTemplate = web.WorkflowTemplates.GetTemplateByBaseID(workflowBaseId);
if (workflowTemplate == null)
{
//Log - no template found and return
return;
}

// Create workflow association
SPWorkflowAssociation workflowAssociation =
SPWorkflowAssociation.CreateListAssociation(workflowTemplate, workflowAssociationName,
taskList, historyList);

// Add workflow association to custom list
list.WorkflowAssociations.Add(workflowAssociation);

workflowAssociation.AssociationData = String.Empty;

//.. details omitted
list.WorkflowAssociations.Update(workflowAssociation);
list.DefaultContentApprovalWorkflowId = workflowAssociation.Id;
list.Update();

web.AllowUnsafeUpdates = allowUnsafeCurrent;
}
}
catch (Exception ex)
{
//Log exception
}
finally
{
//Dispose objects
}
}

The above code snippet will work just fine. But there is a little problem.

What if the site is created on language other than English? One case ask, why would there be any problem as we are fetching the workflow template by Base Id. Well, it turned out that the Base Id is not the same across different languages, it varies. So it failed if site has different locale.

There is also another method GetWorkflowTemplateByName but it didn’t work out for me as well, even though I set the right culture.. So that means, we can not use both the methods.

Solution:¬†Trying both the methods to get the template didn’t work and I didn’t get anything by googling it either. So the only way out of this problem was the following:

Looking at the BaseIds of different sites, a pattern emerged. Let’s examine the BaseIds from English and Swedish sites, as we saw previously

8ad4d8f0-93a7-4941-9657-cf3706f00409 (English, LCID: 1033)
8ad4d8f0-93a7-4941-9657-cf3706f0041D (Swedish, LCID: 1053)

If you look closely, almost entire Id is same except last three digits Let’s take some more examples:

8ad4d8f0-93a7-4941-9657-cf3706f00415 (Polish, LCID: 1045)
8ad4d8f0-93a7-4941-9657-cf3706f00406 (Danish, LCID: 1030)

as you can see, only the last three digits vary from language to language.

Now take the LCID and convert it to hexadecimal representation (thanks to my colleague Matthias Einig for breaking this code ūüėČ )

Lets say,  for:

LCID = 1033 => hex = 409

LCID = 1053 => hex = 41d

LCID = 1045 => hex = 415

So, it became clear that the last three digits that differ are basically the hexadecimal representation of LCID. Having identified this pattern, I simply converted the LCID to hex value and there we have it, the BaseId for approval workflow template for current language

So,  if we change the line #15 in our code snippet above to the following:

Guid workflowBaseId = new Guid("8ad4d8f0-93a7-4941-9657-cf3706f00"+ web.Language.ToString("X"));

With this change, now no matter which locale the site has, it will construct the right workflow template id and will fetch the workflow template for association.

Hope it helps some folks out there ūüôā

Incorrect values for User Fields while copying list items from one site collection to another

Scenario

Recently, I was writting a piece of code to copy list item from a list in one site collection to the list in another site collection. I did the shallow copy (copying field by field).
Both the lists were created using same List definition so they had same fields and content types etc.
The item was successfully copied but then I noticed that the values of user fields were not correct.

Here is the pice of code that i used for copying the item


Public static void Main(string[] args)
{
    using (SPSite sourceSite = new SPSite("http://sp2010demo/sites/source"))
    {
      using (SPWeb web = sourceSite .OpenWeb())
      {
          SPList sourceList= web.Lists.TryGetList("Source List");
          //fetch first item - for demonstration purpose
          SPListItem sourceItem = sourceList.Items[0];
          //instantiate destination site collection
          using (SPSite destinationSite = new SPSite("http://sp2010demo/sites/destination/"))
          {
             var destinationList = destinationSite.RootWeb.Lists.TryGetList("DestinationList");
             if (destinationList != null)
             {
                //Add new item
                SPListItem destinationItem = destinationList.Items.Add();
                //copy item
                CopyListItem(sourceItem, destinationItem);
             }
          }
      }
   }
}

public static void CopyListItem(SPListItem sourceItem, SPListItem destinationItem)
{
        foreach (SPField field in sourceItem.Fields)
        { 
          //Filter out readonly and attachment fields
          if ((!field.ReadOnlyField) &amp;&amp; (field.InternalName != "Attachments"))
          {
                destinationItem[field.Title] = sourceItem[field.Title];
          }
        } 
        destinationItem.Update();
}

Apparently, it looked just fine and i used the same piece of code to copy the list items from one list to another within the same site collection and it worked just perfect as it should !

Problem

So, the problem was actually that when you move this item to a different site collection, users have to be present there in order for it to have the correct value.

If you look at the internal representation of the user field it is basically a look up value and the value is represented something like this: 1;#Sanjay Bhagia

Where first number is the user id in User Information List in the site collection that can be accessed (only if you are admin) by navigating to the following url in your site _catalogs/users/simple.aspx (e.g., http://sp2010demo/sites/source/_catalogs/users/simple.aspx).

So lets do some investigation.

I first open my source site collection i.e., http://sp2010demo/sites/source and navigate to my user information list by navigating to the following url http://sp2010demo/sites/source_catalogs/users/simple.aspx It shows all the users that have been added in my site

userinfosourcesite

Now, if you put the cursor on the user link, you can see the entire url in the status bar of your browser. At the end of the url you can see the querystring ID that has value 1 in this example.

Now lets navigate to the same user list in destination site collection by (http://sp2010demo/sites/destination/_catalog/users/simple.aspx).

Note: In my environment, I already have my user added in both the site collections, that is why I’m able to see my user at both the places but it is possible that you don’t see any user at both the places. User will be added automatically if you have logged in with that user on that site

userinfodestination

By observing the url of the same user we can see that the user id for this user in destination site collection is 10.

So if I’m copying the list item that has user field and that field has my user added (i.e., Sanjay Bhagia) the representation of that user in my source site collection will be 1;#Sanjay Bhagia but the representation of the same user in my destination site collection will be 10;#Sanjay Bhagia¬†!! Hence, the copied item will not display the proper user at the destination list item.

Solution
It is quite easy to fix this issue, I basically fetched the value from user field and called SPWeb.EnsureUser() method to make sure the user is added in the User Information List in my destination site collection and updated the LookupId attribute of the SPUser object.

Here is the modified code:


public static void CopyListItem(SPListItem sourceItem, SPListItem destinationItem)
{
    foreach (SPField field in sourceItem.Fields)
    {
        //Filter out readonly and attachment fields
	if ((!field.ReadOnlyField) && (field.InternalName != "Attachments"))
        {
            //only for user type fields
            if (field.GetType() == typeof(SPFieldUser))
            {
               SPWeb destinationParentWeb = destinationItem.ParentList.ParentWeb;
               if (sourceItem[field.Title] != null)
               {
                 //for field can have multiple selection enabled otherwise you can user SPFieldUserValue class
                 SPFieldUserValueCollection users = new SPFieldUserValueCollection(sourceItem.ParentList.ParentWeb, sourceItem[field.Title].ToString());
                 foreach (SPFieldUserValue user in users)
                 {
                    //this LookupId is the number of user object in User Information List
                    user.LookupId = destinationParentWeb.EnsureUser(user.User.LoginName).ID;
                 }
                destinationItem[field.Title] = users;
               }
            }
            else
              destinationItem[field.Title] = sourceItem[field.Title];
                    }
        }
    destinationItem.Update();
}

So basically, I’m making sure that user object in destination site collection has the correct look up id, that’s it!

Hope it helps.

Creating loosely coupled and generic process steps implementation

Recently, during one of the SharePoint implementations we came across the requirement to build the workflow for site provisioning engine that we are developing.

One of the requirements of the process is to rollback everything that we create/modify if there is any problem/exception during the process. So for example, if we created the site collection and then in the next step process failed at setting up custom permissions, the process should be roll backed to the initial stage.

Normally, this is no big deal to implement everything as a standard workflow and you write a piece of code for different activities of the workflow (or use standard workflow activities from Visual Studio toolbox) but then an idea came up why not write an implementation of the process steps independent of the workflow in a nice and loosely couple way that should take care of roll back in a much flexible and nicer and cleaner way !

So in this post, I will try to describe the solution that I came up with and also implementation details.

So here is the implementation details:

First, I created an interface that contains the method declarations for Execute and RollBack methods


public interface IProcessStep
{
   void Execute(ProcessStepProperties properties);

   void RollBack(ProcessStepProperties properties);
}

As you see in the code above, I’m using parameter of type ProcessStepProperties. This is basically a custom class that I use as a data transfer object (DTO) across different steps and methods. It’s basically a collection (dictionary in my implementation) that contain key/value pairs. Here is the implementation:


public class ProcessStepProperties
{
   public Dictionary<String, Object> Keys = new Dictionary<String, Object>();

   public bool KeyExists(String key)
   {
     if (Keys.ContainsKey(key) && Keys[key] != null)
        return true;
     return false;
   }
}

Having interface ready, we write a class that defines one the process steps. This step is CreateSiteStep that creates the site collection. This class inherits from our interface IProcessStep and hence it provides the implementation of Execute and RollBack methods.


public class CreateSiteStep : IProcessStep
{
   //private method to check for preconditions for Execute method
   private bool PrequisitesPresent(ProcessStepProperties properties)
   {
      if (properties.KeyExists("SiteNumber") && properties.KeyExists("Title") && properties.KeyExists("TemplateName"))
         return true;
      return false;
   }

   public void Execute(ProcessStepProperties properties)
   {
      //Check if preconditions are met
      if (PrequisitesPresent(properties))
      {
         //Create site collection
         CreateSiteFromRequest(properties);

         //Update the property bag
         var spSite = properties.Keys["SPSite"] as SPSite;
         if (spSite != null)
         {
            properties.Keys["SiteUrl"] = spSite.Url;
         }
      }

      //Write checkpoint for rollback
      TransactionLogger.Log(this);
   }

   public void RollBack(ProcessStepProperties properties)
   {
        // Add Logging information  - ULS or event logger if appropriate
        SPSite site = properties.Keys["SPSite"] as SPSite;
        DeleteSite(site);
   }

Now there are couple of things that need to be discussed in the above code.

Firstly, as you see, I have created a private method to check pre-conditions before the Execute method is executed. This is totally upto the specific step to check if it requires any pre-conditions to be met or any values it requires before it proceeds.

Secondly, as i mentioned above I’m using ProcessProperties class (key/value collection) to transfer the state across methods and steps, I’m updating (adding) the value in the collection right after Site collection has been created. In this way, the next step (or rollback) step will have the updated information that might be useful for further execution of the process.

Thirdly and quite importantly there is a call to Log method of  TransactionLogger class.
TransactionLogger.Log(this);

This is very important. Again, TransactionLogger is my custom class that I’m using to log in the information for steps that have successfully been completed. This serves the purpose of check point for me that i use to roll back when anything fails. I’m using Stack as a data structure to push the steps (of type¬†IProcessStep) whenever any step is successfully executed and if there is any failure during the execution, i simply pop the steps and invoke RollBack method until stack is empty – hence everything is cleaned up in case of failover.

Here is the implementation of TransactionLogger class.


public class TransactionLogger
{
  //to keep checkpoints
  private static readonly Stack TransactionStack = new Stack();

  //public property to check number of steps in the stack
  public static int LogCount
  {
      get { return TransactionStack.Count; }
  }

  private IProcessStep Pull()
  {
     return TransactionStack.Pop();
  }

  //Pushes the process step into the stack
  public static void Log(IProcessStep step)
  {
      TransactionStack.Push(step);
  }

  //Pops the step from stack and invoke rollback method for every step  until stack is empty
  public static void RollBack(ProcessStepProperties properties)
  {
      while (TransactionStack.Count > 0)
      {
          var step = TransactionStack.Pop();
          step.RollBack(properties);
      }
   }
}

So, basically the idea is that in case of any failure everything is rolled back and in the implementation above, TransactionLogger class doesn’t need to know the details about any ProcessStep class how to roll back because it is upto every step to roll back the things it has created. That is why TransactionLogger class adds IProcessStep object into the stack and that is our interface!
here is another class called SetSitePermissionGroupStep that basically adds custom permissions to the site collection that has been created in previous process step.


public class SetSitePermissionGroupStep : IProcessStep
{

   // .... code removed for clarity purpose ....

 public override void Execute(ProcessStepProperties properties)
 {
    if (PrequisitesPresent(properties))
    {
       AddAssociatedMemberGroups(properties, _key);
       TransactionLogger.Log(this);
    }
 }

 public override void RollBack(ProcessStepProperties properties)
 {
   //Roll back implementation
 }
}

So, as a recap, we defined an interface called IProcessStep that contains the declaration of Execute and RollBack methods.

Other classes (specific to process steps) inherit from IProcessStep and provide an implementation for Execute and RollBack methods. Execute method of every process step class also add check point after successful execution of the step using TransactionLogger.Log method.

Now having all this setup we need some sort of controller class that could use this implementation. This could be used from within the workflow as well but in my demonstration I’m using a class that implements the Queue and in that queue we can insert separate steps and then Process the queue in the end. Here is the implementation of my controller class.


public class TestProcessSteps
{
    private Queue _queue = new Queue();
    private ProcessStepProperties _properties = new ProcessStepProperties();

    public void Initialize(ProcessStepProperties properties)
    {
       _properties = properties;
    }
    public void Enqueue(IProcessStep step)
    {
       this._queue.Enqueue(step);
    }

    public void ProcessQueue()
    {
      try
      {
         while (_queue.Count > 0)
         {
            //pick item from the queue and invoke execute method
            var step = _queue.Dequeue();
            step.Execute(_properties);
         }
       }
       catch (Exception ex)
       {
          //roll back
          TransactionLogger.RollBack(_properties);
       }
    }
 }

If you notice above, if anything fails (i.e, exception occurs), RollBack method is invoked from TransactionLogger class (that basically pops up items from the stack and invoke rollback method on respective step).

Finally, here is how we test our implementation.


//Instantiate TestProcessStep class
TestProcessSteps testclient = new TestProcessSteps();

//setup properties
properties = InitializeProcessProperties(item);

//Invoke Initialize method on TestProcessSteps class (that basically initializes ProcessProperties)
testclient.Initialize(properties);

//Insert steps into Queue
testclient.Enqueue(new CreateSiteStep());
testclient.Enqueue(new SetSitePermissionGroupStep("MemberGroup"));
testclient.Enqueue(new SetSitePermissionGroupStep("OwnerGroup"));

//Process the Queue (that basically takes items from queue and invoke Execute methods on respective steps)
testclient.ProcessQueue();

Note that during the enqueue, I’m adding SetSitePermissionGroupStep step two times with different parameters – in this way you can actually define atomic steps also using the same class.

Hope it gives the general idea about my thought behind the implementation. Don’t hesitate to provide any feedback or issues if you find any in my solution.

Thanks for taking time to read this long post ūüôā

Remove webpart from a page on Personal site using delegate control

Customizing Personal Sites (site collections for users in my site) could be little challenging at times. One reason being that personal site is provisioned automatically by SharePoint and there could be more than one point from which user can provision the site and there is no support to create your own web template or site definition, as we can do with every other type of site (as per my knowledge). Creating your own web template provides great flexibility and easiness to provision sites ¬†having your own settings and all but lacking this feature for personal sites provide us more challenges, even for small customizations at times. In a recent project, one of the customer’s wish was to remove “Recent Blog Posts” web part from default page when user provisions the personal site.

Solution: After trying out different approaches the final solution turned out to create a delegate control that removes the web part from the page when site is provisioned and having this delegate control added on the site collection through feature stappling.

So, here is what I did to fulfill this requirement.

  1. Create a delegate control
  2. Here is the element file for delegate control

    
    <!--?xml version="1.0" encoding="utf-8"?-->
    
     <Control
     ControlAssembly="MyAssembly, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b848d3ec396f6039"
     ControlClass="MyAssembly.RemoveWebPartsFromPersonalSiteDefaultPage" Id="AdditionalPageHead" Sequence="50"
    
    
  3. Fetch the desired webpart and remove it

and in the code behind file for this delegate control add the functionality to fetch and remove the desired webpart (I’ve added this logic in Render method)



//... code removed

//Fetch Web Part title from SPCore resoruce file for current web language
 //It is important to fetch the localed based on current site's language not based on Browser locale or regional settings!
 var localizedRecentPostWebPartName = SPUtility.GetLocalizedString("$Resources:spscore,MySiteOnet_WebPart_Blog", "spscore", SPContext.Current.Web.Language);

//remove the webpart
 RemoveWebPartsFromWebPartPage(SPContext.Current.Web, "default.aspx", "MiddleRightZone", localizedRecentPostWebPartName);

 //add page redirectino logic otherwise it will be an infinite loop
 // ... code removed

Here is the definition of RemoveWebPartsFromWebPartPage method:



//... code removed

using (SP.SPLimitedWebPartManager webpartManager = web.GetLimitedWebPartManager(webPartPage.Url, Web.PersonalizationScope.Shared))
{
  var collection = webpartManager.WebParts;
  List webParts = new List();

  foreach (WebPart webPart in collection)
  {
    if (fromZoneId == webpartManager.GetZoneID(webPart))
    {
      if (webPart.DisplayTitle == webPartTitle)
      {
          webParts.Add(webPart);
      }
    }
  }

 // delete the web parts
 foreach (WebPart webPart in webParts)
 {
   webpartManager.DeleteWebPart(webPart);
 }
}

That’s it! that is the functionality that we need to remove the webpart. We created a delegate control that contains the code to remove the web part from desired page! Now comes the part where we need to execute this delegate. You can use different approaches depending on your requirement. For this demo, we create a simple site scoped feature and add this delegate as an item in that feature.

Build your solution, create the package and deploy it. Activate the feature. Having this feature activated, as soon as user navigates to the default page, our delegate control will kick in and looks for the desired web part and removes it!

Other approaches could be creating feature stappling mechanism that attaches our delegate control with desire web templates so that this control is attached to every site that is created.

Hope it helps!

AllowEveryoneViewItems – Enable anonymous access to files in list

SharePoint provides anonymous access to sites and document libraries etc but for that you have to set up the policy at web application level and then for specific site or document library as per your needs. But at times you don’t want to allow anonymous access for entire web application or site collection but simply want to provide access to files within a specific document library without going through the trouble of configuring and managing permissions.

If you want to access documents within a document library or files in a list (as an attachment) there is one quick and easy way to do that.

There is a list property called AllowEveryoneViewItems that makes all documents within a document library anonymously accessible without dealing with permissions. There is no option in UI to set this up so you have to either do it through code, declaratively (list definition element.xml) or Powershell (depending on your situation and need).

Here is the PowerShell code for setting this property on any list or library


$web = Get-SPWeb -Identity "[site url]"
$list = $web.Lists.TryGetList("[list title]");
$list.AllowEveryoneViewItems = $true
$list.Update()

This is how you define in list definition xml:


<!--?xml version="1.0" encoding="utf-8"?-->

	<ListTemplate
		Name="[list name]"
		DisplayName="[list display name]"
		Description="[list description]"
		Type="10000"
		BaseType="0"
		Default="True"
		VersioningEnabled="TRUE"
		Hidden="FALSE"
                AllowEveryoneViewItems="TRUE"
		HiddenList="FALSE" />

And this is how you can do it using code


using (SPSite site = new SPSite("[site collection url]"))
{
   using (SPWeb web = site.OpenWeb())
   {
       SPList list = rootWeb.Lists.TryGetList("[list title]");
       if (list != null)
       {
          if (!list.AllowEveryoneViewItems)
          {
             list.AllowEveryoneViewItems = true;
             list.Update();
          }
       }
    }
}

It is important to note here that this property does not apply to all list items, but only to documents in document libraries or to attachments in list items. So you have to access this file directly for it to work ! (reference: SPList.AllowEveryoneViewItems property)

Here is the code snippet for accessing the file directly using HttpWebRequest


var fileContent = String.Empty;
//construct path to the navigation file
string fileUrl = "[full url of the file]";

//create webrequest to fetch this file!
var httpRequest = (HttpWebRequest)WebRequest.Create(fileUrl);
using (var webResponse = (HttpWebResponse)httpRequest.GetResponse())
{
   if (webResponse.StatusCode == HttpStatusCode.OK)
   {
      using (var responseStream = new StreamReader(webResponse.GetResponseStream()))
      {
          //if we get the response, read the content from file as string
          fileContent = responseStream.ReadToEnd();
      }
   }
}