PSConfig /SharePoint Configuration wizard runs longer when upgrading the servers.

During production farm (multi servers environment) SP1 upgrade,  I noticed that the psconfig was running significantly longer. I expected that the first run would be the only one that took very long time, but in my case the subsequent runs on the other servers took on average 25 minutes to finish. As we know that the first run will update the SharePoint databases so, it makes senses that it will take longer time in proportion to the number of the databases. But subsequent runs should only take around 5 minutes each.

I noticed that my config wizard seems paused at certain times, then I checked the upgrade log in the 14 hive\LOGS, which I found that during that apparent paused the log spit out alot of this below message, and they are a lot, as this site is in Mysite DB which has hundreds of site collections.

[PSCONFIG] [SPContentDatabaseSequence] [DEBUG] [3/3/2012 10:10:24 PM]: Site with Id = 961cfd1e-5cf8-4e40-8756-0032a517119b is contained in dictSitesNeedUpgrade, but is not added. Possible sitemap conflicts. Could not connect to http://localhost:32843/SecurityTokenServiceApplication/securitytoken.svc. TCP error code 10061: No connection could be made because the target machine actively refused it 127.0.0.1:32843.

Looking at that message, it seems that the psconfig tried to communicate with the Token Service, which obviously shut down during the process because the IIS service W3SVC (World Web Pulbhisng Service) is stopped.

So I looked at the Central Admin > Upgrade and Migration > Review Databases Status and I found that any content databases for any site collections that use Claim Based Authentication have Status field saying Database is up to date, but some sites are not completely upgraded. It became clear to me why the psconfig run much longer during the servers upgrade, because it tries to upgrade any claim based site collections by calling unavailable Security Token Services. It seems the psconfig try to call this service several times before it spits out the error message and it worsened in my case as there are hundreds of site collections of this type.

So my solution is before running the psconfig  to upgrade the servers (after successfully upgrade the database by running the psconfig at the first time), I run powershell commands to upgrade the databases that have status not completely upgraded. as mentioned in previous paragraph. This will work as the Token Service will be available. Run below powershell script for each db once, where the db_name is the ‘not fully upgraded’ database name. After run this command, my configuration wizard only runs for approximately 5 minutes in each server.

  $ct = Get-SPContentDatabase -Identity <db_name>
  Upgrade-SPContentDatabase -id  $ct.id

SharePoint 2010: Perform complex operation in the WorkflowCompleted Event Receiver

In some of my projects I need to perform complex operations after the SharePoint Approval Workflow finished such as perform complex site provisioning when the status is approved or send an email when status is rejected. In SharePoint 2010 I can use workflow event receivers, one of them is WorkflowCompleted event. It has SPWorkflowEventProperties as its parameter. There is an issue with the parameter is that some its properties are null such as ActivationProperties & AssociationData. So there is no simple way to retrieve SPListItem on which the workflow runs.

Luckily, the InstanceId property is not null, and we can use this property to get the workflow task item from the Workflow Tasks list. And from the workflow task item we can extract listId, itemId and even the workflow name. Below are the internal name of the columns in the workflow task item that I use to retrieve the list item info:

  • WorkflowInstanceID (used this one in the CAML query)
  • WorkflowListId (GUID of the List on which the workflow associated)
  • WorkflowItemId (List item id on which the workflow runs)
  • WorkflowName (Workflow association name as also the workflow field name)

This code shows how to extract the workflow status information and run an operation based on the status during WorkflowCompleted event. Note: the status is based on SharePoint 2010 Approval Workflow Status.

public class SiteRequestListItemEvent : SPWorkflowEventReceiver
{
  //CAML query to retrieve task item based by WF instanceId
  private const string WfInstanceQuery = @"<Where><Eq>
          <FieldRef Name='WorkflowInstanceID'/><Value Type='GUID'>{0}</Value>
          </Eq></Where>";

  private const string WorkflowTaskList = "Workflow Tasks";

  public override void WorkflowCompleted(SPWorkflowEventProperties properties)
  {
    using (SPSite site = new SPSite(properties.WebUrl))
    using (SPWeb web = site.OpenWeb())
    {
      //Query Workflow Task List
      SPList wfTaskList = web.Lists[WorkflowTaskList];
      var items = wfTaskList.GetItems(new SPQuery()
      {
        Query = string.Format(WfInstanceQuery, properties.InstanceId.ToString())
      });
      if (items.Count > 0)
      {

        Guid listId = new Guid(items[0]["WorkflowListId"].ToString());
        int itemId = (int)items[0]["WorkflowItemId"];
        string workflowField = items[0]["WorkflowName"] as string;

        //Get the List item on which the workflow run
        SPListItem item = web.Lists[listId].GetItemById(itemId);

        //get the workflow status
        int status = Convert.ToInt32(item[workflowField]);

        //do something when approved
        if (status == 16)
        {
          //do something such as provision a site, apply permission, etc.
        }
      }

    }

  }

}

