Search This Blog

Saturday, October 18, 2014

InfoPath 2010 - Sharepoint 2010 Custom Workflow

Workflow is one of the core features in Sharepoint, it is very flexible and very powerful, a handful of workflows are included out of the box but the real power of workflow is in custom workflows. For this example I'm going to create a new sequential list workflow that will create a new site collection when a InfoPath 2010 Form is submitted to a list that is running my custom workflow. The workflow will be initiated upon form submit but will not create a site collection until the site name list field is populated. The custom workflow will be deployed as a site feature.

In Part 2 I'll create a new Visual Studio solution for the custom workflow project, the project will include some exception handling in case any errors occur so I can see the output from the exception(s).

Prior to creating the Visual Studio project the following items need to be in place.

1. A demo Site Collection where the solution will be deployed
2. A customized forms list where I'll attach the workflow

Create the Sharepoint Site and List

First create a new site collection where the workflow will be deployed. Once this is in place create a new list, choose Site Actions - View All Site Content | Create. Select the Issue Tracking list type from the available list templates, for this example I will name the list Issues.

Once the Issues list has been created select the list and from the list tab in the ribbon choose customize form, this will open the form in InfoPath.


All of the default fields are ok for this example but we'll need to add a SiteName field to the form. I'll replace the Issue Status default field with the Site Name that I need for the custom workflow. Select the Issue Status text from the labels in the left column and change the text to Site Name, then select the dropdown to the right and delete this dropdown control.

Next with your cursor still in the area where the Issue Status dropdown control was double click the textbox control on the Home tab of the ribbon on the top of the page. This will add a new textbox control to the form.















The new textbox should be given a value of field1 by default. From the Fields dialog on the right side right click on the field1 property and select field properties.


Enter SiteName in the Display Name and Name properties, select ok.

Next select File and choose Quick Publish to save the changes to the form in Sharepoint.


This completes part 1, in part 2 I'll create the Visual Studio Solution with the custom workflow

Create the Visual Studio Project for the Workflow Solution

Select New Project | Visual C# | .Net Framework 3.5 | Empty Sharepoint Project


Add a new item to the project

Right click on the project name and select Add | New Item - Select C# | Sharepoint 2010 | Sequential Workflow


The Sharepoint Customization Wizard dialog will open, select a name for the new workflow InfoPath_Workflow, Select List Workflow



Note: The site you selected must have a task and workflow history list available for the new workflow or you will receive the following error message. This might occur if you used the blank site template to create the new site.


Select the Sharepoint lists to associate the workflow with



Ensure the workflow starts automatically when a new item is created checkbox is selected and click finish


The workflow designer surface will open in Visual Studio



Select View Toolbox and pin the Toolbox open. Expand the Windows Workflow V3.0 Node

Drag a While Control into the line under the onWorkflowActivated1 shape

Drag a Code Control onto the line under the whileActivity1 shape

Expand the Sharepoint Workflow Node

Drag a OnWorkflowItemChanged activity onto the text Drop and Activity Here inside the whileActivity1 shape



Note: Workflows are stored in a gallery at the Site Collection level so the scope will be Site.

Configure the Workflow Feature

When I added the Sequential Workflow to the project a Sharepoint Feature was added, I'll now configure this Feature. 




I'll start by expanding the features folder and renaming the feature to InfoPathWorkflow. Next I'll double click the Feature name to open the Feature properties. 



I'll change the Feature title in the Title textbox and ensure the Feature scope is set to Site. I'll also verify the the InfoPathWorkflow is listed in the Items in the Feature control.

Verify/Validate the Package

In the Solution Explorer I'll double click on the Package folder.



This will bring up the properties for the package, I'll rename the package and ensure the InfoPathWorkflow is in the Items in the Package control. Next I'll Validate the package and sure there are no warnings or errors with the deployment package.

Select View | Other Windows | Package Explorer



Right click on the package and select Validate



You should see a successful validation in the output window

This completes part 2, in part 3 I'll add the custom code to log any exceptions, allow the custom workflow to start and complete when specific conditions are met and create a new site collection.

Add Custom Code

Next I'll add some custom code to define the logic for the workflow by expanding the InfoPathWorkflow folder and double clicking the InfoPathWorkflow.cs class. This will bring up the design workflow design view. In the design view click the whileActivity1 red exclamation | Choose the property 'Condition' is not set option. 




