Step by Step Tutorial. Creating Workflows for Windows Sharepoint Services and MOSS2007 (part 9/20)

Step 9/20. Using ASXP forms in workflows

Download the code 

[24/11/2008] Update : I cover aspx association forms in step 14 with much more details.

Scenario

 

Since WSS doesn’t allow the use of Infopath Forms , we will write our initiation form in “pure” aspx.

[Note: we will improve and extend this sample in the future]

 

Hands-on

 

We’ll start from step 8 solution.

In the U2UExpenseReport project, add a text file with an aspx extension : InitManager.aspx.

In source mode, copy and paste the following code fragment in the aspx page:

 
<head id="Head1" runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        Temporary Manager
        <asp:TextBox ID="TextBoxManager" runat="server"></asp:TextBox>
        <asp:Button ID="ButtonSubmit" runat="server" Text="Submit" />
       
        </div>
    </form>
</body>
</html>

In design mode, the page will look like this: 

 

 

 Let’s add a new class, InitManager in a file InitManager.aspx.cs (make sure the class is public !).

 

 

Derive  InitManager from the Page class :

 

public   class InitManager : System.Web.UI.Page
{

 

Add the page directive at the top of InitManager.aspx:

 

<%@ Page Language="C#" AutoEventWireup="true" ValidateRequest="False" Inherits="U2U.ExpenseReport.InitManager" %>

 

We need to provide more details about the assembly of the InitManager class by adding an Assembly directive to the page; this directive has to know the assembly strong name, so recompile the project and retrieve U2U.ExpenseReport strong name (with Reflector for instance).

Add the Assembly directive before the page directive (don’t forget that my PublicKeyToken is different than your own):

 
<%@ Assembly Name="U2U.ExpenseReport, Version=1.0.0.0, Culture=neutral, PublicKeyToken=338fc881ff6f0902"%>
<%@ Page Language="C#" AutoEventWireup="true" ValidateRequest="False" Inherits="U2U.ExpenseReport.InitManager" %>

 

Implement the Page_Load event handler:

 

protected void Page_Load(object sender, EventArgs e)
{
}

 

 

Add a new protected member that will allow us to retrieve the textbox value of the .aspx page:

 

 
public   class InitManager : System.Web.UI.Page
{
        protected TextBox TextBoxManager;

 

 

Add the corresponding using directive :

 

using System.Web.UI.WebControls;

 

Modify the Page_Load event handler to display the manager:

 
 
protected void Page_Load(object sender, EventArgs e)
   {
       if (Page.IsPostBack)
       {
                Response.Write("hello, the submitted manager is " +
                  TextBoxManager.Text);
       }
   }

 

 

Specify that the aspx page is the Initialization Form:

in the workflow.xml file, remove the <Instantiation_FormURN> in  <MetaData>.

Modify the InstantiationUrl:

InstantiationUrl="_layouts/InitManager.aspx"

Modify the install.bat file to copy the aspx form(s) to the Layouts directory:

xcopy /s /Y *.aspx "%programfiles%\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS\"

Build the solution, call install.bat and test the workflow; the instanciation form should show up and when we click on the submit button whe should have a feeback.

 

Linking the form to the workflow

 

We want the workflow to be started when we click on the submit button.

We won’t interact directly with the workflow Runtime in Sharepoint but we interact with the site collection workflow manager (SPWorkflowManager class).

Here is the pseudo-code:

Web.Site.WorkflowManager.StartWorkflow(<listItem>, <associationTemplate>, <InitializationData>);

When Sharepoint will call our Initilialization form, it will provide some useful parameters :

·         Request.QueryString["List"] which contains the List Guid.

·         Request.Params["TemplateID"] which contain the association Guid.

·         Request.Params["ID"] which contains the ListItem Guid.

 

Define the following members in the InitManager class:

 
private SPWeb _Web;
private SPList _List;
private SPListItem _listItem;
private SPWorkflowAssociation _assocTemplate;

 

And the corresponding using directives:

 
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.WebControls;

 

Upgrade the Page_Load code:

 
protected void Page_Load(object sender, EventArgs e)
{
   if (Page.IsPostBack)
    {
               
       // Get the web site
      _Web = SPControl.GetContextWeb(this.Context);
       // Get the List
      string strListID = Request.QueryString["List"];
      if (strListID != null)
           _List = _Web.Lists[new Guid(strListID)];
      // Get the WorkflowAssociation
      Guid assocTemplateId = new Guid(Request.Params["TemplateID"]);
      _assocTemplate = _List.WorkflowAssociations[assocTemplateId];
      // Get the ListItem
      _listItem = _List.GetItemById(Convert.ToInt32(Request.Params["ID"]));
      _Web.AllowUnsafeUpdates = true;
      Response.Write("hello, the submitted manager is " +
TextBoxManager.Text);
      Response.Write("<BR> the list title is " + _List.Title);
     }
 }

Rebuild, call install.bat and test the workflow.

 

Serializating the Initialization form data

 

Usually the iitialization form has several controls;  the form data must be serialized in Xml and provided to the workflow.

Create a new class to encapsulate the Initialization form data:

 
namespace U2U.ExpenseReport
{
    [Serializable()]
      public class InitFormData
      {
        private string _manager;
 