Below are the internal value sof the SPWorkflowStatus. SharePoint 2010 Approval workflow add some additional statuses such as Approved, Cancelled & Reject:

  • NotStarted = 0
  • FailedOnStart = 1
  • InProgress = 2
  • ErrorOccurred = 3
  • StoppedByUser = 4
  • Completed = 5
  • FailedOnStartRetrying = 6
  • ErrorOccurredRetrying = 7
  • ViewQueryOverflow = 8
  • Cancelled = 15
  • Approved = 16
  • Rejected = 17

Dynamic list form with ECMAScript Client Object Model

Client Object model is one of my favorite features in SharePoint 2010. It allows us to build rich features or interactive UI fairly quickly using Javascript and SharePoint Designer. In this post I will show how to use ECMAScript Client OM that performs custom operation in a custom Display Form. I will extend the custom form of  the Announcement List I created in my previous post to include additional operation.  In the list I added 2 more fields as shown below:

  1. Type: Yes/No, DisplayName: Read Flag, Internal Name: ReadFlag
  2. Type: User field (multi value), Display Name: Have Read, Internal Name: HaveRead

Then I added a button called ‘I have read’. The button will show whenever the ReadFlag field equal to ‘Yes’ and HaveRead doesn’t contain current user. And whenever a user click the button, I will add the current user into the HaveRead userfield collection and update the list item. There wouldn’t any managed code involve to implement this. Ok let’s get started with the detail implementation. I will reuse the same data form webpart and the its xlst from my previous post.

  1. On the dataform webpart I will add a new parameter binding to get the current user as shown below.
       <ParameterBinding Name="UserID" Location="CAMLVariable" DefaultValue="CurrentUserName"/>
    
  2. To use this variable in our xsl, we need to add UserID in the global parameter as shown below at line 3.
       <xsl:param name='ManualRefresh'></xsl:param>
      <xsl:param name='dvt_firstrow'>1</xsl:param>
      <xsl:param name="UserID"/>
    
  3. As I mentioned before that the button will be shown when ReadFlag = true and HaveRead doesn’t contain current user. The code below shows how to implement this.
    .
    <xsl:variable name="currentUserHasRead">
         <xsl:value-of select="contains(@HaveRead,$UserID)"/>
    </xsl:variable>
    ----------------------
    <xsl:if test="(@ReadFlag = '1')  and $currentUserHasRead = 'false'">
       <button class="ms-ButtonHeightWidth" id="IHaveRead" onclick="SetHaveRead({@ID});return false">
          I have read
      </button>
    </xsl:if>
    

    The first part of the xsl is creating a boolean variable which equals to true if current user is already added into HaveRead userfield collection. The 2nd part of the xsl is adding a html button inside an xsl if clause. The onclick button is calling a javascript function SetHaveRead and passing the itemId into the function. The ‘return false’ is added to avoid the button to perform postback

  4. Below is the client script that adding the current user to the HaveRead user field collection.
    var item;
    var ctx;
    var user;
    var notifyId;
    var $Id;
    
    function SetHaveRead(itemId) {
        notifyId = SP.UI.Notify.addNotification("Executing", true);
        $Id = itemId;
        ctx = SP.ClientContext.get_current();
        var announList = ctx.get_web().get_lists().getByTitle("Announcements");
        item = announList.getItemById(itemId);
        user = ctx.get_web().get_currentUser();
        ctx.load(item);
        ctx.load(user);
        ctx.executeQueryAsync(Function.createDelegate(this, this.doUpdate),
       Function.createDelegate(this, this.OnFailed));
    }
    
    function doUpdate(sender, args) {
        var users = item.get_item('HaveRead');
        if (!users)
            users = new Array();
    
        users.push(SP.FieldUserValue.fromUser(user.get_loginName()));
    
        ctx = SP.ClientContext.get_current();
        var announList = ctx.get_web().get_lists().getByTitle("Announcements");
        item = announList.getItemById($Id);
        item.set_item('HaveRead', users);
        item.update();
    
        ctx.load(item);
        ctx.executeQueryAsync(Function.createDelegate(this, this.UpdateSuccess),
        Function.createDelegate(this, this.OnFailed));
    }
    
    function UpdateSuccess(sender, args) {
        $("#IHaveRead").css("display", "none");
        SP.UI.Notify.removeNotification(notifyId);
    }
    
    function OnFailed(sender, args) {
        alert('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
        SP.UI.Notify.removeNotification(notifyId);
    }
    

    This is a brief explanation about the code. The first method is the one called by the button, in this one it grabs current item & current user using Client API. In doUpdate method, it retrieves the HaveRead field value, if it is empty, initialize an array finally using SP.FieldUserValue.fromUser to get retrieve the UserFieldValue from the current user and push it into the array, then performs item update operation. When updateSuccess, the button is hidden using jquery. During the operation the (Executing) notification will be shown as shown in 2nd pic on this post. This msdn article contains javascript Client OM API.

  5. Finally we need to add the javascript reference to the page.

In conclusion DataForm webpart & Javascript Client OM is a powerfull combination to perform sharepoint customization fairly quickly.

You can find the xsl here.

SharePoint 2010 – Add custom list form to existing list

In this post I will show you how to add a custom list form to existing list using SharePoint 2010 object model. Before we get into how, I would like to put some backgrounds about why I would do this:

  • I want to have a nicer display form (not in a form format) for an existing list such as anouncement list.
  • I also want to add a web part to the custom display form such as social comment web part.
  • And I want to be able to activate this display form as part of a feature.

This is my custom announcement display form looks like. As we can see that the list form web part has been replaced with a dataform webpart with custom rendering and also I added a social comment web part after the dataform web part. This can be done easily using SharePoint Designer. When creating a custom form, SharePoint Designer would add a DataForm web part instead of a ListForm web part to the aspx file. Interestingly, if we delete the dataform webpart, the aspx file would loose its function as a form. So we should modify the xlst of the dataform web part instead of replacing it.

To achieve the same result using codebehind, we need to do this followings:

1. Provision an aspx file and set it as a form page. As shown in the code below. In SharePoint 2007 we can’t achieve this as SPTemplateFileType.FormPage is new in SharePoint 2010.

  //the list's forms are located in the list's rootfolder
  SPFolder root = list.RootFolder;
  string customFormUrl = webUrl.LocalPath + "/" + root.Url + "/" + formName;
  //provision a new list form
  SPFile file = root.Files.Add(customFormUrl, SPTemplateFileType.FormPage);

2.Provision a webpart that implements IListWebPart such as ListFormWebPart, DataFormWebPart, DataViewWebPart, etc. Then we need to set the two properties of the web part: ListId, the List GUID, and PageType, this will determine what type of the form either display, new or edit form. My observation shows that without an IListWebPart and settings those 2 properties the aspx page wouldn’t be provisioned as a form. This below code excerpt shows how to provision the webpart and set those 2 properties (line 18, 19)

 //Provision IList Web Part in this case DataForm web part onto the new form
 SPLimitedWebPartManager limitedWebPartManager = file.GetLimitedWebPartManager(PersonalizationScope.Shared);
IListWebPart part = null;

//set specific dataform webpart properties
//for query (Parameter bindings & DataSources),
DataFormWebPart webPart = new DataFormWebPart();
webPart.ParameterBindings = ;
SPDataSource ds = new SPDataSource(); //the detail spdatasource initialization not shown
webPart.DataSources.Add(ds);

//for rendering XslLink
webPart.XslLink = webUrl.LocalPath + xslUrl;

//set the IListWebPart Propertes required for the form
part = webPart;
string str2 = list.ID.ToString("B").ToUpper(CultureInfo.InvariantCulture);
part.ListId = list.ID;
part.PageType = PAGETYPE.PAGE_DISPLAYFORM;

3. In the XSL don’t forget to add FormToolbar control as this would initialize the form’s ribbon, without it the form’s ribbon wouldn’t be displayed. The easiest way to get the right xsl is modifying the one created by SharePoint Designer when we provision a custom form via SharePoint Designer.

4. Lastly, set the list Default form. SPList has these properties: DefaultDisplayFormUrl, DefaultEditFormUrl and DefaultNewFormUrl. In my case I would set the DefaultDisplayFormUrl.

  list.DefaultDisplayFormUrl = customFormUrl;

With this approach we can provision custom forms on any list using code behind. This source code contains an example of an implementation of the approach by provisioning a custom display form for announcement list through a List Event receiver.

Related Post:
Dynamic list form with ECMAScript Client Object Model

SharePoint 2010: Custom Provider – cross site collection navigation

Recently I get a request to have a consistent top navigation across site collection, in which I need to retrieve the navigation items from a site collection to be used in different site collections (in the same web application).  As it is known that SharePoint is using Provider framework to build its navigation, and for Publishing site it is using PortalSiteMapProvider that provides additional caching mechanism. The issue with the provider is that it cannot live cross site collection boundary.

In this post, I would like to share how to build the custom navigation provider utilizing the PortalSiteMapProvider from different site collection (in this post I call it source site collection), I provide the source code at the end of this post. Let’s get started:

  1. We need to create a custom HttpHandler (ashx) that hosts the source site collection’s PortalSiteMapProvider. The ashx is deployed to layouts directory. This excellent post shows how to build the custom HttpHandler, in fact the source code is based on the post with some modification. Basically the handler will build JSON object tree based on the navigation structure of the source site collection.
  2. Then create a custom SiteMap provider that calls the ashx created at step 1. The provider will call the handler using WebRequest with the user credential, and deserialize the received JSON into a object structure used by the provider.
  3. Replace the implementation of OOB site map data source with our own. In v4.master the navigation data sources (SiteMapDataSource) are enclosed in delegate controls, below are the delegate controls in the master page
    Navigation Location Delegate Ctl ID
    Top navigation TopNavigationDataSource
    Quick launch QuickLaunchDataSource

    With this knowledge, we can replace the OOB data source with my own implementation. Below is the definition in my elements.xml to replace the top navigation data source with our own implementation.

    <Control Sequence="30"
       Id="TopNavigationDataSource"
       ControlClass="System.Web.UI.WebControls.SiteMapDataSource"
       ControlAssembly="System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
       <Property Name="ID">topSiteMap</Property>
       <Property Name="SiteMapProvider">SymprgNavigationProvider</Property>
       <Property Name="EnableViewState">false</Property>
       <Property Name="ShowStartingNode">true</Property>
     </Control>
    

    You can see that I replaced the top navigation data source implementation to use my custom provider: SymprgNavigationProvider. And also make sure the control sequence number is less than the OOB publishing control defined in Navigation feature (14 hive/TEMPLATE/FEATURES/Navigation /NavigationSiteSettings.xml- the OOB Publishing sequence number is 50).  Put this elements.xml to a web scoped feature. By activating the feature we can replace the top navigation.

  4. The last step is adding the custom navigation handler to the web.config inside siteMap > providers element as shown below. The custom provider (Symprg.NavProvider.NavigationProvider) has two custom attributes:
    • sourceUrl: the source site collection url that provides the navigation items
    • depth: the depth of the navigation, by default is 1 if not specified.
      <siteMap defaultProvider="CurrentNavigation" enabled="true">
        <providers>
           ---------------------
           <add name="SymprgNavigationProvider" type="Symprg.NavProvider.NavigationProvider, Symprg.NavProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d7699941b48f79bc" sourceUrl="http://aSiteCollection" depth="2"/>
        </providers>
      </siteMap>
    

You can find the source code here.

SharePoint 2010 dynamic approvers for OOB Approval Workflow part 1

This Article Series:

  1. SharePoint 2010 dynamic approvers for OOB Approval Workflow part 1
  2. SharePoint 2010 dynamic approvers for OOB Approval Workflow part 2

Microsoft has improved SharePoint’s Approval Workflow in SharePoint 2010 that it is now customizable where its completion condition, behavior of individual task and behavior of overall task processes can be changed on design time using SharePoint Designer 2010. The customization allow us to build more complex approval workflows such as updating list items/documents during the approval process, modify the permissions of the item, modify the email content, etc. This video shows you how to customize OOB Approval workflow.

The approvers of the worklow are defined on the Association or Initiation form. But in some cases we might want to have approvers determine dynamically based on the meta data of the workflow’s item. There are 2 approaches to inject approvers into the Approval Workflow:

  1. Inject the approvers to the Association Data from code behind. This approach can be achieved by starting the workflow in the code behind by calling StartWorkflow method of the SPWorkflowManager class and passing Association data parameter containing the dynamic approvers.
  2. Pass the approvers into the Approvers parameter of the Approval Workflow  during design time (in SharePoint Designer 2010). The picture shows the Approvers parameter.

To be able to inject the approvers data we need to know its data format. As we know, the approvers of the workflow are defined in its association form on the Approvers field as picture below. The association form is an Infopath form so that the approvers field on the form are represented by an xml data. Below is the xml data of the approvers (Approvers xml) as on the picture.

<d:Approvers
   xmlns:d="http://schemas.microsoft.com/office/infopath/2009/WSSList/dataFields"
   xmlns:pc="http://schemas.microsoft.com/office/infopath/2007/PartnerControls">
     <d:Assignment>
       <d:Assignee>
         <pc:Person>
           <pc:DisplayName>Daniel Laksana</pc:DisplayName>
           <pc:AccountId>aDomain\danielLogin</pc:AccountId>
           <pc:AccountType>User</pc:AccountType>
         </pc:Person>
         <pc:Person>
           <pc:DisplayName>Northwind Members</pc:DisplayName>
           <pc:AccountId>Northwind Members</pc:AccountId>
           <pc:AccountType>SharePointGroup</pc:AccountType>
         </pc:Person>
       </d:Assignee>
       <d:Stage xsi:nil="true" />
       <d:AssignmentType>Serial</d:AssignmentType>
      </d:Assignment>
      <d:Assignment>
       <d:Assignee>
         <pc:Person>
           <pc:DisplayName>aDomain\aGroup</pc:DisplayName>
           <pc:AccountId>aDomain\aGroup</pc:AccountId>
           <pc:AccountType>SecurityGroup</pc:AccountType>
         </pc:Person>
         <pc:Person>
           <pc:DisplayName>Northwind Visitors</pc:DisplayName>
            <pc:AccountId>Northwind Visitors</pc:AccountId>
            <pc:AccountType>SharePointGroup</pc:AccountType>
         </pc:Person>
         </d:Assignee>
          <d:Stage xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
         <d:AssignmentType>Parallel</d:AssignmentType>
     </d:Assignment>
  </d:Approvers>

There are 3 possible values of the AccountType:

  1. User
  2. SharePointGroup
  3. SecurityGroup for Active Directory group.

To inject dynamic approvers to the workflow, we can transform any user data from SPUser, SPFieldUserValue , UserProfile or any other data types to the xml format and passing it as part of the Association data of the workflow. You can find the sample implementation in the 2nd post of this series. Referring back to the 2 approaches I mentioned in the beginning of this post:

  1. With the code behind approach, StartWorkflow method of the SPWorklowManager, we can replace the Approvers part of the AssociationData property of the SPWorkflowAssociation parameter.
  2. For the workflow approach, we need to build a custom activity that has output format the same as the Approvers xml, and place the activity before the Approval workflow as shown in the picture.  I posted an article about the custom activity here.  The output xml must exclude the Approvers element as shown below as an example:
<d:Assignments xmlns:d=""http:="//schemas.microsoft.com/office/infopath/2009/WSSList/dataFields">
  <d:Assignment xmlns:d="http:=//schemas.microsoft.com/office/infopath/2009/WSSList/dataFields">
    <d:Assignee>
       <pc:Person
         xmlns:pc="http:=//schemas.microsoft.com/office/infopath/2007/PartnerControls">
        <pc:DisplayName>Approvers 1</pc:DisplayName>
        <pc:AccountId>Approvers 1</pc:AccountId>
        <pc:AccountType>SharePointGroup</pc:AccountType>
      </pc:Person>
       <pc:Person
         xmlns:pc="http:=//schemas.microsoft.com/office/infopath/2007/PartnerControls">
        <pc:DisplayName>Approvers 2</pc:DisplayName>
        <pc:AccountId>Approvers 2</pc:AccountId>
        <pc:AccountType>SharePointGroup</pc:AccountType>
      </pc:Person>
    </d:Assignee>
    <d:Stage>0</d:Stage>
    <d:AssignmentType>Serial</d:AssignmentType>
  </d:Assignment>
</d:Assignments>

With these mechanisms we can have dynamics approvers  for OOB Approval Workflow.

Microsoft SharePoint Workflow, Infopath, ASP.NET

Add custom commands on Infopath browser form ribbon

In Infopath 2010 browser form, the commands are shown on Ribbon, so from usability point of view, it would be great if we have our custom commands reside in the Ribbon.  The OOB Ribbon are provisioned by a SharePoint  feature called IPFSSiteFeatures, one of the xml files in it contains the Ribbon definition, IPFSRibbon.xml. And the command handlers are defined in javascript file, Core.js, in <hive 14>\TEMPLATE|LAYOUTS\INC

Infopath Custom Command

In the picture, we can see I have added a custom command called Complete. The  command will display a javascript prompt and then perform a postback where the code behind run a server operation. In my case it is set the field on the form, extract some data in the form into custom lists for reporting and send emails to interested party.

Below is the ribbon definition for the Complete command. The most important part is the Location, I put my custom command into the first Ribbon group (Ribbon.ContextualTabs.InfoPathHomeTab.FormAction). The command handler is a javascript function defined in CommandUIHandlers. And also make sure to provision this custom action as site feature (Scope=Web) as this custom command will be added to any Form libraries in the site.

<CustomAction Id="IFCustomRibbon" Location="CommandUI.Ribbon">
    <CommandUIExtension>
      <CommandUIDefinitions>
         <CommandUIDefinition Location="Ribbon.Tabs.InfoPathHomeTab.FormActions.Controls._children">
             <Button Id="Ribbon.Tabs.Lcms.FormActions.Controls.Complete"
               Sequence="70" Command="CompleteForm"
               Image16by16="/_layouts/$Resources:core,Language;/images/formatmap16x16.png" Image16by16Top="-48" Image16by16Left="-48"
              Image32by32="/_layouts/$Resources:core,Language;/images/formatmap32x32.png" Image32by32Top="0" Image32by32Left="-96"
              LabelText="Complete"
              TemplateAlias="o2"
              ToolTipTitle="Complete"
              ToolTipDescription="$Resources:CcaLcms,cui_desc_CompleteForm;"/>
        </CommandUIDefinition>
      </CommandUIDefinitions>
   <CommandUIHandlers>
       <CommandUIHandler Command="CompleteForm" CommandAction="javascript:CompleteForm();
          function CompleteForm(){
             if(confirm('This will complete the form and send notification to interested party'))
             {
               var $command = $get('FormControl_V1_I1_B29');
               $command.click();
             }
          }
        "/>
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>

The Command handler is mapped to a javascript function. In it it will display a confirmation to users and then perform a postback. The trick to do a postback is on the Infopath form we have a hidden button that runs custom code during on click event. As it is hidden the button wouldn’t be displayed on the browser form but its html representation (<input>) can be still be found in form’s html.

To have the button callable in javascript we need to get the button Id. This can be achieved by using IE Developer Tool or Firebug to inspect the browser form and get the id of the html input tag that represents the button. In this example The html input tag would have attributes: buttonid=FormComplete and value=Complete. The
FormControl_V1_I1_B29 in the javascript above is the input tag id of the Complete button.

With the mechanism we basically can execute any Infopath buttons from our ribbon’s custom commands, such as run a workflow, open a modal dialog, etc. Though there is a catch that the buttons id tend to change if we change the structure of the form, but if we put the hidden buttons on the very top of the form, we can avoid the changes of the ids when the form structure changes.

Additionally we also can call the submit and close from javascript. I got this following functions from Core.js mentioned at the beginning of the article. You can dig the Core.js to find more functions.

   var a = IPFSRibbon_GetFocusedFormControlId();
   IPFSRibbon_HandleButtonClick(a, 'submit');  //this will submit the form
   IPFSRibbon_HandleButtonClick(a, 'close');   //this will close the form
   var navigateUrl = CurrentFormData_UrlToNavigateToOnClose(a);

NOTE. You must delete the browser’s cache files every time deploying the changes of the ribbon definition (CustomAction).

Microsoft SharePoint 2010, Infopath 2010, .NET

SPD 2010 Custom Workflow Activity

In a recent project, I needed to build a custom workflow activity that would be used in SharePoint Designer. In my workflow I need to retrieve a list of users based on certain criteria. So I need a custom Activity that query a custom list as shown below containing a user field collection column called Users and other fields: Category and Range where it will be used for rule of determining the users.

Category Range Users

Before going into implementation detail, the picture below shows the end result of my custom activity.

SharePoint Designer Custom ActivityIts name is Lookup Worklow Approvers. It has 3 parameters:

  • List name: is the custom list where the activity lookups up for the users
  • Rule: is telling the activity how to retrieve users from the list. The rule is simple, for the custom list above it would be Field=Category, Field=Range, Result=Users. The activity would build a CAML query based on the rule.
  • the return values is coma separated user accounts.

Here is the steps to build this custom Activity:

  1. First create an actions file, an xml file use by SharePoint for defining Workflow Actions/Activities used by SharePoint Designer. It must be deployed in {hive 14}\TEMPLATE\1033\Workflow. The picture shows how it structures in VS SharePoint project. In this example, I created MyCustomActivities.actions.
    Below is the content of my actions file.
    <?xml version="1.0" encoding="utf-8" ?>
    <WorkflowInfo>
      <Actions>
        <Action Name="Lookup Workflow Approvers"
            ClassName="MyCustom.Workflow.Actions.LookupApproversActivity"
            Assembly="MyCustom, Version=1.0.0.0, Culture=neutral, PublicKeyToken=accb8d1cf1b32c7f"
            Category="My Custom Activities"
            AppliesTo="all">
          <RuleDesigner Sentence="Get Approver from List %1 with rule %2 (output to %3)">
            <FieldBind DesignerType="ListNames" Id="1" Text="list name" Field="ListValue" />
            <FieldBind DesignerType="Stringbuilder" Id="2" Text="rules" Field="RuleValue" />
            <FieldBind DesignerType="ParameterNames" Id="3" Text="approvers" Field="ReturnValue" />
          </RuleDesigner>
          <Parameters>
            <Parameter Name="ListValue" Type="System.String, mscorlib" Direction="In" DesignerType="ListNames" Description="Approver matrix list name." />
            <Parameter Name="RuleValue" Type="System.String, mscorlib" Direction="In" DesignerType="Stringbuilder" Description="Approver matrix fields." />
            <Parameter Name="ReturnValue" Type="System.String, mscorlib" Direction="Out" DesignerType="WritableFieldNames" Description="Workflow variable output by this action." />
            <Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" Direction="In" DesignerType="Hide" />
          </Parameters>
        </Action>
      </Actions>
    </WorkflowInfo>
    The explanation of the xml can be found here. The Rule Designer will be the text displayed on SPD where the variables are marked by %. The variables are mapped to Id attributes of the FieldBind elements. The FieldBind elements map the SPD designer variables to code behind variables defined in Parameters elements. One of the parameter element is __Context, this is the Workflow Context that would be passed into the codebehind.
  2. Next step, is the code behind. Please see this msdn article for step by step of creating a custom workflow activity.
         public class LookupApproversActivity : Activity
        {
            #region Fields
            public static DependencyProperty __ContextProperty = DependencyProperty.Register("__Context", typeof(WorkflowContext), typeof(LookupApproversActivity));
            public static DependencyProperty ReturnValueProperty = DependencyProperty.Register("ReturnValue", typeof(string), typeof(LookupApproversActivity));
            public static DependencyProperty ListValueProperty = DependencyProperty.Register("ListValue", typeof(string), typeof(LookupApproversActivity));
            public static DependencyProperty RuleValueProperty = DependencyProperty.Register("RuleValue", typeof(string), typeof(LookupApproversActivity));
            #endregion
    
        }
        

    After declaring the Dependency Properties, we defined the Properties, they must match Parameter elements defined in the action xml.

     #region Properties
            [ValidationOption(ValidationOption.Optional)]
            public WorkflowContext __Context
            {
                get
                {
                    return (WorkflowContext)base.GetValue(__ContextProperty);
                }
                set
                {
                    base.SetValue(__ContextProperty, value);
                }
            }
    
            [ValidationOption(ValidationOption.Optional)]
            public string ReturnValue
            {
                get
                {
                    return (string)base.GetValue(ReturnValueProperty);
                }
                set
                {
                    base.SetValue(ReturnValueProperty, value);
                }
            }
    
            [ValidationOption(ValidationOption.Optional)]
            public string ListValue
            {
                get
                {
                    return (string)base.GetValue(ListValueProperty);
                }
                set
                {
                    base.SetValue(ListValueProperty, value);
                }
            }
    
            [ValidationOption(ValidationOption.Optional)]
            public string RuleValue
            {
                get
                {
                    return (string)base.GetValue(RuleValueProperty);
                }
                set
                {
                    base.SetValue(RuleValueProperty, value);
                }
            }
    
            #endregion
    

    And then the implementation of Execute method: The __Context would be used to retrieve current web, current item as shown in the first 2 lines. Then it retrieves the custom List containing the users using Workflow Helper class GetListGuid method passing in ListValue variable and __Context. Next is parsing the RuleValue variable to retrieve the fields. As mentioned above the rule is field=<fieldname> for field and result=<fieldname> for the user field. At line 27, it performs CAML query to the rule list and then parse the result of the query to the ReturnValue variable. Note. There are several classes are my own utility classes such as QueryData and Utility.

    protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
    {
       SPWeb currentWeb = this.__Context.Web;
       SPListItem currentItem = currentWeb.GetListItem(this.__Context.CurrentItemUrl);
       SPList ruleList = currentWeb.Lists[Helper.GetListGuid(this.__Context,ListValue)];
       var rules = from rule in RuleValue.Split(new string[] { "\r\n", "\n", "\r", "," }, StringSplitOptions.RemoveEmptyEntries)
          let r = rule.Trim().Split(new char[] { '=' })
          select new
          {
             Type = r[0].ToLower().Trim(),
             Field = r[1].Trim(),
          };
    
      string approverFieldName = string.Empty;
      List qData = new List();
      foreach (var r in rules)
      {
         if (string.Compare(r.Type, "field", true) == 0)
         {
            SPField field = ruleList.Fields.GetField(r.Field);
            qData.Add(new QueryData() { Field = field.InternalName, Value = currentItem.GetItemProperty(r.Field), Type = field.TypeAsString });
          }
          if (string.Compare(r.Type, "result", true) == 0)
               approverFieldName = r.Field;
      }
    
      SPListItem val = ruleList.GetItems(new SPQuery() { Query = QueryData.BuildQuery(qData) }).Cast().FirstOrDefault();
    
      this.ReturnValue = Utility.ParseUserValues(val[approverFieldName] as SPFieldUserValueCollection));
    
      return ActivityExecutionStatus.Closed;
    }
    
  3. The last configuration that need to be done is registering the workflow as authorized type in the web.config under System.Workflow.ComponentModel.WorkflowCompiler element add this
    <authorizedType Assembly="MyCustom, Version=1.0.0.0, Culture=neutral, PublicKeyToken=accb8d1cf1b32c7f" Namespace="MyCustom.Workflow.Actions" TypeName="*" Authorized="True" />
    
    After deploying the solution, perform IIS Reset. Then our custom activity would be available in SPD Workflow Actions drop down.

