10069 Views // 0 Comments // Not Rated

Importing SharePoint Designer Workflows Into Visual Studio (Part 3)

You've made it to Part 3! In Part 1, I described the general situations we faced importing SharePoint Designer 2010 (SPD) Workflows (WF) into Visual Studio 2010 (VS). Part 2 went into a lot of detail about the specific issues we encountered. In this final piece, I'll outline the...are you ready for this...SEVENTY-ONE step procedure that I was given. When the engineer at Microsoft first sent over this document, it was actually only a few very (although extremely convoluted) steps. So I broke them down and distilled the process into something that anyone familiar with VS could follow.

When designing a WF, assign its construction to either VS or SPD in the third place after classifying them as "simple" or "complex" in the second place having defined what each of those terms means to your team and their skillsets in the first place. Then, after the SPD WFs are built and tested, the following procedure gets them into VS, addressing almost all of the concerns listed in Part 2. I'm going to assume you're starting form an existing VS solution.

Let me begin by first defining some placeholders I use in the step-by-step:

  • [display name of workflow]: The "friendly" name of the WF, with spaces and capitalization.
  • [folder name of workflow]: After you do an import, you'll have a folder named "Workflows" and under that a folder with a cryptic-ish name that generally resembles an "intenral" name for your WF. For example, I had a WF with a display name of "Process Intake" and the generated folder was named "ProcessIntakeFT" in VS. Meh.
  • [workflow node]: In Solution Explorer, this is the representation of the folder with the above name under the "Workflows" folder off the root of the project. But it won't be a folder icon, since VS imports it as a module.
  • [solution namespace]: I follow a standard namespace convention, where my solution name is the name of the client, then the application name, then, for each project, a description of what it does. So something like Client.Application.DataAccess or Client.Application.Web. For workflows, I throw a .Workflows namespace in there, so they are all organized together. This would look like Client.Project.Workflows.SomeWorkflow. Therefore [solution namespace] is everything before the ".Workflows" part.
  • [display name of solution]: The "friendly" name of your application, which for me, is usually something like "Client Intranet" with spaces and capitalization.
  • [name of feature]: There is one feature in these SharePoint VS projects, and this will stand for whatever you rename it to.
  • [content type node]: This is another module under the [workflow node] folder that contains a content type SPD generates for the WF task.

Here's what Solution Explorer will look like after an import (I did clean up some of the node names to make it little more intuitive):

Solution Explorer

