Walkthrough: Create SharePoint 2010 Workflow Association Form and Initiation Form in Visual Studio 2010 by using Application Pages (Part 1 of 2)

Because of some requests by my blog readers I’d like to show you how to create Workflow Association and Initiation (“Instantiation”) forms in Visual Studio 2010 for use with a sequential or state machine workflow in SharePoint 2010.

This is Part 1 of 2 that describes the steps 1 to 17. You will find Part 2 here: https://blog.kenaro.com/2011/04/24/walkthrough-create-sharepoint-2010-workflow-association-form-and-initiation-form-in-visual-studio-2010-by-using-application-pages-part-2-of-2/

For this demo I’ll use my sample project on Codeplex: http://spworkflowdemo.codeplex.com/

There I’ve shown how to create Workflow Task Forms with Visual Studio. See my blog posts on this topic: Part 1, Part 2 and Part 3.

To follow this walkthrough you need to create a blank SharePoint 2010 site collection. I used this URL for the site collection: “http://sharepoint.local/sites/workflow”.

The code of this walkthrough is published in the demo project at Codeplex. If you would like to follow the walkthrough step by step you can start with the demo projects source code bundled in release 0.1.0.0. Or you just download the package 0.2.x.0 where the complete (working) code is included.

If you have difficulties to deploy the project in Visual Studio 2010 please read the “Part 3” post of my previous blog series.

There is a article on MSDN that I used years ago to do this for MOSS. The code of this article is adapted from the MSDN article. I can’t find them at the moment. I’ve modified the code and “upgraded” them form SPS2010.

Let’s start…

1. You need to have a site collection “http://sharepoint.local/sites/workflow”.

2. Now start Visual Studio 2010 and open the downloaded package of my Codeplex hosted project http://spworkflowdemo.codeplex.com/

Use project version 0.1.0.0 !!! Deploy the project. If you have difficulties to deploy it please read this blog post:

3. In this walkthrough we will create a sequential workflow named “Workflow 2”.

3. Now we add a new Task List for our workflow. Add a new “List Definition” project item named “Workflow 2 Tasks”.

image

Click “Add”.

In the next step select “Task” as base type for the List Defintion and check “Add a list instance”.

image

In the “Schema.xml” file of the create list definition edit the “List” tag at the beginning of the file: Add the attribute “Type” with Value “107”.

Modify the “ContentTypes” tag:

    <ContentTypes>
      <ContentTypeRef ID="0x010801">
      </ContentTypeRef>
    </ContentTypes>
This references to the “Workflow Task” content type.
Now edit the “Elements.xml” file of the List Definition (not the “Elements.xml” file of the List Instance!).
Change the “Type” attribute of the “ListTemplate” tag to “107”. You may change the “Description” attribute.
Now change the “Elements.xml” file of the List Instance.
Change the “Title” attribute of the “ListInstance” tag to “Workflow 2 Tasks”.
Furthermore change the “TemplateType” attribute to “107” and the “Url” attribute to “Lists/Workflow2Tasks”.

4. Now we ass a new “host list” for the workflow. Our workflow will be associated with this list. It’s a simple list of type “Custom List” with no column modifications.

Add  a new “List Definition” project item named “Workflow 2 Host List”.

image

Click “Add”.

Now choose “Custom List” and change the display name. Be sure to choose “Add list instance…”.

image

Click “Finish”.

You may change the “Elements.xml” of the List Instance. Modify the “Title” and “Url” attributes of the “ListInstance” tag.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <ListInstance Title="Workflow 2 Host List"
                OnQuickLaunch="TRUE"
                TemplateType="10001"
                Url="Lists/Workflow2HostList"
                Description="My List Instance">
  </ListInstance>
</Elements>

No further changes have to be done for now.

5. Add a new module project item “Module” named “Workflow 2 Forms”. There we will store our Initiation and Association forms.

image

Click “Add”.

First remove the “Sample.txt” file from the module.

Than modify the “Elements.xml” file of the module. Add a “URL” attribute to the “Module” tag and set it’s value to “Workflow2Forms”. Add a “RootWebOnly” attribute to the “Module” tag and set the value to “False”.

6. Now add a new project item of type “Class” to the module. Name the class “Workflow2Data” and mark them as “public”. This class will contain the data the users have to enter on Association or Initiation form.

image

Modify the namespace of the new class file. It should be “ik.SharePoint2010.Workflow”.

Add the Attribute “[Serializable()]” to the class definition. (The entered association and initiation data will be serialized as XML in the SharePoint database.)

image

Add this “using” lines at top of the file:

using System.IO;
using System.Xml.Serialization;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Now add four public string properties to this class. Name them “Data1” … “Data4”. Use this template for all of the properties:

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

         private string _dataX = default(string);
 
         public string DataX
         {
             get
             {
                 return _dataX;
             }
             set
             {
                 _dataX = value;
             }
         }