In the properties window select from the Condition property dropdown: Code Condition




Next expand the Condition property and type into the child Condition property: notDone and press enter



The logic in the code will allow the workflow to run until our condition is met and we change a variable to indicate the workflow is complete.

First I'll create a method to indicate the workflow is not complete. Right click on the workflow design surface and select View Code. Find the auto-generated notDone method and replace the code with the following code snippet.

bool done = default(bool);
private void notDone(object sender, ConditionalEventArgs e)
{
     e.Result = !done;
}

The while activity looks at the e.Result value to determine if the loop will continue or not. The onWorkflowItemChanged event will await the event that the workflows list item has changed ( is the site name provided? ) when that happens the after event (invoked) of the onWorkflowItemChanged activity will fire providing the opportunity to change the done variable to true. The while loop will then run the notDone method to check the e.Result which will always return the opposite of done (!done) the while loop will exit when done is set to true.

Double click the onWorkflowItemChanged1 shape in the designer view to generate the onWorkflowChanged1_Invoked method. Replace the auto-generated method with the following code snippet

private void onWorkflowItemChanged1_Invoked(object sender, ExternalDataEventArgs e)
{
     isSiteNameProvided();
}

private void isSiteNameProvided()
{
     if(!string.IsNullOrEmpty(workflowProperties.Item["SiteName"] as string))
     {
          done=true;
     }
}

Double click on the onWorkflowActivated1 shape to generate the onWorkflowActivated1_Invoked method. Replace the auto-generated method with the following code snippet.

private void onWorkflowActivated1_Invoked(object sender, ExternalDataEventArgs e)
{
     isSiteNameProvided();
}

From the workflow designer surface click on the onWorkflowItemChanged1 red exclamation and choose the activity 'onWorkflowItemChanged1' does not have a Correlation Token property set option




Note: A Correlation Token is just an unique identifier for the workflow, it's used to keep all of the individual workflow activities together. 

From the properties window select the CorrelationToken property dropdown: workflowToken.

Back to the design surface and choose the codeActivity1 red exclamation, select the property 'ExecuteCode' is not set. Double click the codeActivity1 shape to generate the ExecuteCode method

Add Exception Handling to the Workflow

Next I'll add some error handling so if an exception occurs we'll write the text of the exception to the History List and set the column status to error logged and allow the workflow to exit and complete.

Next ensure the toolbox is open and the Windows Workflow V3 node is expanded. Drag and drop a FaultHandler activity onto the Drop FaultHandlerActivity Here text. 



In the Workflow Exceptions window select the faultHandlerActivity1 red exclamation, choose the Property 'FaultType' is not set or its value cannot be resolved to an actual type option. 



In the properties window select the FaultType property ellipses, in the Browse and Select a .NET type window Select Referenced Assemblies | mscorlib on the Type tab. Select the System type, locate Exception in the type name column (scroll the right pane down) Select the Exception type name (System.Exception) and select OK.



In the Visual Studio Toolbox expand the Sharepoint Workflow node, Drag and drop a LogToHistoryListActivity activity into the Drop Activities Here text. Drag and drop a SetState activity on the line below the logToHistoryListActivity1 shape

Next I'll configure the Log to History List Activity to write the exception details to the Workflow History List and the Set State Activity to end the Workflow with a custom status called Error Logged. 

On the InfoPathWorkflow design surface right click the logToHistoryListActivity1 shape and select the properties option. Select the HistoryDescription property and click the ellipsis. 

In the Bind History Description dialog expand faultHandlersActivity1 | faultHandlerActivity1 | Fault | Message and select OK


In the properties window select the HistoryOutcome property and click the ellipsis. In the bind History Outcome to an activity dialog expand faultHandlersActivity1 | faultHandlerActivity1 | Fault - Select the StackTrace option and select OK


In the Visual Studio Solution Explorer double click the elements.xml file

Replace the metadata node and its contents with the following code 

<MetaData>
  <ExtendedStatusColumnValues>
         <StatusColumnValue>Error Logged</StatusColumnValue>
  </ExtendedStatusColumnValues>
</MetaData>


Back to the design surface for the Infopath workflow, double click the setState1 Activity, replace the setState1_MethodInvoking with the following code

private void setState1_MethodInvoking(object sender, EventArgs e)
        {
            setState1.State = (int)SPWorkflowStatus.Max;
        }


This code will set the workflow state to the custom StatusColumnValue defined in the elements.xml file