In the next post I will show how to modify this custom activity to feed approvers to the SharePoint 2010 OOB Approval Workflow.

Microsoft SharePoint 2010,SharePoint Designer, SharePoint Workflow, ASP.NET

Customize Application pages top navigation bar

As known that in SharePoint 2010 the application pages have a new property called DynamicMasterPageFile that enable them to use the site master page instead of using the OOB application.master. But it would maintain the default top navigation control although the one in site master page changes.

The reason it maintains the default top navigation is that application pages override content in PlaceHolderTopNavBar with a user control called TopNavBar.ascx where it contains the default top navigation control (AspMenu).

To have the application master use our top nav control, we need to find the PlaceHolderTopNavBar and PlaceHolderHorizontalNav in the master page, set it Visible = false and make sure they don’t enclose any controls.

  <asp:ContentPlaceHolder id="PlaceHolderTopNavBar" runat="server" Visible="false"/>
  <asp:ContentPlaceHolder id="PlaceHolderHorizontalNav" runat="server" Visible="false"/>

Microsoft SharePoint 2010, SharePoint Designer, SharePoint Workflow

Ribbon: insert any Web Part using javascript

In this article I will show how to add a web part using SharePoint 2010′s javascript. As we know we can add web parts on a page using command in Contextual Ribbon Tab called Insert Web Part. Behind the scene there is a server control called WebPartAdder, this control registers a javascript file called wpadder.js containing functions for selecting  web part from web part gallery and inserting it. Tapping into these functions we can add any web parts purely using javascript.