All of the “DataX” properties will be used in association form, but only “Data3” and “Data4” will be editable in initiation form too!

Furthermore we need a static “DeserializeFormData” method:

         public static Workflow2Data DeserializeFormData(string xmlString)
         {
             using( MemoryStream stream =    new MemoryStream(Encoding.UTF8.GetBytes(xmlString)) )
             {
                 XmlSerializer serializer = new XmlSerializer(typeof(Workflow2Data));
                 Workflow2Data data = (Workflow2Data)serializer.Deserialize(stream);
                 return data;
             }
         } 

7. Now we add another “Class” project item to the module named “Workflow2DataPages”. This class have to be “public”. This will become the base class of the Association Form and Initiation Form ASPX’s code behind class.

image

Set the namespace of the class to “ik.SharePoint2010.Workflow”.

Set the base class to “LayoutsPageBase”.

image

Replace all the “usings” at the top of the file through this:

using System;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.WebControls;

using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Xml;
using System.Xml.Serialization;

using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.Security;
using System.Security.Permissions;

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Now add an enumeration to the class definition:

public enum FormType
{
    Association,
    Initiation
}

.csharpcode, .csharpcode pre
{
font-size: small;
color: black;
font-family: consolas, “Courier New”, courier, monospace;
background-color: #ffffff;
/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt
{
background-color: #f4f4f4;
width: 100%;
margin: 0em;
}
.csharpcode .lnum { color: #606060; }

Add the following field class members. You will need them on every association or instantiation form you may create.

protected SPList list;
protected SPContentType contentType;
protected string workflowName;
protected HyperLink hlReturn;
protected string requestQueryString;
protected bool useContentTypeTemplate = false;

Now add some special field class members that are belong to the current workflow context. Here we add four TextBoxes for the “Data” properties that we will enter in our association or instantiation forms:

protected TextBox textboxData1;
protected TextBox textboxData2;
protected TextBox textboxData3;
protected TextBox textboxData4;

8. In “Workflow2DataPages.cs” add the method “OnLoad”:

 protected override void OnLoad(EventArgs e)
 {
     base.OnLoad(e);
 
     EnsureRequestParamsParsed();
 
     SPBasePermissions perms = SPBasePermissions.Open | SPBasePermissions.ViewPages;
     if( useContentTypeTemplate )
         perms |= SPBasePermissions.AddAndCustomizePages;
     else
         perms |= SPBasePermissions.ManageLists;
 
     Web.CheckPermissions(perms);
 }
 

This code will parse the query string part of the URL and check the users permissons.

The following Method is called by “OnLoad” and parses the query string part of the URL:

 protected void EnsureRequestParamsParsed()
 {
     workflowName = Request.Params["WorkflowName"];
 
     string strListID = Request.QueryString["List"];
     string strCTID = Request.QueryString["ctype"];
 
     if( strListID != null )
         list = Web.Lists[new Guid(strListID)];
 
     if( strCTID != null )
     {
         requestQueryString = "ctype=" + strCTID;
 
         if( list != null )
         {
             requestQueryString += "&List=" + strListID;
             contentType = list.ContentTypes[new SPContentTypeId(strCTID)];
         }
         else
         {
             contentType = Web.ContentTypes[new SPContentTypeId(strCTID)];
             useContentTypeTemplate = true;
         }
     }
     else
         requestQueryString = "List=" + strListID;
 }
 

9. The next method we will add to “Workflow2DataPages” is used for deserializing the workflows initiation or association data from the SharePoint objects.

 internal void PopulatePageFromXml(string associationXml, FormType type)
 {
     Workflow2Data Workflow2Data = new Workflow.Workflow2Data();
     if( !string.IsNullOrEmpty(associationXml) )
     {
         XmlSerializer serializer = new XmlSerializer(typeof(Workflow2Data));
         XmlTextReader reader = new XmlTextReader(new System.IO.StringReader(associationXml));
         Workflow2Data = (Workflow2Data)serializer.Deserialize(reader);
     } 
     /* ikarstein: Start your modifications here */
     if( type == FormType.Association )
     {
         textboxData1.Text = Workflow2Data.Data1;
         textboxData2.Text = Workflow2Data.Data2;
     }
 
     textboxData3.Text = Workflow2Data.Data3;
     textboxData4.Text = Workflow2Data.Data4;
/* ikarstein: End of Modifications */ }
  

As you can see the method will use a XmlSerializer object to deserialize the data stored in the parameter named “associationXml”. The method sets the values of the four TextBoxes that will be used to enter data in the browser.

10. Now we add a method for serializing the workflow data after the user has modified them in the browser.

 internal string SerializePageToXml(FormType type)
 {
     Workflow2Data data = new Workflow2Data();
     /* ikarstein: Start your modifications here */
     if( type == FormType.Association )
     {
         data.Data1 = textboxData1.Text;
         data.Data2 = textboxData2.Text;
     }
 
     data.Data3 = textboxData3.Text;
     data.Data4 = textboxData4.Text;
     /* ikarstein: End of Modifications */
 
     using( MemoryStream stream = new MemoryStream() )
     {
         XmlSerializer serializer = new XmlSerializer(typeof(Workflow2Data));
         serializer.Serialize(stream, data);
         stream.Position = 0;
         byte[] bytes = new byte[stream.Length];
         stream.Read(bytes, 0, bytes.Length);
         return Encoding.UTF8.GetString(bytes);
     }
 }
  

11. At least we add the following method to “Workflow2DataPages.cs”:

 [SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
 protected override void OnPreInit(EventArgs e)
 {
     base.OnPreInit(e);
 
     string customMasterUrl = SPControl.GetContextWeb(this.Context).MasterUrl; //~masterurl/default.master
     this.MasterPageFile = customMasterUrl;
 }
  

12. Now we can start creating the Association Form ASPX page.

In the Visual Studio project add a new “Application Page” project item named “Workflow2AssociationForm.aspx”.

image

This item will be created inside the “Layoutsik.SharePoint2010.Workflow” folder. You have to move them from there into the module named “Workflow 2 Forms”. Do this by Drag’n’Drop in the Solution Explorer pane.

image

Now edit the ASPX page.

Modify the “Page” tag:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Workflow2AssociationForm.aspx.cs" 
Inherits="ik.SharePoint2010.Workflow.Workflow2AssociationForm"
MasterPageFile="~masterurl/default.master" %>

As you can see we need to replace the “DynamicMasterPageFile” attribute through “MasterPageFile” and change the “Inherits” attribute by modifying the classes full name. (Below we will change the namespace of the generated class file.)

Now we need to add some “Register” tags to register some SharePoint controls for using them in the site.

<%@ Register TagPrefix="wssuc" TagName="LinksTable" src="/_controltemplates/LinksTable.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="InputFormSection" src="/_controltemplates/InputFormSection.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" src="/_controltemplates/InputFormControl.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="LinkSection" src="/_controltemplates/LinkSection.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" src="/_controltemplates/ButtonSection.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="ActionBar" src="/_controltemplates/ActionBar.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="ToolBar" src="/_controltemplates/ToolBar.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="ToolBarButton" src="/_controltemplates/ToolBarButton.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="Welcome" src="/_controltemplates/Welcome.ascx" %>

You can remove the content placeholder named “PlaceHolderAdditionalPageHead”.

The following ASP.NET code we add to the content placeholder named “PlaceHolderPageTitle”:

    <asp:Literal ID="Literal1" runat="server" Text="Customize Workflow" />

The following ASP.NET code we add to the content placeholder named “PlaceHolderPageTitleInTitleArea”:

    <%
        string strPTS = "Customize " + workflowName;
        SPHttpUtility.HtmlEncode(strPTS, Response.Output);
    %>
    :<asp:HyperLink ID="hlReturn" runat="server" />

The following ASP.NET code we add to the content placeholder named “PlaceHolderPageImage”:

    <img src="/_layouts/images/blank.gif" width="1" height="1" alt="" />
The following ASP.NET code we add to the content placeholder named “PlaceHolderPageDescription”:
    <%
        string strPD = "Use this page to customize this instance of " + workflowName + ".";
        SPHttpUtility.HtmlEncode(strPD, Response.Output);
    %>

Now we add a ASP.NET table control and some more content to the content placeholder named “PlaceHolderMain”. Inside this snipped the TextBoxes are defined that the user will use later while editing the association data.

<asp:Table CellSpacing="0" CellPadding="0" BorderWidth="0" CssClass="ms-propertysheet">
    <wssuc:InputFormSection Title="Workflow Data Values" Description="Specify the default workflow data values." runat="server">
        <template_inputformcontrols>
            <wssuc:InputFormControl Runat="server" LabelText="Specify Form Data Values:">
                <Template_Control>
                    <table border="0" cellspacing="0" cellpadding="0">
                        <!-- ikarstein: Start your modifications here -->
                        <tr>
                            <td class="ms-authoringcontrols">
                                Data 1: <asp:TextBox id="textboxData1" runat="server"></asp:TextBox>
                            </td>
                        </tr>
                            <tr>
                            <td class="ms-authoringcontrols">
                                Data 2: <asp:TextBox id="textboxData2" runat="server"></asp:TextBox>
                            </td>
                            </tr>
                            <tr>
                            <td class="ms-authoringcontrols">
                                Data 3: <asp:TextBox id="textboxData3" runat="server"></asp:TextBox>
                            </td>
                            </tr>
                            <tr>
                            <td class="ms-authoringcontrols">
                                Data 4: <asp:TextBox id="textboxData4" runat="server"></asp:TextBox>
                            </td>
                            </tr>
                        <!-- ikarstein: End of modifications -->
                    </table>
                </Template_Control>
            </wssuc:InputFormControl>
        </template_inputformcontrols>
    </wssuc:InputFormSection>
        
    <input type="hidden" name="WorkflowDefinition" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["WorkflowDefinition"]),Response.Output); %>'/>
    <input type="hidden" name="WorkflowName" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["WorkflowName"]),Response.Output); %>'/>
    <input type="hidden" name="AddToStatusMenu" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["AddToStatusMenu"]),Response.Output); %>'/>
    <input type="hidden" name="AllowManual" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["AllowManual"]),Response.Output); %>'/>
    <input type="hidden" name="RoleSelect" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["RoleSelect"]),Response.Output); %>'/>
    <input type="hidden" name="GuidAssoc" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["GuidAssoc"]),Response.Output); %>'/>
    <input type="hidden" name="SetDefault" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["SetDefault"]),Response.Output); %>'/>
    <input type="hidden" name="HistoryList" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["HistoryList"]),Response.Output); %>'/>
    <input type="hidden" name="TaskList" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["TaskList"]),Response.Output); %>'/>
    <input type="hidden" name="UpdateLists" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["UpdateLists"]),Response.Output); %>'/>        
    <input type="hidden" name="AutoStartCreate" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["AutoStartCreate"]),Response.Output); %>'/>
    <input type="hidden" name="AutoStartChange" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["AutoStartChange"]),Response.Output); %>'/>
        
    <wssuc:ButtonSection runat="server">
        <template_buttons>
            <asp:PlaceHolder runat="server">
                <asp:Button runat="server" class="ms-ButtonHeightWidth" OnClick="BtnOK_Click" Text="OK" id="btnOK" />
            </asp:PlaceHolder>
        </template_buttons>
    </wssuc:ButtonSection>
