SP2010 SPF Workflow Part 4: ASPX Task Forms

22 June 2011

So it turns out even after you port the xsn files generated from SharePoint designer across into your Visual Studio solution, if you've renamed the fields in the Task Content Types, there's no real way to modify or update the .xsn files without InfoPath. So instead, I went down the path of creating my own custom task forms (making all the .xsn porting pain I did yesterday irrelevant, but that's how it goes...)

There's two kinds of task forms you can create - full .aspx pages, or .ascx RenderingTemplate. I opted to go with a RenderingTemplate, because I was more familiar with this option, but there is a blog post that shows you how to do a full .aspx here.

Here's what you need for a custom .ascx form:

  • an .ascx file containing your RenderingTemplate(s), being deployed via a Mapped folder to the CONTROLTEMPLATES folder (I normally deploy mine directly to the CONTROLTEMPLATES folder, as it seems like SharePoint only parses the root folder for available RenderingTemplates)
  • some code (I used a feature receiver) to associate the RenderingTemplate with your workflow task content type by setting the Display/New/EditFormTemplateName properties. While the documentation says you can specify these in the content type xml definition in the XmlDocuments section, I haven't seen this ever work fully and would not recommend removing Inherits="true" in order to get this to work

You can base your new RenderingTemplate off the ListForm Rendering Template in the DefaultTemplate.ascx in 14\TEMPLATE\CONTROLTEMPLATES, and add <Sharepoint:FormField> elements as required. Once you've got this all set up, if you deploy you should be able to see your new rendering template come up.

However, there are two gotchas which you'll encounter:

Gotcha #1 - If you save more than once, an exception is thrown with the message "Task is currently locked by a running workflow and cannot be edited"

This is because for the Workflow Task content types, instead of saving the items normally, you need to use the SPWorkflowTask.AlterTask method - I created a custom version of the SaveButton class and overwrote the SaveItem() method to AlterTask instead.

public class UpdateWorkflowTask : SaveButton
{
    protected override bool SaveItem()
    {
        //return base.SaveItem();

        Hashtable taskHash = new Hashtable();

        return SPWorkflowTask.AlterTask(SPContext.Current.ListItem, taskHash, true);
    }
}
Gotcha #2 - If you are using a CollectDataTask, setting the item to completed does not resume the workflow

When calling the AlterTask() method, you can set other properties of the ListItem to update by adding entries to the Hashtable. Microsoft's implementation of the CollectDataTask looks not only for the visible status field in order to continue, but also a hidden field called FormField. If you reflect the code, you'll find this:

// Microsoft.SharePoint.WorkflowActions.WithKey.CollectDataTask
private void AfterOnTaskChanged(object sender, EventArgs e)
{
    if (this._outProperties.ExtendedProperties.Contains(SPBuiltInFieldId.FormData))
    {
        this._taskShouldContinue &= (string.CompareOrdinal((string)this._outProperties.ExtendedProperties[SPBuiltInFieldId.FormData], "Completed") != 0);
    }
    if (this._outProperties.ExtendedProperties.Contains("TaskStatus"))
    {
        this._taskShouldContinue &= (string.CompareOrdinal((string)this._outProperties.ExtendedProperties["TaskStatus"], "Completed") != 0);
    }
    this.OutProperties = this._outProperties;
}

So in order to update the Task item in the proper way to resume the workflow, we can simply update our custom SaveButton to update the hidden FormData field, as follows:

public class UpdateWorkflowTask : SaveButton
    {
        protected override bool SaveItem()
        {
            //return base.SaveItem();

            Hashtable taskHash = new Hashtable();
            taskHash["Completed"] = "TRUE";
            taskHash["Status"] = "Completed";
            taskHash["PercentComplete"] = 1.0f;
            taskHash["WorkflowOutcome"] = "Completed";
            taskHash["FormData"] = "Completed";

            return SPWorkflowTask.AlterTask(SPContext.Current.ListItem, taskHash, true);
        }
    }

That should be all you need to create a task form that works correctly with your workflow! There are lots of nice things that the InfoPath workflow forms do which you'll need to code yourself - such as loading the edit form even when being displayed, having a link back to the related workflow item, etc - but the above should be enough to help you create a basic form to have something functional.

Side Note: if you're going with the ControlTemplates / .ascx path, you may need to perform an IISRESET on your sharepoint box in order for SharePoint to pick up the newly deployed RenderingTemplates; a classic symptom for this issue is a blank form appearing for your new/edit/display forms.

Tags: ascx, SharePoint Designer, task form, Workflow

Add a Comment

No Comments