Back to the workflow design surface, select the red exclamation next to setState1 activity


Choose the Activity setState1 does not have CorrelationToken property set, select the correlationtoken property dropdow: workflowToken

Now when an exception occurs it will be handled and logged as shown below



Create a New Site Collection and Add Quicklaunch Link to Site

The next part of this tutorial will involve programmatically creating a new site collection if the site name condition is met on the list we are attaching this custom workflow to. 

Right click on the InfoPath workflow design surface and select view code. In the top area of the InfoPathWorkflow.cs file add the following import directive

using Microsoft.SharePoint.Navigation;

Next replace the method codeActivity1_ExecuteCode with the following code.

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
        {
            //Get the Site Name
            string SiteName =
              workflowProperties.Item[SITE_NAME_COLUMN].ToString();
         
            // Get the system token, instantiate new site and web objects
            // in order to have permissions to create the new Site Collection 

            SPUserToken _sysToken = default(SPUserToken);
            SPSecurity.RunWithElevatedPrivileges(delegate()
            {
                using (SPSite site = new SPSite(workflowProperties.Site.Url))
                {
                    _sysToken = site.SystemAccount.UserToken;
                }
            });

            using (SPSite siteCollection = new SPSite(workflowProperties.Site.Url, _sysToken))
            {
                using (SPWeb web = siteCollection.OpenWeb(workflowProperties.Web.ServerRelativeUrl))
                {
                    //Gather user information
                    SPUser user = web.SiteAdministrators[0];
                    string adminLogin = getLoginName(user);
                    string adminEmail = user.Email;
                    string adminDisplayName = user.Name;

                    SPSite newSiteCollection = default(SPSite);

                    Microsoft.SharePoint.SPSecurity.RunWithElevatedPrivileges(
                      () =>
                      {
                         //Set the new site collection properties
                          newSiteCollection =
                            web.Site.WebApplication.Sites.Add(
                            "/sites/" + SiteName,  // Url
                            SiteName,
                            "Site Created by Submitting InfoPath Form",
                            1033,  // locale identifier - US English
                            "SGS#0",  // Basic Group Work Site Template
                            adminLogin,
                            adminDisplayName,
                            adminEmail);
                      });

                    if (default(SPSite) != newSiteCollection)
                    {
                        // Add Site Collection Administrators
                        addSiteCollectionAdministrators(web.SiteAdministrators,
                          newSiteCollection.RootWeb.SiteAdministrators);

                        string newSiteCollectionUrl =
                          newSiteCollection.MakeFullUrl(newSiteCollection.ServerRelativeUrl);

                        // Add Site collection link to Quick Launch
                        SPNavigationNode projectSiteNode =
                          new SPNavigationNode(SiteName, newSiteCollectionUrl, true);

                        // Create or retrieve the Site Collections node
                        SPNavigationNode siteCollectionsNode =
                          ensureHeadingNode("Site Collections", web.Navigation.QuickLaunch);
                      
                        siteCollectionsNode.Children.AddAsLast(projectSiteNode);
                    }
                }
            }
        }
      
        //Extract a readable username
        private string getLoginName(SPUser user)
        {
            string loginName = default(string);

            int pipePosition = user.LoginName.IndexOf("|");

            if (0 >= pipePosition)
            {
                loginName = user.LoginName.Substring(pipePosition + 1);
            }
            else
            {
                loginName = user.LoginName;
            }

            return loginName;
        }

        // Add the Site Collection Administrators from
        // the current Site Collection to the new Site Collection

        private static void addSiteCollectionAdministrators(
          SPUserCollection existingSiteAdmins, SPUserCollection newSiteAdmins)
        {
            // Add the Workflow's Site Collection Administrators
            // to the new Site Collection
            foreach (SPUser admin in existingSiteAdmins)
            {
                newSiteAdmins.Add(
                  admin.LoginName, admin.Email, admin.Name, admin.Notes);
            }
        }

        // Add a custom SPQuickLaunchHeading NavNode
        // if it doesn't already exist
        private static SPNavigationNode ensureHeadingNode(
          string nodeHeading, SPNavigationNodeCollection quickLaunch)
        {
            // Get the first NavNode in the Web's Quick Launch
            SPNavigationNode projectSitesNode = quickLaunch[0];

            if (nodeHeading != projectSitesNode.Title)
            {
                // Quick Launch Heading NavNode called nodeHeading
                // linked to the Web's Home Page (empty string)
                projectSitesNode =
                  new SPNavigationNode(nodeHeading, string.Empty);
                quickLaunch.AddAsFirst(projectSitesNode);
            }
            return projectSitesNode;
        }