</asp:Table>

(I’ve marked the section you should modify for your special needs with HTML comments…)

13. Now we modify the “code behind” for Workflow2AssociationForm.aspx. – Open “Workflow2AssociationForm.aspx.cs”. Replace the content with this code:

 using System;
 using Microsoft.SharePoint;
 using Microsoft.SharePoint.WebControls;
 using Microsoft.SharePoint.Utilities;
 using Microsoft.SharePoint.Workflow;
 using System.Web.UI.WebControls;
 using Microsoft.SharePoint.Security;
 using System.Security.Permissions;
 using System.Web;
 
 namespace ik.SharePoint2010.Workflow
 {
     public partial class Workflow2AssociationForm : Workflow2DataPages
     {
         protected struct WorkflowOptions
         {
             public string taskListName;
             public string historyListName;
             public Guid taskListId;
             public Guid historyListId;
 
             public bool allowManual;
             public bool autoStartCreate;
             public bool autoStartChange;
 
             public bool setDefault;
             public bool updateLists;
             public bool lockItem;
         }
 
         protected WorkflowOptions workflowOptions = new WorkflowOptions();
 
         protected SPWorkflowTemplate baseTemplate;
         protected SPWorkflowAssociation associationTemplate;
         protected HyperLink hlReturn;
 
         protected override void OnPreRender(EventArgs e)
         {
             base.OnPreRender(e);
             if( associationTemplate != null )
                 PopulatePageFromXml((string)associationTemplate.AssociationData, FormType.Association);
         }
 
         protected override void OnLoad(EventArgs ea)
         {
             base.OnLoad(ea);
 
             //Get the Workflow Name.
             FetchAssociationInfo();
             GetTaskAndHistoryList();
         }
 
         private void FetchAssociationInfo()
         {
             SPWorkflowAssociationCollection wfAccociationCollection;
             baseTemplate = Web.WorkflowTemplates[new Guid(Request.Params["WorkflowDefinition"])];
             associationTemplate = null;
 
             if( contentType != null )
             {
                 // Associating with a content type.
                 wfAccociationCollection = contentType.WorkflowAssociations;
                 hlReturn.Text = contentType.Name;
                 hlReturn.NavigateUrl = "ManageContentType.aspx" + requestQueryString;
             }
             else
             {
                 list.CheckPermissions(SPBasePermissions.ManageLists);
 
                 wfAccociationCollection = list.WorkflowAssociations;
                 hlReturn.Text = list.Title;
                 hlReturn.NavigateUrl = list.DefaultViewUrl;
             }
             if( wfAccociationCollection == null || wfAccociationCollection.Count < 0 )
             {
                 throw new SPException("No Associations Found");
             }
 
             workflowOptions.autoStartCreate = ( Request.Params["AutoStartCreate"] == "ON" );
             workflowOptions.autoStartChange = ( Request.Params["AutoStartChange"] == "ON" );
             workflowOptions.allowManual = ( Request.Params["AllowManual"] == "ON" );
             workflowOptions.lockItem = ( Request.Params["LockItem"] == "ON" );
             workflowOptions.setDefault = ( Request.Params["SetDefault"] == "ON" );
             workflowOptions.updateLists = ( Request.Params["UpdateLists"] == "TRUE" );
 
             string associationGuid = Request.Params["GuidAssoc"];
             if( associationGuid != string.Empty )
             {
                 associationTemplate = wfAccociationCollection[new Guid(associationGuid)];
             }
 
             SPWorkflowAssociation checkForDuplicateTemplate = wfAccociationCollection.GetAssociationByName(workflowName, Web.Locale);
 
             if( checkForDuplicateTemplate != null && ( associationTemplate == null || associationTemplate.Id != checkForDuplicateTemplate.Id ) )
             {
                 throw new SPException("Duplicate workflow name is detected.");
             }
         }
 
         private void GetTaskAndHistoryList()
         {
             if (useContentTypeTemplate)
             {
                 workflowOptions.taskListName = Request.Params["TaskList"];
                 workflowOptions.historyListName = Request.Params["HistoryList"];
             }
             else
             {
                 string taskListGuid = Request.Params["TaskList"];
                 if (taskListGuid[0] != ) // already existing list
                 {
                     workflowOptions.taskListId = new Guid(taskListGuid);
                 }
                 else  // new list
                 {
                     SPList list = null;
                     workflowOptions.taskListName = taskListGuid.Substring(1);
                     try
                     {
                         list = Web.Lists[workflowOptions.taskListName];
                     }
                     catch (ArgumentException)
                     {
                     }
 
                     if (list != null)
                         throw new SPException("A list already exists with the same name as that proposed for the new task list. "+
                                               "Use your's Back button and either change the name of the workflow or "+
                                               "select an existing task list.&lt;br&gt;");
                 }
 
                 // Do the same for the history list
                 string historyListGuid = Request.Params["HistoryList"];
                 if (historyListGuid[0] != ) // user selected already existing list
                 {
                     workflowOptions.historyListId = new Guid(historyListGuid);
                 }
                 else // User wanted a new list
                 {
                     SPList list = null;
 
                     workflowOptions.historyListName = historyListGuid.Substring(1);
 
                     try
                     {
                         list = Web.Lists[workflowOptions.historyListName];
                     }
                     catch (ArgumentException)
                     {
                     }
                     if (list != null)
                         throw new SPException("A list already exists with the same name as that proposed for the new history list. Use your's Back button and either change the name of the workflow or select an existing history list.&lt;br&gt;");
                 }
             }
         }
 
         public void BtnOK_Click(object sender, EventArgs e)
         {
             SPList taskList = null;
             SPList historyList = null;
             if (!IsValid)
                 return;
             if (!useContentTypeTemplate)
             {
                 // If the user requested a new task list, create it.
                 if( workflowOptions.taskListId == Guid.Empty )
                 {
                     string description = string.Format("Task list for the {0} workflow.", workflowName);
                     workflowOptions.taskListId = Web.Lists.Add(workflowOptions.taskListName, description, SPListTemplateType.Tasks);
                 }
 
                 // If the user requested a new history list, create it.
                 if( workflowOptions.historyListId == Guid.Empty )
                 {
                     string description = string.Format("History list for the {0} workflow.", workflowName);
                     workflowOptions.historyListId = Web.Lists.Add(workflowOptions.historyListName, description, SPListTemplateType.WorkflowHistory);
                 }
                 taskList = Web.Lists[workflowOptions.taskListId];
                 historyList = Web.Lists[workflowOptions.historyListId];
             }
 
             // Perform association (if it does not already exist).
             bool isNewAssociation;
             if (associationTemplate == null)
             {
                 isNewAssociation = true;
                 if (!useContentTypeTemplate)
                     associationTemplate = SPWorkflowAssociation.CreateListAssociation(baseTemplate,
                                         workflowName, taskList, historyList);
                 else
                 {
                     associationTemplate = SPWorkflowAssociation.CreateWebContentTypeAssociation(baseTemplate, workflowName, 
                                         workflowOptions.taskListName, workflowOptions.historyListName);
                 }
             }
             else // Modify existing template.
             {
                 isNewAssociation = false;
                 associationTemplate.Name = workflowName;
                 associationTemplate.SetTaskList(taskList);
                 associationTemplate.SetHistoryList(historyList);
             }
 
             // Set up startup parameters in the template.
             associationTemplate.Name = workflowName;
             associationTemplate.LockItem = workflowOptions.lockItem;
             associationTemplate.AutoStartCreate = workflowOptions.autoStartCreate;
             associationTemplate.AutoStartChange = workflowOptions.autoStartChange;
             associationTemplate.AllowManual = workflowOptions.allowManual;
 
             if (associationTemplate.AllowManual)
             {
                 SPBasePermissions newPerms = SPBasePermissions.EmptyMask;
 
                 if (Request.Params["ManualPermEditItemRequired"] == "ON")
                     newPerms |= SPBasePermissions.EditListItems;
                 if (Request.Params["ManualPermManageListRequired"] == "ON")
                     newPerms |= SPBasePermissions.ManageLists;
 
                 associationTemplate.PermissionsManual = newPerms;
             }
 
             // Place data from form into the association template.
             associationTemplate.AssociationData = SerializePageToXml(FormType.Association);
 
             // If this is a content type association, add the template to the content type.
             if (contentType != null)
             {
                 if( isNewAssociation )
                     contentType.WorkflowAssociations.Add(associationTemplate);
                 else
                     contentType.WorkflowAssociations.Update(associationTemplate);
 
                 if( workflowOptions.updateLists )
                     contentType.UpdateWorkflowAssociationsOnChildren(false);
             }
             else // Else, if this is a list association.
             {
                 if (isNewAssociation)
                     list.WorkflowAssociations.Add(associationTemplate);
                 else
                     list.WorkflowAssociations.Update(associationTemplate);
 
                 if (associationTemplate.AllowManual && list.EnableMinorVersions)
                 {
                     // If this WF was selected to be the content approval WF 
                     // (m_setDefault = true, see association page) then enable content
                     // Approval for the list.
                     if (list.DefaultContentApprovalWorkflowId != associationTemplate.Id && workflowOptions.setDefault)
                     {
                         if (!list.EnableModeration)
                             list.EnableModeration = true;
                         list.DefaultContentApprovalWorkflowId = associationTemplate.Id;
                         list.Update();
                     }
                     else if( list.DefaultContentApprovalWorkflowId == associationTemplate.Id && !workflowOptions.setDefault )
                     {
                         // Reset the DefaultContentApprovalWorkflowId
                         list.DefaultContentApprovalWorkflowId = Guid.Empty;
                         list.Update();
                     }
                 }
             }
 
             SPUtility.Redirect("WrkSetng.aspx", SPRedirectFlags.RelativeToLayoutsPage, HttpContext.Current, requestQueryString);
         }
     }
 }
 
 

