Tag Archives: workflow

SP2010 SPF Workflow Part 5: List ID Lookups The Proper MS Way

Finally after a couple of days of pain, I’ve got the end to end workflow which can be modified in SPD, and can be deployed to any site regardless of List/ListID…and here’s the two biggest gotchas I encountered.

  1. Make sure you are developing your workflows on the same version of SP as the target environment – SPD is smart/crazy enough to generate .xsn task forms if you are connected to a SP2010 box, but use regular .aspx forms if you are connected to a SPF box.
  2. If you have any lookups to lists, you will have to open the generated workflow .xoml files and remove the hardcoded references to listids.

If you’ll recall from previous posts, for point #2, I had created a crazy feature receiver that dynamically strips out the list ids and replaces them with the correct list ids. But it turns out that there’s actually a much easier way, hidden in the SharePoint dlls. Taking a quick look in reflector….

[sourcecode language="c-sharp"]

// Microsoft.SharePoint.WorkflowActions.FindValueActivity
protected override ActivityExecutionStatus Execute(ActivityExecutionContext context)
{
int returnValue = Helper.FindValue(this.__Context, this.ExternalListId, this.ExternalFieldName, this.FindValue); //Note the external list id property…
this.ReturnValue = returnValue;
return ActivityExecutionStatus.Closed;
}

// Microsoft.SharePoint.WorkflowActions.Helper
public static int FindValue(WorkflowContext context, string externalListId, string fieldNameInExternal, object value)
{
return Helper.FindValueWithKey(context, Helper.GetListGuid(context, externalListId), fieldNameInExternal, value).Id; //goes into this Helper method…
}

// Microsoft.SharePoint.WorkflowActions.Helper
public static Guid GetListGuid(WorkflowContext context, string listNameOrId)
{
Guid result;
try
{
if (null == context)
{
result = new Guid(listNameOrId);
}
else
{
SPWeb web = context.Web;
if (null == web)
{
result = new Guid(listNameOrId);
}
else
{
SPList sPList = null;
try
{
Guid uniqueID = new Guid(listNameOrId);
sPList = web.Lists[uniqueID];
}
catch (FormatException)
{
sPList = web.Lists[listNameOrId]; //which supports setting listid by name!!
}
result = sPList.ID;
}
}
}
catch (Exception)
{
result = default(Guid);
}
return result;
}

[/sourcecode]

If that didn’t make any sense to you, it just means that you can use List Names in place of the ListId and ExternalListId properties within your .xoml files.

For example:

[sourcecode language="xml"]
<ns1:FindValueActivity x:Name="ID55" __Context="{ActivityBind ROOT,Path=__context}" ExternalFieldName="User_x0020_Account" ReturnValue="{x:Null}" ExternalListId="<strong>My List Name</strong>" ValueType="System.String">
[/sourcecode]

This isn’t very well documented (on msdn, for example, it just says Identifier which kind of implies GUIDS only).

Now, we just need SharePoint designer to at least have the option to generate .xoml files in a site-collection agnostic way, and then none of this would be necessary!

SP2010 SPF Workflow Part 4: ASPX Task Forms

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 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.

[sourcecode language="c-sharp"]
public class UpdateWorkflowTask : SaveButton
{
protected override bool SaveItem()
{
//return base.SaveItem();

Hashtable taskHash = new Hashtable();

return SPWorkflowTask.AlterTask(SPContext.Current.ListItem, taskHash, true);
}
}
[/sourcecode]

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:

[sourcecode language="c-sharp"]
// 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;
}
[/sourcecode]

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:

[sourcecode language="c-sharp"]
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);
}
}
[/sourcecode]

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.

SPD2010 Workflow Packaging Part 3: Custom Task Forms

Following on from the previous two posts, here’s another thing that you might have to do in order to get your workflow working correctly, if you’ve used any custom task forms or “Collect Data from User” actions. Many thanks to Benjamin Hutting and his blog post that pointed me in the right direction!

What you’ll find is that you will have deployed your workflow and everything is fine, until you need to use one of the .xsn files, whether it be the initiation form, or a task form – more than likely, the error you will get will be something around “the form has not been browser enabled”.

To fix this, you need to add a couple of properties to your xsn forms:

[sourcecode language="xml"]
<Property Name="ContentTypeId" Value="0x010107004E610F5A29049D419EC0DF99AC577F7C" Type="string" />
<Property Name="vti_contenttag" Value="{FFBDB99D-81D4-44E0-80ED-F5FD8C7B14D3},9,8" Type="string" />
<Property Name="ipfs_streamhash" Value="wVkgvw//oxebgHKAKQzsBNDC+YwEiBPCl5897nHvfJo=" Type="string" />
<Property Name="vti_privatelistexempt" Value="true" Type="string" />
[/sourcecode]

You can grab these properties either from looking at the original .xsn in SharePoint Manager, or by opening up the generated SPD Template .wsp.

Another issue you may run into, which I did, is that although the administrator can open the .xsn files, normal users can’t access the .xsn files / browse to them, even though the object model will tell you they have full permissions. Adding the Manage Lists permissions allows them to view these files, but a better option is probably setting the AllowEveryoneViewItems property for the Workflow list to true.

While I feel like the vti_privatelistexempt property is meant to circumvent the need for this, it doesn’t seem to work for me – keen to hear if anyone has used this properly before!

EDIT: This…didn’t end up working for me, since I had no way to update the .xsns nicely without having InfoPath Forms Services / Infopath – so take the above advice with a grain of salt if you’re attempting to move the autogenerated .xsn created by SPD2010…i even tried unpacking the .xsn / cab, renaming the in the .xsf, but no luck…

SPD 2010 Reusable Workflows And The Problem With List Lookups

UPDATE: Please ignore the contents of this post and read this post instead to learn how to modify your workflows to handle deployment between different site collections/lists/list ids.

You’d think that from being a constant problem ever since 2007, there would be a little more of an effort to fix this from Microsoft; but there doesn’t seem to be much movement on the “List ID” issue that plagues Lookup field and Workflow development…

If you read the previous post, you’ll see that I was packaging a reusable workflow from SPD to VS2010, which worked fantastic…except that when you eventually opened up the workflow within SharePoint designer and try to edit any actions / lookups to lists, they are all broken.

Thanks to this post, I managed to piece together a workaround for this, allowing me to deploy a reusable workflow and connect the lookups to list by name instead of ID.

The basic idea is to look at the .xoml file for your workflow, find any hardcoded list ID references, and replace them with some sort of string token which we can then replace programmatically with the correct List ID

So for the first part, you want to alter your .xoml – you’re on the look out for:

[sourcecode language="xml"]<ns1:LookupActivity x:Name="ID337" __Context="{ActivityBind ROOT,Path=__context}" FieldName="WorkflowItemId" ListItem="{ActivityBind ID338,Path=ReturnValue}" LookupFunction="LookupInt" ListId="{}{7418F891-7B8E-46B8-893F-8C76D06463E2}" />[/sourcecode]

In my case, I replaced them with “$@$[List Name]“, but anything should be fine as long as it’s unique.

[sourcecode language="xml"] <ns1:LookupActivity x:Name="ID337" __Context="{ActivityBind ROOT,Path=__context}" FieldName="WorkflowItemId" ListItem="{ActivityBind ID338,Path=ReturnValue}" LookupFunction="LookupInt" ListId="{}{@$@Assessment Tasks}" /> [/sourcecode]

For the second part, adding to the previous SharePoint solution with the .xoml and associated files, feature receiver and Workflow list instance, I added another feature and feature receiver to run some string replacements on the .xoml. (I did try to bind an event receiver to the Workflow list but didn’t have much luck, and I got about 90% through in my attempt to extend the default Microsoft.SharePoint.Workflow.SPDeclarativeWorkflowProvisioningFeatureReceiver class; if you manage, you could just write all this into one event receiver on one feature instead of having another feature.)

And in the new feature receiver, open up the workflow list, find the .xoml file, and do some replacements on the tokens you inserted. The code I used looked a little like this (you may need to tweak it for your own use):

[sourcecode language="c-sharp"]

using System;
using System.Linq;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using System.Xml.Linq;
using System.IO;
using System.Collections.Generic;