Deploy and Test


The last part of this tutorial will involve deploying the Visual Studio Solution and testing.
From Visual Studio right click on the project name and select Deploy, ensure there are no errors in the build and wait for the process to complete.

Next, well visit the site that we deployed the workflow to, in my case it is at  http://w2k8r2_2010dev/sites/apps/

Select the list that we created in step 1 - Issues


From the Ribbon select the List tab and Click the Workflow Settings dropdown | Add Workflow

Select the InfoPath Workflow from the available Workflows, enter a name for the workflow and select the Start this workflow when a new item is created checkbox


From the Issues List create a new item

Note: If your URL has an underscore (_) in it you will receive the following message: The form cannot be displayed in the browser because the use of session cookies has been disabled in the current browser settings

In my case and can simply change the machine name in the URL with Localhost after adding an alternate access mapping and everything works.

Next Add the new item - MyIssue, I'll intentionally leave the SiteName field blank to demonstrate the workflow functionality



Immediately after we submit the new item the status will change to Starting


Then the workflow status will quickly change to In Progress


The workflow status will remain at In Progress until we edit the item we submitted and add a SiteName

Note: If a SiteName is added initially the workflow will complete quickly on its own

Next I'll edit the entry and add the SiteName (MyIssues Site) and in a minute or two I will see the Quicklaunch area under Site Collections will be updated with the new SiteName. If this is the first entry then the Site Collections heading will be added and a link to the new site will be listed below.


This completes this tutorial. If you would like to download the Visual Studio Solution Click Here



**************************************************************************************************************************************
***************************************************************************************************************************
InfoPath 2007 Forms and SharePoint 2007 Form Library Sample

In this article I will demonstrate how to create InfoPath form and publish it to SharePoint Form Library using InfoPath 2007. It will illustrate the concept of InfoPath form customization and publishing and I will use the Form Library described in this post in later articles about search customization and workflow integration.

UPD: I have published an article about InfoPath 2010 and SharePoint 2010 to start illustrating new capabilities and benefits. It's here: InfoPath 2010 and SharePoint 2010 - Part 1.


Scenario
We need an automated expense claim form for our organization, because currently all expense claims are paper-based and it's very ineffective and time-consuming to process expense claims. As a prerequisite I have created the Form Library called Expense Claims on my SharePoint Web site.

Solution

1. Start InfoPath 2007 and select Sample - Expense Report from the Customize a Sample dialog which appears upon the start:























In this scenario we will use predefined sample form, because it already has most of the data fields that used in many organizations. Therefore we can customize it according to our own needs rather than start from scratch.

2. Set up SharePoint form library connection to be able to publish our form template, to fill the form in browser and to submit the form.

First we remove the existing Main data connection.

Go to Tools->Data Connections menu item:




















Select Main submit connection and press Remove button:


































Press Yes button when you see the notification dialog:








Press Add button to create new data connection:






































In the appeared Data Connection Wizard select Create a new connection to: -> Submit data and hit Next button:
































Select "To a document library on a SharePoint site" option for a destination and hit Next button:

























Specify a Document Library URL - in my case it is "http://your_server_name/Expense%20Claims" - and file name - I created a function to make it unique - a combination of user name, start and end dates. Tick the checkbox "Allow overwrite if file exists" and hit Next button:
























Enter a name for this data connection and hit Finish button:























Close the data connections dialog:












































Go to Tools->Submit Options menu item:




























Tick the checkbox "Allow users to submit this form", select "Send form data to a single destination" and select "SharePoint document library" option from the dropdown. Then select the SharePoint data connection we have just created:





































Press Advanced button on the dialog and select "Close the form" in the "After submit:" dropdown. Then press OK button:














































3. 100% of clients I worked with in regard to InfoPath forms needed current user's information automatically populated from SharePoint profile, SharePoint list or some other user information storage. The less effort way is to create a data connection in the form template to consume relevant SharePoint Web Services and then pull out necessary information from that data connection. 

There are two great blog posts on the Web illustrating this method:

 InfoPath - Get the current user without writing code


We will use these two combined with some modifications which will allow us to remove some errors from the implementation. 