As an example I will add a custom control in Insert Web Part Contextual Ribbon as shown below. The control would insert Contet Editor WebPart into a web part zone on a page.

Here is the ribbon definition for the Insert Content Editor command.

<CustomAction Id="AddCustomWebPart"
                    RegistrationId="100" RegistrationType="List"
                    Location="CommandUI.Ribbon">
    <CommandUIExtension>
      <CommandUIDefinitions>
        <CommandUIDefinition Location="Ribbon.WebPartInsert.WebParts.Controls._children">
          <Button
            Id="Ribbon.DL.WebPartInsert.WebParts.WebPart"
            Sequence="40"
            Command="insertMyWebPart"
            LabelText="Insert Content Editor"
            Image16by16="/_layouts/images/edit.gif"
            Image32by32="/_layouts/images/placeholder32x32.png"
            TemplateAlias="o1"
            ToolTipTitle="Insert my web part"
            ToolTipDescription="$Resources:core,cui_STT_ButInsertWebPart;"/>
        </CommandUIDefinition>
      </CommandUIDefinitions>
      <CommandUIHandlers>
        <CommandUIHandler Command="insertMyWebPart" CommandAction="javascript: addMyWebPart('Media and Content','Content Editor');"/>
      </CommandUIHandlers>
    </CommandUIExtension>
  </CustomAction>