In most cases you should not need to modify this code! Because the serialization and deserialization of the data is done in the “Workflow2DataPages” base class.

14. Now we create another “Application Page” project item named “Workflow2InitiationForm.aspx”. This will be created in folder “Layoutsik.SharePoint2010.Workflow”. Move them to the module project item named “Workflow 2 Forms”.

image

Now you can remove the “Layouts” folder in the project structure in the Solution Explorer pane. (This folder was created automatically by adding a “Application Page” project item.)

image

Now edit the ASPX page.

Modify the “Page” tag:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Workflow2InitiationForm.aspx.cs" 
Inherits="ik.SharePoint2010.Workflow.Workflow2InitiationForm"
MasterPageFile="~masterurl/default.master" %>

As you can see we need to replace the “DynamicMasterPageFile” attribute through “MasterPageFile” and change the “Inherits” attribute by modifying the classes full name. (Below we will change the namespace of the generated class file.)

Now we need to add some “Register” tags to register some SharePoint controls for using them in the site.

<%@ Register TagPrefix="wssuc" TagName="LinksTable" src="/_controltemplates/LinksTable.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="InputFormSection" src="/_controltemplates/InputFormSection.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="InputFormControl" src="/_controltemplates/InputFormControl.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="LinkSection" src="/_controltemplates/LinkSection.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="ButtonSection" src="/_controltemplates/ButtonSection.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="ActionBar" src="/_controltemplates/ActionBar.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="ToolBar" src="/_controltemplates/ToolBar.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="ToolBarButton" src="/_controltemplates/ToolBarButton.ascx" %> 
<%@ Register TagPrefix="wssuc" TagName="Welcome" src="/_controltemplates/Welcome.ascx" %>