Choose Tools->Data Connections from menu:
































Press Add button:


































In the appeared Data Connection Wizard select Create a new connection to: -> Receive data and hit Next button: 




































Select Web service as a source of data and hit Next button:
































Specify the Web service URL as "http://your_server_name/_vti_bin/UserProfileService.asmx" and hit Next button:





























From the list of available operations select GetUserProfileByName and hit Next button: 
































Leave everything as it is on the Web service parameter screen and hit Next button:
















































Leave everything as it is on the following screen and hit Next button:





























Set the name of the connection as GetUserProfileByName (leave default in this case) check that "Automatically retrieve data when form is opened" is ticked on and hit Finish button:


































Our new connection should appear in the list of available connections. Now we have to convert the connection to be the UDC file. Hit Convert button:




































Before we continue we need to create Data Connection Library on our SharePoint Web site. Go to Create menu:

















Select Data Connection Library from the list of available Libraries types:






























Provide name and description for the new Data Connection Library. I called it Data Connections for simplicity. Hit Create button:




























When the newly created library appears in the browser copy the URL from the address bar. In my case it is "http://your_server_name/Data%20Connections/": 














Return back to the data connection convert screen and paste the data connection library URL into the relevant text box then add GetUserProfileByName.udcx as file name. In my case it is "http://your_server_name/Data%20Connections/GetUserProfileByName.udcx". Set up the link type as "Relative to site collection (recommended)" and hit OK button:

























Now we have got the connection ready to use in our form:




































Return to the Data Connections library and have a look at the data connection file we have just created. It's status is Pending which means we have to approve it so everybody will be able to use it to fill the form:


 Select Approve/reject action from the item menu for our connection file:





















Select Approved from the Approval Status options, add comment if necessary and hit OK button:


























Go to Tools->Form Options in the menu:






























Select "Open and Save" category and hit "Rules" button:




































Press Add button in the appeared dialog box:






















Provide the name for the new rule (Load User Info in my case) and hit Add Action button: 
































Press Select a field button:


































Select GetUserProfileByName data source and select AccountName from Query fields: 






































Press fx button near Value text box: 






























Hit Insert Function button in the Insert Formula dialog:













Select All from categories and function called userName: 



  










Action screen will come up with Field and Value set up. Click OK button: 

















On the Rule dialog we will see the first action added. Hit Add Action button again: 















In the action dropdown choose "Query using a data connection" option and choose GetUserProfileByName data connection. Hit OK button:















Hit Add Action button again: 

















Press Select a field button:
















Select Main data source and select "name" field from the form template Data Source. Hit OK button: 


















On the Insert Formula dialog press Insert Field or Group button:













Select GetUserProfileByName data source, select Value from Data fields and hit Filter Data button:


















On the Filter Data dialog hit Add button:











In the first dropdon select "Select field or group" option:






Select Name field from Data fields and click OK button:
  

















In the third dropdown (value) type the "PreferredName" text and hit OK button: 






You will see the new filter in the list of filters. You can add as many filters as you like, but for this scenario I will leave only one. Click OK when finished:












Click OK on the Select Field or Group dialog to save changes: 



  















 Click OK on the Insert Formula dialog to save changes:













Click OK to save new rule:
  














Click OK on the Rules for Opening Forms dialog:












4. Our form is ready to publish.

Go to File->Publish menu item:












Tick "Always save the form template when publishing" checkbox and press OK button: 





Select the first option and hit Next button:



















Enter the SharePoint Web site URL and hit Next button: 





















Tick "Enable this form to filled out by using a browser" checkbox, select Document Library option and press Next button:

















Select "Update the form template in an existing document library", select Expense Claim library and press Next button:














Press Add button to expose form template fields in SharePoint form library columns:















Select a field, specify whether you will use existing column or create new one and press OK button: 




If you specified that you are going to edit form data from SharePoint you would get the following message:


 Do the same for all fields you want to add to SharePoint form library and then press Next button when finished:



 Press Publish button and wait until the process is completed:


Tick the "Open this document library" checkbox and hit Close button:



 When the form library appears go to Settings->Form Library Settings action in the library menu:

Select General settings->Advanced settings:
  
 Select "Display as a Web page" option and press OK button:




 Create new form:


 If you set up everything correct you will see that user information is automatically displayed on the form:


Submit couple of forms for test:


No comments:

Post a Comment