In this example our CustomAction is applicable for general/custom list (RegistrationType=List and RegistrationId=100) . This msdn article lists all OOB ListTemplates Id. The location of the custom command is in  Ribbon.WebPartInsert.WebParts.Controls (Contextual Tab Insert Web Part Group). The command handler for the web part is our javascript function call addMyWebPart. Below is the the implementation of addMyWebPart.
function addMyWebPart(wpCategory, wpTitle) {
   var webPartAdder = SP.Ribbon.WebPartComponent.getWebPartAdder();
   var webPart = findWebPart(webPartAdder, wpCategory, wpTitle);
   if (webPart) {
     var selectedZone = $get('_wzSelected').value;
     if (!selectedZone) selectedZone = "Main";
       webPartAdder.addItemToPageByItemIdAndZoneId(webPart.id, selectedZone);                           
   }
   else
       alert('web part not found!');
}

function findWebPart(webPartAdder, category, wpTitle){
  if(webPartAdder){
    var wpCategory = findByTitle(webPartAdder._cats, category); 
    if(wpCategory)
      return findByTitle(wpCategory.items, wpTitle);
  }
}

function findByTitle(list, title){
   for (i=0; i < list.length; i++){
     var item = list[i];
     if(item.title == title)
       return item;
   }
}

The function’s arguments are the  web part’s Category and web part’s Title of the web part defined in web part gallery (_catalogs/wp).  First thing it would try to get the WPAdder instance. WPAdder class are defined in wpadder.js. It has several properties and method such as :

  • addItemToPageByItemIdAndZoneId method: this would add the web part into a page given the web part item id and zone id
  • _cats: containing the list of web part categories
  • Each web part category has items properties containing the list of web parts in the category
  • Both category and item have title property

After getting the WPAdder instance it would try to find the web part by iterating the web part category list and matching the title of the category. If the category found it would iterate the items in the category and match the title of the item. Having found the item, it would try to get the selected web part zone by checking the hidden variable called _wzSelected. Then after that called the method to add the webpart to the page.

We can provision the javascript above using another CustomAction with Location attribute sets to ScriptLinks as shown below.

 <CustomAction Id="AddWpScript" ScriptSrc="RibbonTest/RibbonScript.js" Location="ScriptLink" Sequence="100"></CustomAction>

This picture shows the project structure in visual studio. Put the 2 CustomActions (ribbon definition and javascript def) into the elements.xml and the javascript into the RibbonScript.js. Off-course, it needs a feature that contains the WpAdderElement to provision it.

To see how it works, go to any custom list and click Modify Form Web Part from the ribbon or add query string DisplayMode=Design to any view for example to see the design mode of the default view we can do  AllItems.aspx?DisplayMode=Design. This would open the page on Design mode  and then we can insert the Content Editor Web Part using our custom command in the Insert Web part Contextual Tab.

With this mechanism we can add any web part to page purely using javascript.

Microsoft SharePoint 2010,SharePoint Designer, SharePoint Workflow, Web Part