You can remove the content placeholder named “PlaceHolderAdditionalPageHead”.

The following ASP.NET code we add to the content placeholder named “PlaceHolderPageTitle”:

    <asp:Literal ID="Literal1" runat="server" Text="Customize Workflow" />

The following ASP.NET code we add to the content placeholder named “PlaceHolderPageTitleInTitleArea”:

    <%
        string strPTS = "Customize " + workflowName;
        SPHttpUtility.HtmlEncode(strPTS, Response.Output);
    %>

The following ASP.NET code we add to the content placeholder named “PlaceHolderPageImage”:

    <img src="/_layouts/images/blank.gif" width="1" height="1" alt="" />
The following ASP.NET code we add to the content placeholder named “PlaceHolderPageDescription”:
    <%
        string strPD = "Use this page to customize this instance of " + workflowName + ".";
        SPHttpUtility.HtmlEncode(strPD, Response.Output);
    %>

Now we add a ASP.NET table control and some more content to the content placeholder named “PlaceHolderMain”. Inside this snipped the TextBoxes are defined that the user will use later while editing the association data.

<asp:Table CellSpacing="0" CellPadding="0" BorderWidth="0" CssClass="ms-propertysheet">
    <wssuc:InputFormSection Title="Workflow Data Values" Description="Specify the workflow data values." runat="server">
        <template_inputformcontrols>
            <wssuc:InputFormControl LabelText="Specify Form Data Values:" runat="server">
                <Template_Control>
                    <table border="0" cellspacing="0" cellpadding="0">
                        <!-- ikarstein: Start your modifications here -->
                        <tr>
                            <td class="ms-authoringcontrols">
                                Data 3: <asp:TextBox id="textboxData3" runat="server"></asp:TextBox>
                            </td>
                        </tr>
                        <tr>
                            <td class="ms-authoringcontrols">
                                Data 4: <asp:TextBox id="textboxData4" runat="server"></asp:TextBox>
                            </td>
                            </tr>
                        <!-- ikarstein: End of modifications -->
                    </table>
                </Template_Control>
            </wssuc:InputFormControl>
        </template_inputformcontrols>
    </wssuc:InputFormSection>
        
    <input type="hidden" name="WorkflowDefinition" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["WorkflowDefinition"]),Response.Output); %>'/>
    <input type="hidden" name="WorkflowName" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["WorkflowName"]),Response.Output); %>'/>
    <input type="hidden" name="AddToStatusMenu" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["AddToStatusMenu"]),Response.Output); %>'/>
    <input type="hidden" name="AllowManual" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["AllowManual"]),Response.Output); %>'/>
    <input type="hidden" name="RoleSelect" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["RoleSelect"]),Response.Output); %>'/>
    <input type="hidden" name="GuidAssoc" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["GuidAssoc"]),Response.Output); %>'/>
    <input type="hidden" name="SetDefault" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["SetDefault"]),Response.Output); %>'/>
    <input type="hidden" name="HistoryList" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["HistoryList"]),Response.Output); %>'/>
    <input type="hidden" name="TaskList" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["TaskList"]),Response.Output); %>'/>
    <input type="hidden" name="UpdateLists" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["UpdateLists"]),Response.Output); %>'/>        
    <input type="hidden" name="AutoStartCreate" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["AutoStartCreate"]),Response.Output); %>'/>
    <input type="hidden" name="AutoStartChange" value='<% SPHttpUtility.NoEncode(SPHttpUtility.HtmlEncode(Request.Form["AutoStartChange"]),Response.Output); %>'/>

    <wssuc:ButtonSection runat="server">
        <template_buttons>
            <asp:PlaceHolder runat="server">
                <asp:Button runat="server" class="ms-ButtonHeightWidth" OnClick="BtnOK_Click" Text="OK" id="btnOK" />
            </asp:PlaceHolder>
        </template_buttons>
    </wssuc:ButtonSection>