And here we go:

  1. In VS, create new project of type "Import Reusable Workflow" named [solution namespace].Workflows.[folder name of workflow].
  2. In the ensuing wizard, select the local SharePoint site URL and file location of the WSP exported from SPD.
  3. In Solution Explorer, rename the feature "[folder name of workflow]Workflow." This is then [name of feature].
  4. Double click the [name of feature].feature node.
  5. In the feature designer, give it a proper Title ("[display name of solution] [display name of workflow] Workflow") and description ("Installs the [display name of workflow] workflow and any supporting infrastructure.").
  6. Remove the content type from the asset list on the right by clicking on its box, and then clicking the "<" button. You'll see it move over to the list of assets on the left. Critical Note: this is a step we performed because we wanted to use our custom, existing content types for tasks. This is also the thing that didn't work! Omit this step to use the default content type SPD conjures up, and everything works.
  7. Press Control + S to save the file.
  8. In Solution Explorer, delete the folder for the content type (under the "Workflows\[folder name of workflow]" folder). Critical Note: Again, omit this to use the SPD content type.
  9. Copy the InfoPath 2010 (IP) forms into the workflow node (which is the first folder under "Workflows").
  10. Delete the "Other Imported Files" folder.
  11. Right click each XSN file, and select "Properties."
  12. In the property pane, change the "DeploymentType" to "ElementFile."
  13. There could be zero to many XSN files here, one for the association form (if defined) and one for each task content type the WF is associated with. Repeat the above two steps for each if there are any.
  14. In Solution Explorer, right click [workflow node], and select properties. Expand the "Feature Receiver" node.
  15. Enter "Microsoft.Office.Workflow.Feature, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" for the "Assembly."
  16. Enter "Microsoft.Office.Workflow.Feature.WorkflowFeatureReceiver" as the "Class Name."
  17. Click the ellipses in the value for "Feature Properties." A window pops up.
  18. Add a member with a key of "GloballyAvailable" and a value of "true."
  19. Add a member with a key of "RegisterForms" and a value of "[folder name of workflow]\*.xsn."

    Workflow Properties

  20. In Solution Explorer, if you are source controlled, check out all XSN files. If not, make sure they are not read-only, as the following steps involve opening the IP client application, which doesn't handle read-only files well. (It doesn't seem to refresh the file attributes, so if the XSN is read-only when opened, you'll have to close IP, make the file not read-only, and open it back up.)
  21. Right click the workflow node and select "Open Folder in Windows Explorer."
  22. In Windows Explorer, right click the task form (the XSN file with, hopefully, the word "Task" somewhere in the file name), and select "Design." (Note: although it's technically possible to have many task forms as mentioned above, this procedure only supports using one.)
  23. In InfoPath, click "File."
  24. Click "Form Template Properties."
  25. Copy the "ID" field into a new instance of Notepad. Precede it with [TASK].
  26. Click "OK."
  27. Close InfoPath.
  28. Click "Don't Publish."
  29. In Windows Explorer, right click the association form's XSN file and select "Design."
  30. In IP, click "File."
  31. Click "Form Template Properties."
  32. Copy the "ID" field into the existing Notepad instance. Precede it with [ASSN].
  33. Click "OK."
  34. In Notepad, your text file should look like this:


  35. In IP, click on the "Page Design" tab.
  36. Select the "Associate" view in the "Views" section's "View" dropdown in the ribbon.
  37. Click "Properties."
  38. In the popup, check "Set as default view."
  39. Press "OK."

    Info Path Default View

  40. Click on the "Data" tab.
  41. Click "Form Load" in the "Rules" section.
  42. In the "Rules" pane on the right, click "New" and select "Action."
  43. Name it "Switch view on form load" under "Details for."
  44. Click the link under "Condition."
  45. In the popup, select "The expression" in the first dropdown.
  46. Enter "isStartWorkflow = "true"" (without the outer quotes) in the textbox.
  47. Click "OK."
  48. Back in the "Rules" pane, click "Add" and select "Switch views."
  49. Select "Start" in the "View" dropdown.
  50. Click "OK."

    Info Path Form Load Rules

  51. Click the disk (save) icon above "File."
  52. Click "Save" in the popup.
  53. Close InfoPath.
  54. Click "Don't Publish."
  55. In Visual Studio, in Solution Explorer, open the "Elements.xml" file under the [workflow node].
  56. Add the following markup directly above the closing tag toward the bottom of the file:

    Code Listing 1

    1. <Instantiation_FormURI>[ID from notepad of the association form]</Instantiation_FormURI>
    2. <Association_FormURN>[ID from notepad of the association form]</Association_FormURN>
    3. <Task0_FormURN>[ID from notepad of the task form]</Task0_FormURN>

  57. Set the value for the "Workflow" node's "Name" attribute to "[display name of solution] [display name of workflow] Workflow."
  58. Press Control + S to save the file.
  59. Add the following file references to the project (some of these assemblies need to sucked out of the GAC, so this post has an attachment that's a ZIP file of these DLLs):
    • Microsoft.Office.Workflow.Actions.dll
    • Microsoft.Office.Workflow.Pages.dll
    • Microsoft.Office.InfoPath.Server.dll
    • Microsoft.Office.InfoPath.dll
  60. Add the "SharePoint 'Layouts' Mapped Folder" to the project.
  61. Add an Application Page to this folder's child. Name it "MyTaskFormHost."
  62. In the Application Page HTML editor, remove all content except for the @Page directive.
  63. Copy the following ABOVE your @Page directive:

    Code Listing 2

    1. <%@ Assembly Name="$SharePoint.Project.AssemblyFullName$" %>
    2. <%@ Assembly Name="Microsoft.Office.Workflow.Pages, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
    3. <%@ Import Namespace="Microsoft.SharePoint.ApplicationPages" %>
    4. <%@ Assembly Name="Microsoft.SharePoint, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>

  64. Copy the following BELOW your @Page directive:

    Code Listing 3

    1. <%@ Import Namespace="Microsoft.SharePoint.WebControls" %>
    2. <%@ Register Tagprefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
    3. <%@ Register Tagprefix="Utilities" Namespace="Microsoft.SharePoint.Utilities" Assembly="Microsoft.SharePoint, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
    4. <%@ Import Namespace="Microsoft.SharePoint" %>
    5. <%@ Assembly Name="Microsoft.Web.CommandUI, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
    6. <%@ Register Tagprefix="InfoPath" Namespace="Microsoft.Office.InfoPath.Server.Controls" Assembly="Microsoft.Office.InfoPath.Server, Version=, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
    7. <%@ Register TagPrefix="wssuc" TagName="LinksTable" src="/_controltemplates/LinksTable.ascx" %>
    8. <%@ Register TagPrefix="wssuc" TagName="InputFormSection" src="/_controltemplates/InputFormSection.ascx" %>
    9. <%@ Register TagPrefix="wssuc" TagName="InputFormControl" src="/_controltemplates/InputFormControl.ascx" %>
    10. <%@ Register TagPrefix="wssuc" TagName="LinkSection" src="/_controltemplates/LinkSection.ascx" %>
    11. <%@ Register TagPrefix="wssuc" TagName="ButtonSection" src="/_controltemplates/ButtonSection.ascx" %>
    12. <%@ Register TagPrefix="wssuc" TagName="ActionBar" src="/_controltemplates/ActionBar.ascx" %>
    13. <%@ Register TagPrefix="wssuc" TagName="ToolBar" src="/_controltemplates/ToolBar.ascx" %>
    14. <%@ Register TagPrefix="wssuc" TagName="ToolBarButton" src="/_controltemplates/ToolBarButton.ascx" %>
    15. <%@ Register TagPrefix="wssuc" TagName="Welcome" src="/_controltemplates/Welcome.ascx" %>
    16. <asp:Content ID="Content1" ContentPlaceHolderId="PlaceHolderAdditionalPageHead" runat="server">
    17. </asp:Content>
    18. <asp:Content ID="Content2" ContentPlaceHolderId="PlaceHolderPageTitle" runat="server">
    19. <SharePoint:EncodedLiteral ID="EncodedLiteral1" runat="server" text="<% $Resources:dlc, WrkTask_PageTitle %>" EncodeMethod="HtmlEncode" />
    20. </asp:Content>
    21. <asp:Content ID="Content3" ContentPlaceHolderId="PlaceHolderPageTitleInTitleArea" runat="server">
    22. <a tabindex="1" href="<% SPHttpUtility.AddQuote(SPHttpUtility.UrlPathEncode(List.DefaultViewUrl,true),Response.Output); %>">
    23. <% SPHttpUtility.HtmlEncode(List.Title,Response.Output); %>
    24. <a>
    25. </asp:Content>
    26. <asp:Content ID="Content4" ContentPlaceHolderId="PlaceHolderTitleBreadcrumb" runat="server">
    27. <asp:SiteMapPath SiteMapProvider="SPContentMapProvider" id="ContentMap" SkipLinkText="" runat="server" />
    28. </asp:Content>
    29. <asp:Content ID="Content5" ContentPlaceHolderId="PlaceHolderLeftNavBar" runat="server">
    30. </asp:Content>
    31. <asp:Content ID="Content6" ContentPlaceHolderId="PlaceHolderMain" runat="server">
    32. <SharePoint:FormComponent ID="FormComponent1" TemplateName="WorkflowEditFormToolBar" ControlMode="Edit" runat="server">
    33. <table class="ms-informationbar" style="margin-top: 10px;" border="0" cellpadding="2" cellspacing="0" width="100%">
    34. <tr>
    35. <td width="10" valign="center" style="padding: 4px;">
    36. <img src="/_layouts/images/Workflows.gif" alt="<% SPHttpUtility.AddQuote(SPHttpUtility.HtmlEncode(GetLocString("WrkTask_PageTitle")),Response.Output); %>" />
    37. </td>
    38. <td>
    39. <% SPHttpUtility.NoEncode(m_pageDescription, Response.Output); %>
    40. </td>
    41. </tr>
    42. </table>
    43. <InfoPath:XmlFormView ID="XmlFormControl" runat="server" style="width: 100%;" />
    44. <SharePoint:FormDigest ID="FormDigest1" runat="server" />
    45. </asp:Content>

  65. In the code behind file of this page, update the using statements at the top of the file to the following:

    Code Listing 4

    1. using System;
    2. using Microsoft.SharePoint;
    3. using Microsoft.Office.Workflow;
    4. using Microsoft.SharePoint.WebControls;
    5. using Microsoft.Office.InfoPath.Server.Controls;

  66. Change the class to inherit from "WrkTaskIPPage."
  67. Update the contents of the class to be the following:

    Code Listing 5

    1. protected override void OnInit(EventArgs e)
    2. {
    3. XmlFormControl.Initialize += new EventHandler<InitializeEventArgs>(XmlFormControl_Initialize_Custom);
    4. XmlFormControl.SubmitToHost += new EventHandler<SubmitToHostEventArgs>(XmlFormControl_OnSubmitToHost_Custom);
    5. XmlFormControl.Close += new EventHandler(XmlFormControl_OnClose_Custom);
    6. base.OnInit(e);
    7. }
    8. protected void XmlFormControl_Initialize_Custom(object sender, InitializeEventArgs ea)
    9. {
    10. this.ModifySettings();
    11. }
    12. protected void XmlFormControl_OnSubmitToHost_Custom(object sender, SubmitToHostEventArgs e)
    13. {
    14. this.ModifySettings();
    15. }
    16. protected void XmlFormControl_OnClose_Custom(object sender, EventArgs e)
    17. {
    18. this.ModifySettings();
    19. }
    20. private void ModifySettings()
    21. {
    22. this.m_isCustomXsn = true;
    23. int taskType = (int)this.m_task[SPBuiltInFieldId.TaskType];
    24. if (taskType == 2)
    25. {
    26. this.XmlFormControl.DefaultView = "ChangeView";
    27. }
    28. }

  68. In Visual Studio, rebuild the project, make sure it compiles.
  69. In Solution Explorer, if you're using the SPD content type, open the elements.xml file under [workflow node]\[content type node].
  70. In the XML editor, under the "FormURLs" element, set "_layouts/ [solution namespace].Workflows.[folder name of workflow]/ MyTaskFormHost.aspx" as the value for the existing "Display" and "Edit" elements.
  71. Add a "New" element as a child to "FormURLs" and set "_layouts/ [solution namespace].Workflows.[folder name of workflow]/ MyTaskFormHost.aspx" as the value for that as well. If you are indeed using your own custom content type, which, recall, is the piece that fails with this procedure, go into the code or configuration that provisions your existing content type, and make sure that "_layouts/ [solution namespace].Workflows.[folder name of workflow]/ MyTaskFormHost.aspx" is set for the NewFormUrl, EditFormUrl, and DisplayFormUrl properties.

The code and markup above was given to me by Microsoft, so use third-hand code at your own risk! But in general, that's it. I mentioned a few times that the one thing that didn't work was using existing content types for our task forms. This was a show-stopper for us, and the only reason we didn't move forward with this procedure. But I hope this outlines what has to be dealt with when exporting "complex" SPD WFs into VS.

And, of course, what has to be dealt with after making assumptions...

Have fun!

No Tags

No Files

No Thoughts

Your Thoughts?

You need to login with Twitter to share a Thought on this post.