        public string Manager
        {
            get { return _manager; }
            set { _manager = value; }
        }
     
      }
}

 

Add a new helper class, AspxHelpers, with the following static methods:

 

      class AspxHelpers
      {
            public static string SerializeFormToString(Type aType, object
initData)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                XmlSerializer serializer = new XmlSerializer(aType);
                serializer.Serialize(stream, initData);
                stream.Position = 0;
                byte[] bytes = new byte[stream.Length];
                stream.Read(bytes, 0, bytes.Length);
                return Encoding.UTF8.GetString(bytes);
            }
        }
public static void InitiateWorkflow
(    
string InitData,
SPWeb Web,
SPList List,
SPListItem listItem,
SPWorkflowAssociation assocTemplate
)
        {
            try
            {
                Web.Site.WorkflowManager.StartWorkflow(listItem,
assocTemplate, 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 InitManager,  add a new data member:

 

    public class InitManager : System.Web.UI.Page

      {

       

        private InitFormData _initData = new InitFormData();

 

Upgrade the Page_Load event handler to serialize the form data and to start the workflow:

 
        protected void Page_Load(object sender, EventArgs e)
        {
           
            _initData.Manager = TextBoxManager.Text;
            string initData =
AspxHelpers.SerializeFormToString(typeof(InitFormData),_initData;
            AspxHelpers.InitiateWorkflow(initData, _Web, _List, _listItem,
                                                            _assocTemplate);
        }
 

 

We need to upgrade the section of the workflow code which deserializes the data coming from the Initialization form.

Select the workflow in the Workflow Designer; double click on the first activity (onWorkflowActivated1) and replace the existing deserialization code with the following one:

XmlSerializer serializer = new XmlSerializer(typeof(InitFormData));
XmlTextReader reader = new XmlTextReader(new System.IO.StringReader(WorkflowProperties.InitiationData));
InitFormData tempManager = (InitFormData)serializer.Deserialize(reader);
this.Manager = tempManager.Manager;

Rebuild the solution, call install.bat and test the workflow.

[24/11/2008]Update : I cover aspx association forms in step 14 with much more details.

 


This hands-on training is the property of Redwood S.L sprl and may not be organized in class or in group without the prior written permission of Serge Luca. Should you wish to organize this hands-on training in your company or institution, please contact Serge Luca  first to enter into a licence agreement. Each trainer or teacher using this hands-on training should have a licence agreement. Please ask your trainer or Serge Luca whether he or she has entered into a licence agreement with Redwood S.L sprl.

The hyperlink to this hands-on training may be placed on your website for free, on the condition that the name Serge Luca is clearly mentioned in the reference. Please send us a mail containing the link to the web page our reference is used on.


7 responses to “Step by Step Tutorial. Creating Workflows for Windows Sharepoint Services and MOSS2007 (part 9/20)

  1. Hello Serge
     
    I had to mark the assembly as SafeControl in the <SaveControls> section of the web.config. Only after that i was able to see the initiation form.
     
    Before that i had a "File not found"-Exception.
     
    Regards
    Philipp

  2. Thank your for your feeback; have you tried my solution ? In my case, I\’m just registering the dll in the Gac 
    Serge

  3. I tried different project combinations (combined aspx/wf project, separate projects,…) as well as the one you\’ve got in the article but i couldn\’t do it without marking the assembly as SaveControl. The marking as SaveControl was only neccessary for the aspx pages, the workflow itself worked successfully without marking.
     
    By the way, i like it more with a separate aspx application project for the forms, so there are no problems to work with the vs designer. Which can be very helpful if you are creating more complex aspx pages. But there is still the question with the master page. Do you have a demo masterfile in the project just for a more or less correct vs-designer visualisation or just nothing?
     
    Regards
    Philipp

  4. Hi Serge,
     
    I\’m loving the tutorial, keep up the good work!  However, the source code does not appear to be available and there was something I wanted to check.
     
    Cheers,
    JC 

  5. Could you please explain why WSS 3.0 doesn\’t allow InfoPath forms or provide a reference that explains this? I have a state machine workflow built with Wss 3.0 that uses InfoPath forms hosted in an XmlFormView control on an aspx page (a page that ships with WSS called "FormServer.aspx"). However, the form seems to not be posting data entered by users, and I\’m trying to find out why. Thanks

  6. Thanks for the great info. I am curious as to why InfoPath forms can\’t be used with WSS workflows because I\’ve managed to get them to show up in browser windows. However they are not saving data at this time. Could you please explain this or provide a link to some more info on this?Thanks

  7. Hi , Iam facing a problem whenever i try to add any type of workflow even the built in ones like approval , the site gives me unknown error , and the site become unaccessible , when I close the browser and open another session the site is opening like usual ? what do u think the problem?

Leave a comment