</asp:Table>

(I’ve marked the section you should modify for your special needs with HTML comments…)

You see in the ASP.NET code: in this form only “Data3” and “Data4” will be modified. The propertied “Data1” and “Data2” will be modified only in the Association Form. (The “DataX” properties are defined in the “Workflow2Data” class.

15. Now we modify the “code behind” for Workflow2InitiationForm.aspx. – Open “Workflow2InitiationForm.aspx.cs”. Replace the content with this code:

using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.Workflow;
using System.Web;
using Microsoft.SharePoint.Security;
using System.Security.Permissions;

namespace ik.SharePoint2010.Workflow
{
    public partial class Workflow2InitiationForm : Workflow2DataPages
    {
        protected SPListItem listItem;
        protected string listItemName;
        protected string listItemUrl;
        protected SPWorkflowAssociation associationTemplate;
        protected SPWorkflowTemplate baseTemplate;

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            GetListItemInfo();
            GetAssociationInfo();

            if( !IsPostBack )
            {
                PopulatePageFromXml((string)associationTemplate.AssociationData, FormType.Initiation);
            }
        }

        private void GetAssociationInfo()
        {
            Guid associationTemplateId = new Guid(Request.Params["TemplateID"]);

            associationTemplate = list.WorkflowAssociations[associationTemplateId];
            
            if( associationTemplate == null ) 
            {
                SPContentTypeId contentTypeId = (SPContentTypeId)listItem["ContentTypeId"];
                SPContentType contentType = list.ContentTypes[contentTypeId];
                associationTemplate = contentType.WorkflowAssociations[associationTemplateId];
            }

            if( associationTemplate == null )
                throw new SPException("The requested workflow could not be found.");

            baseTemplate = Web.WorkflowTemplates[associationTemplate.BaseId];
            
            workflowName = associationTemplate.Name;
            
            string m_formData = (string)associationTemplate.AssociationData;
        }

        private void GetListItemInfo()
        {
            listItem = list.GetItemById(Convert.ToInt32(Request.Params["ID"]));

            if( listItem.File == null )
                listItemUrl = Web.Url + listItem.ParentList.Forms[PAGETYPE.PAGE_DISPLAYFORM].ServerRelativeUrl + "?ID=" + listItem.ID.ToString();
            else
                listItemUrl = Web.Url + "/" + listItem.File.Url;

            if( list.BaseType == SPBaseType.DocumentLibrary )
            {
                listItemName = (string)listItem["Name"];

                int i = listItemName.LastIndexOf('.');
                
                if( i > 0 )
                    listItemName = listItemName.Substring(0, i);
            }
            else
                listItemName = (string)listItem["Title"];
        }

        public void BtnOK_Click(object sender, EventArgs e)
        {
            string InitData = SerializePageToXml(FormType.Initiation);
            InitiateWorkflow(InitData);
        }

        private void InitiateWorkflow(string InitData)
        {
            try
            {
                Web.Site.WorkflowManager.StartWorkflow(listItem, associationTemplate, InitData);
            }
            catch( Exception ex )
            {
                SPException spEx = ex as SPException;

                string errorString;

                if( spEx != null && spEx.ErrorCode == -2130575205 /* SPErrorCode.TP_E_WORKFLOW_ALREADY_RUNNING */)
                    errorString = SPResource.GetString(Strings.WorkflowFailedAlreadyRunningMessage);
                else if( spEx != null && spEx.ErrorCode == -2130575339 /* SPErrorCode.TP_E_VERSIONCONFLICT */)
                    errorString = SPResource.GetString(Strings.ListVersionMismatch);
                else if( spEx != null && spEx.ErrorCode == -2130575338 /* SPErrorCode.TP_E_LISTITEMDELETED */)
                    errorString = spEx.Message;
                else
                    errorString = SPResource.GetString(Strings.WorkflowFailedStartMessage);

                SPUtility.Redirect("Error.aspx", SPRedirectFlags.RelativeToLayoutsPage, HttpContext.Current,
                    "ErrorText=" + SPHttpUtility.UrlKeyValueEncode(errorString));
            }

            SPUtility.Redirect(list.DefaultViewUrl, SPRedirectFlags.UseSource, HttpContext.Current);
        }

    }
}