namespace MyProject
{
/// <summary>
/// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
/// </summary>
/// <remarks>
/// The GUID attached to this class may be used during packaging and should not be modified.
/// </remarks>

[Guid("193f813a-1a59-4ffe-97a2-4c8b931c57be")]
public class MyEventReceiver: SPFeatureReceiver
{
// Uncomment the method below to handle the event raised after a feature has been activated.

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
//find the hidden workflows list
SPWeb currentWeb = properties.Feature.Parent as SPWeb;
SPList workflowList = currentWeb.Lists["Workflows"];
SPFolder wfFolder = workflowList.RootFolder.SubFolders["MyWorkflow"];
SPFile wfXoml = wfFolder.Files["MyWorkflow.xoml"];

XDocument xmlDoc;
byte[] buffer = wfXoml.OpenBinary();
using (MemoryStream memoryStream = new MemoryStream(buffer))
{
using (TextReader textReader = new StreamReader(memoryStream))
{
xmlDoc = XDocument.Load(textReader);
}
}

XNamespace ns1 = "clr-namespace:Microsoft.SharePoint.WorkflowActions.WithKey;Assembly=Microsoft.SharePoint.WorkflowActions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=null";
List<XElement> findValueActivites = new List<XElement>(from el
in xmlDoc.Root.Descendants(ns1 + "FindValueActivity")
where el.Attribute("ExternalListId").Value.Contains("@$@")
select el);

List<XElement> lookupActivities = new List<XElement>(from el
in xmlDoc.Root.Descendants(ns1 + "LookupActivity")
where el.Attribute("ListId").Value.Contains("@$@")
select el);

ParseListReferences(findValueActivites, "ExternalListId", currentWeb);
ParseListReferences(lookupActivities, "ListId", currentWeb);

//write updates
using (MemoryStream memoryStream2 = new MemoryStream())
{
using (TextWriter textWriter = new StreamWriter(memoryStream2))
{
xmlDoc.Save(textWriter);
wfXoml.SaveBinary(memoryStream2);
}
}
}

private void ParseListReferences(List<XElement> findValueActivites, string sAttributeName, SPWeb web)
{
foreach (XElement el in findValueActivites)
{
//get the value of the listid
string sListName = el.Attribute(sAttributeName).Value.Trim(new char[] { ‘{‘, ‘}’, ‘ ‘, ‘@’, ‘$’ });
el.Attribute(sAttributeName).Value = String.Format("{{}}{{{0}}}", web.Lists[sListName].ID.ToString().ToUpperInvariant());
}
}

}
}

[/sourcecode]

(Note the use of the XNamespace to select the correct elements to update, in the code above.)

With those two parts, you’ll have a reusable workflow which you can deploy to any SharePoint site which is still open for end users to customize with SharePoint Designer. Hard work! *phew*

Manually Export SPD 2010 Reusable Workflow To Visual Studio 2010

This post is part one of five around SharePoint Designer workflow deployments.
Part Two (no longer applicable)
Part Three (custom task forms: infopath)
Part Four (custom task forms: aspx/ascx)
Part Five (listids)

SharePoint Designer 2010 comes with a lot of improvements around Workflow creation and management, and one of the better new features are Reusable Workflows, allowing Power Users to create and edit workflows that can be used in any site by saving them as templates – which automatically generates a .wsp in order to redeploy these workflows.

However, if you want more control over the generated solution, there aren’t many options, and importing a reusable workflow template into Visual Studio 2010 removes the ability for users to edit those workflows in SharePoint Designer. (I didn’t look too much into this, but I believe it stops being treated as a no-code workflow).

So, after reading this post I found online around packaging the workflows manually, it turns out it’s not too hard to create these wsps yourself.

The main components of the .wsp are:

  • A list instance for the Workflow
  • A module to deploy the .xoml and associated workflow definition files
  • A feature with the same receiver assembly and class used by the SPD generated .wsp

The first step is downloading the workflow files for the workflow you want to package, so you can include it in your solution. The easiest way seems to be to use SharePoint Designer to browse to the correct location and open/save the files, as shown below.

Keeping those files handy, create a new SharePoint project and a new List Instance item. (While you could probably throw this workflow into an existing SharePoint project, it ties your other content and workflow into the same assembly which means when you update the other stuff, it might “upgrade” your workflow and affect existing instances needlessly…)

For your new List Instance, name it Workflows and set the Url to Workflows. Change the Template Type to 117 (the No-Code workflow list), and the feature id to 00bfea71-f600-43f6-a895-40c0de7b0117. The xml should look something like below:

[sourcecode language="xml"]
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<ListInstance Title="Workflows"
OnQuickLaunch="FALSE"
TemplateType="117"
FeatureId="00bfea71-f600-43f6-a895-40c0de7b0117"
Url="Workflows"
Description="">
<Data>
<Rows>
<Row>
<Field Name="ContentTypeId">0x012000B262179482B514438E7D6F41D4AA1345</Field>
<Field Name="FileLeafRef">Assessment Task Notification</Field>
</Row>
</Rows>
</Data>
</ListInstance>
</Elements>
[/sourcecode]

(I’m not sure whether the Data Row is required as shown above, but I decided to include it for safety’s sake…)

Once that’s done, create a module to deploy the files you grabbed from SharePoint designer into that list. Your module should end up looking something like below:

[sourcecode language="xml"]
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Module Name="Assessment Task Notification" Url="Workflows/Assessment Task Notification" Path="Assessment Task Notification">
<File Path="Assessment Task Notification.xoml" Url="Assessment Task Notification.xoml" Type="GhostableInLibrary" />
<File Path="Assessment Task Notification.xoml.rules" Url="Assessment Task Notification.xoml.rules" Type="GhostableInLibrary" />
<File Path="Assessment Task Notification.xoml.wfconfig.xml" Url="Assessment Task Notification.xoml.wfconfig.xml" Type="GhostableInLibrary" />
<File Path="Assessment Task Notification.xsn" Url="Assessment Task Notification.xsn" Type="GhostableInLibrary" />
</Module>
</Elements>
[/sourcecode]

Note that you want the Type to be GhostableInLibrary, and you may have to set the build action for the *.xoml file to None – if you open this up in the Workflow Designer in Visual Studio 2010, you’ll see a whole lot of errors around __Context and other things; you can safely ignore these as they get taken care of by the Feature Receiver.

Speaking of feature receiver, the last thing we need to do is to add it. Open up your feature, and set the following properties for your feature:

Receiver Assembly = “Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c”
Receiver Class = “Microsoft.SharePoint.Workflow.SPDeclarativeWorkflowProvisioningFeatureReceiver”

And that’s it! If you deploy your solution from Visual Studio, you should see the workflow come up as being available in SharePoint designer, and be able to edit and publish as per normal. Have fun!

Nintex Workflow Pro Tip: Hiding The Workflow Diagram on Initiation Forms

Whenever you run a workflow, Nintex displays a diagram of the workflow on the right side as part of the initiation form – in most cases, this is great as it serves as a quick visual reminder of what workflow you’re about to kick off. However, if you really want to hide everything on here, the only way to do this is to customize the InitiationForm.

If you take a look at the url of the browser, you’ll notice that the Nintex Workflow loads at http://[my site]/_layouts/NINTEXWORKFLOW/StartWorkflow.aspx – so all we need to do is make a copy of this form that doesn’t include the diagram, tell the workflow to use our custom version, and we’re done!

I would recommend copying the form and including it in a mapped Sharepoint Folder in a VS2010 project as the easiest way to provision the form to a Sharepoint installation – below I’ve made a copy and renamed it StartWorkflowNoDiagram.aspx

After renaming all the content from StartWorkflow to StartWorkflowNoDiagram, all we need to do is hide the WorkflowVisualizer control.

I’m not sure if there’s a cleaner way to hide it than putting it in a hidden div, but stripping the control out altogether causes null reference exceptions, so I figured I’d rather have the form working than not!

The last thing to do is to point the workflow to use a custom form – this setting can be found in Workflow Settings, right at the very bottom:

In my case, because my version of the StartWorkflowNoDiagram page was in a mapped folder, I put the following url in the field:

/_layouts/NINTEXWORKFLOW/StartWorkflowNoDiagram.aspx

Happy coding!

Nintex Workflow Pro-Tip: Updating Publishing HTML Fields

Super, super busy here in Manila, but this was a bit of a gotcha so I thought I’d share:

If you’re looking to update a list item with a publishing html field, don’t use the text box in Update Item action directly, as it will html encode all the angle brackets in your html and generally not work as you’d probably want it to. Instead, you need use the build string action to build your html and store it into a workflow variable, and refer to it within your Update Item action – that way, your html won’t be escaped and you can do beautiful, beautiful dynamic HTML tables.

P.s. Nintex is AWESOME.