In most cases you should not need to modify this code! Because the serialization and deserialization of the data is done in the “Workflow2DataPages” base class.

16. Now modify the “Elements.xml” file of the module “Workflow 2 Forms”.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="Workflow 2 Forms" Url="Workflow2Forms" RootWebOnly="False">
    <File Path="Workflow 2 FormsWorkflow2AssociationForm.aspx" Url="Workflow2AssociationForm.aspx" />
    <File Path="Workflow 2 FormsWorkflow2InitiationForm.aspx" Url="Workflow2InitiationForm.aspx" />
</Module>
</Elements>

You have to add the two “File” tags (including their content) to the “Module” tag.

17. Now we are done with creating the Association and Initiation form! In the next post I will create a sequential workflow, connect them with both forms created in this walkthrough and test the project. Continue reading here: Part 2.

2 thoughts on “Walkthrough: Create SharePoint 2010 Workflow Association Form and Initiation Form in Visual Studio 2010 by using Application Pages (Part 1 of 2)

  1. Hi ikarstein,
    Thanks for the above valuable post. I have tried all the above steps and deployed the solution but I am not getting there. Its showing me errors randomly. If I create Task1Form.aspx inside the module ” Workflow 1 Forms”, I am getting parsor error “The attribute ‘autoeventwireup’ is not allowed in this page” and some times regarding assembly. I guess it was not recognizing that as safe assembly.

    Second time I have tried it by keeping the form as an application page i.e. under “_layouts” folder but now its showing runtime error on page.

    Also in one of my previous tries, I managed to show that Task1Form.aspx but It was just showing labels not text fields.

    Can you please assist me ?

    Thanks.

  2. Hi Ikarstein, great job!

    I added another text field Test4 to Taks1Form in WorkFlow 1 and it works fine.
    Now I try add person field in the same way, but entered value (person) isn’t save to the task.
    I used SharePoint:PeopleEditor Control on task form and Field Type=”User” in schemas.

    Could you give any advice for this?
    Thanks.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.