I’ve been doing a fair bit of work with Declarative (SPD) Workflow just recently and packaging these up into Visual Studio deployment solutions, this post is a collection of things I’ve learnt about the process of authoring SPD workflows, exporting these from SPD and placing them into Visual Studio solutions for deployment.
Reference:
Declarative (SPD) Workflow Components
A declarative (SPD) workflow in it’s simplest form will comprise the following files;
- Workflow.xoml – the workflow logic
- Workflow.xoml.rules – the workflow rule definitions
- Workflow.xoml.wfconfig.xml – the workflow configuration file
If the workflow contains an Association/Initiation form (parameters), the workflow will also have;
- Workflow Association.xsn – the association (InfoPath) form
If the workflow uses a Task process, the workflow will also have a Task InfoPath form for each separate type of task;
- Workflow Task XX.xsn – the association (InfoPath) form
Changing the business logic of the workflow, results in changes only to the .xoml & .xoml.rules files, this is a useful fact to know as it comes into play during the develop/debug/test cycle when you want to update a workflow already placed into your Visual Studio project.
Changing a workflows Association/Initiation parameters, adding Tasks, changing Task fields will cause changes to the set of InfoPath (.xsn) forms associated with your workflow.
- Globally Reusable workflows are published to the _catalogs/wfpub catalog (TemplateType = 122) in the root web of a site collection.
- Reusable workflows are published to a Workflows library (TemplateType = 117) in a site (SPWeb).
Workflow Provisioning Feature (Reusable or Globally Reusable) – Feature Receiver
Your custom SPD workflow (and all of its component files) is placed into a Module element in your Visual Studio project, and this Module is attached to a feature;
- For Globally Reusable workflows the Feature scope should be Site.
- For Reusable workflows the Feature scope should be Web.
The Feature you provision your workflow with must have its feature receiver Class and Assembly attributes set to;
ReceiverClass: Microsoft.SharePoint.Workflow.SPDeclarativeWorkflowProvisioningFeatureReceiver
&
ReceiverAssembly: Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c
Alternatively, the Feature you provision your workflow with can have a custom feature receiver, which should create an instance of Microsoft.SharePoint.Workflow.SPDeclarativeWorkflowProvisioningFeatureReceiver and call its Activate method.
The Microsoft.SharePoint.Workflow.SPDeclarativeWorkflowProvisioningFeatureReceiver feature receiver;
- Ensures that the workflow is placed in a reusable (NoCode) Workflow library (TemplateType = 117) or the global (site collection) Workflow catalog (TemplateType = 122)
- Opens the workflow’s .wfconfig.xml and sets the XomlVersion, RulesVersion & PreviewVersion attribute values of the WorkflowConfig/Template element to match the SPFile.UIVersion value of the relevant files using the URL’s in the XomlHref, RulesHref & PreviewHref attributes of the WorkflowConfig/Template element.
Sample .wfconfig.xml file;
<WorkflowConfig Version="14.0.0.6117"> <Template BaseID="{B2CE33BD-4E6D-41FE-A644-AA0F179C08A3}" DocLibURL="_catalogs/wfpub" XomlHref="_catalogs/wfpub/ProductTracking/ProductTracking.xoml" XomlVersion="V1.0" AssociationURL="_layouts/CstWrkflIP.aspx" TaskContentType="0x01080100C9C9515DE4E24001905074F980F93160" StartManually="true" Visibility="RootPublic" Category="List;#ContentType" RulesHref="_catalogs/wfpub/ProductTracking/ProductTracking.xoml.rules" RulesVersion="V1.0" Description="Routes a document for tracking, update and approval." PreviewVersion="V1.0" AllowStartOnMajorCheckin="true" Name="ProductTracking" /> <WorkflowConfig>
Workflow Provisioning Modules
For Reusable workflows, your provisioning Module should include a ListInstance element to create the Workflows library.
If you are going to create workflow associations programmatically, you should include ListInstance elements to create the Tasks (if it won’t be on the site in question) and Workflow History libraries also, because when you create workflow associations by code, or even by CAML, the workflow Tasks list and Workflow History lists must already exist.
<ListInstance FeatureId="{00bfea71-f600-43f6-a895-40c0de7b0117}" TemplateType="117" Title="Workflows" Description="" Url="Workflows" RootWebOnly="FALSE" /> <ListInstance FeatureId="{00bfea71-4ea5-48d4-a4ad-305cf7030140}" TemplateType="140" Title="Workflow History" Description="History list for workflow." Url="Lists/Workflow History" RootWebOnly="FALSE" />
A typical Workflow provisioning module you include in a Visual Studio project will look something like this (with or without .xsn forms);
<Module Name="ISGSApprovalWF" Url="Workflows/ISGS Request Approval" Path="ISGSApprovalWF\Files" RootWebOnly="FALSE"> <File Url="ISGS Request Approval Task.xsn" Type="GhostableInLibrary" Path="ISGS Request Approval Task.xsn"> <Property Name="ContentTypeId" Value="0x01010700BBB4D90213014B4E95C7ACC14FB603C6" /> <Property Name="ContentType" Value="User Workflow Document" /> </File> <File Url="ISGS Request Approval.xoml.rules" Type="GhostableInLibrary" Path="ISGS Request Approval.xoml.rules"> <Property Name="ContentTypeId" Value="0x01010700BBB4D90213014B4E95C7ACC14FB603C6" /> <Property Name="ContentType" Value="User Workflow Document" /> </File> <File Url="ISGS Request Approval.xoml.wfconfig.xml" DoGUIDFixUp="true" Type="GhostableInLibrary" Path="ISGS Request Approval.xoml.wfconfig.xml"> <Property Name="ContentTypeId" Value="0x01010700BBB4D90213014B4E95C7ACC14FB603C6" /> <Property Name="ContentType" Value="User Workflow Document" /> </File> <File Url="ISGS Request Approval.xoml" DoGUIDFixUp="true" Type="GhostableInLibrary" Path="ISGS Request Approval.xoml"> <Property Name="ContentTypeId" Value="0x01010700BBB4D90213014B4E95C7ACC14FB603C6" /> <Property Name="ContentType" Value="User Workflow Document" /> </File> <File Url="ISGS Request Approval.xsn" Type="GhostableInLibrary" Path="ISGS Request Approval.xsn"> <Property Name="ContentTypeId" Value="0x01010700BBB4D90213014B4E95C7ACC14FB603C6" /> <Property Name="ContentType" Value="User Workflow Document" /> </File> </Module>
The content type is User Workflow Document ~ 0x010107
<ContentType ID="0x010107" Name="$Resources:DocumentWorkflowItem" Group="_Hidden" Description="$Resources:DocumentWICTDesc"> <FieldRefs> <FieldRef ID="{e9359d15-261b-48f6-a302-01419a68d4de}" Name="BaseAssociationGuid" /> <FieldRef ID="{566da236-762b-4a76-ad1f-b08b3c703fce}" Name="XomlUrl" /> <FieldRef ID="{ad97fbac-70af-4860-a078-5ee704946f93}" Name="RulesUrl" /> <FieldRef ID="{a05a8639-088a-4aea-b8a9-afc888971c81}" Name="Visibility" /> <FieldRef ID="{b75067a2-e23b-499f-aa07-4ceb6c79e0b3}" Name="AssociatedListId" /> <FieldRef ID="{8b02a33c-accd-4b73-bcae-6932c7aab812}" Name="RestrictContentTypeId" /> <FieldRef ID="{5263cd09-a770-4549-b012-d9f3df3d8df6}" Name="WorkflowDisplayName" /> </FieldRefs> </ContentType>
Of note here are columns Visibility and RestrictContentTypeId for the .xoml.wfconfig.xml file;
- Reusable workflows should set the Visibility column to Public
- Globally Reusable workflows should set the Visibility column to RootPublic
- If your workflow is restricted by a specific Content Type, the ID of that Content Type should be populated into the RestrictContentTypeId column ~ the reference article at the top of this post discusses workflows restricted by Content Type in more detail.
Set the Workflow Xoml and Rules Versions in your Provisioning Solution
In your workflow .xoml.wfconfig.xml file in your provisioning solution set the XomlVersion and RulesVersion attributes of the WorkflowConfig/Template element to “V1.0” – this sets the base-line version for the newly provisioned files and workflow configuration.
Workflows using the Task Process and Variants
Workflows which use the Task Process or its variants; Approval, Collect Feedback etc, include a custom Task content type and Task Form (.xsn).
This custom Task content type must be (CAML) provisioned just like a regular custom content type to the site collection content type gallery before the Workflow is provisioned, either by the Workflow provisioning feature or some other feature;
Task (process) Content Type(s) are also contained in the .wfconfig.xml at element WorkflowConfig/ContentTypes, these are not replicas of the CAML Content Type definitions, but share significant common attribute values (Name, ContentTypeID etc);
If the workflow is a Globally Reusable Workflow, the WorkflowForm attribute of the WorkflowConfig/ContentTypes/ContentType element must begin with a leading /, e.g. /_catalogs/wfpub/MyWorkflow/MyWorkflowTask.xsn
If the workflow is a Reusable Workflow, the WorkflowForm attribute of the WorkflowConfig/ContentTypes/ContentType element must begin with Workflows, e.g. Workflows/MyWorkflow/MyWorkflowTask.xsn
When using the Task Process activity, or using a copied 2010 Approval workflow, the Task form (.xsn), and also the Task content type, will usually end up with unusual names (Approval, Collect Feedback, Approval Workflow Task _x0028_en-US_x0029_ Copy 5.xsn etc).
Because we’re Dev’s and anal about these things, no doubt we’d like to change these names and you can do this when you place the workflow files into your Visual Studio solution Module. I’d keep the Content Type ID’s the same but the Content Type names can be fixed up as long as you remember to change both the .wfconfig.xml file and the elements file where the (CAML) Content Type definition is placed.
- The Association/Initiation form (.xsn) name can be changed by changing the name of the .xsn file in your Visual Studio solution filesystem and updating the Instantiation_FormURI/Association_FormURN elements of the WorkflowConfig/MetaData element in the .wfconfig.xml file.
- The Task Content Type name can be changed by updating the WorkflowConfig/ContentTypes/ContentType element in the .wfconfig.xml file ~ Don’t forget to also change the (CAML) ContentType name in the elements file.
- The Task form (.xsn) name can be changed by changing the name of the .xsn file in your Visual Studio solution filesystem and updating the WorkflowForm attribute of the WorkflowConfig/ContentTypes/ContentType element in the wfconfig.xml file ~ You also need to make a change to one of the (CAML) Content Types XmlDocument’s (see below).
The Task Content Type includes a set of XmlDocuments in its CAML definition, an important one of note has the NamespaceURI http://schemas.microsoft.com/sharepoint/v4/workflow/forms.
This contains a WorkflowForm element which ties the Content Type to the Task form (.xsn), and if you change the Task form (.xsn) name you must follow that change through here too;
<WorkflowForm xmlns="http://schemas.microsoft.com/sharepoint/v4/workflow/forms">~site/Workflows/MyWorkflow/MyWorkflowTask.xsn</WorkflowForm> <WorkflowForm xmlns="http://schemas.microsoft.com/sharepoint/v4/workflow/forms">~sitecollection/_catalogs/wfpub/MyWorkflow/MyWorkflowTask.xsn</WorkflowForm>
- If the workflow is a Reusable workflow use the ~site token at the beginning of the URL.
- If the workflow is Globally Reusable use the ~sitecollection token at the beginning of the URL.
Workflows which contain List References
Workflows which contain List references will only work where the referenced list resides in the same site the workflow instance is running in.
List references are typically included by the FindValueActivity, LookupActivity and CopyItemActivity, the problem with Workflow list references is that SPD uses the List ID (Guid) to reference the list rather than the list name;
The workflow actions in Microsoft.SharePoint.WorkflowActions, use a call through to Microsoft.SharePoint.WorkflowActions.Helper.GetListGuid(…) to resolve a list ID or Name, meaning that these activities can use the List ID or the List Name;
Depending on your version of SharePoint (SP1, CU etc) and Office SPD, when the workflow is saved as a template, the resultant .xoml file will contain activities using list references as list ID’s (Guid’s) or the list name, you’ll have to check your .xoml file and convert list references using the list ID to list Name references ~ a list Name reference is the URL of the list’s root folder (see below).
To convert a list reference using a list ID (Guid) to a list references using a list Name, update them as shown below;
FindValueActivity using a List ID Reference (Before)
<ns1:FindValueActivity x:Name="ID394" ReturnValue="{x:Null}" ExternalFieldName="isgsApproverKey" __Context="{ActivityBind ROOT,Path=__context}" ValueType="System.String" ExternalListId="{}{c154a97e-c80f-4f45-a01f-12b0d5476748}">
FindValueActivity using a List Name Reference (After)
<ns1:FindValueActivity x:Name="ID394" ReturnValue="{x:Null}" ExternalFieldName="isgsApproverKey" __Context="{ActivityBind ROOT,Path=__context}" ValueType="System.String" ExternalListId="{}{$ListId:Lists/Approvers;}">
A FindValueActivity, for instance, is usually accompanied by a LookupActivity which also contains a list reference;
Before
<ns1:LookupActivity ListId="{}{c154a97e-c80f-4f45-a01f-12b0d5476748}" x:Name="ID393" FieldName="isgsApprover" LookupFunction="LookupUser" __Context="{ActivityBind ROOT,Path=__context}" ListItem="{ActivityBind ID394,Path=ReturnValue}" />
After
<ns1:LookupActivity ListId="{}{$ListId:Lists/Approvers;}" x:Name="ID393" FieldName="isgsApprover" LookupFunction="LookupUser" __Context="{ActivityBind ROOT,Path=__context}" ListItem="{ActivityBind ID394,Path=ReturnValue}" />
When a workflow with list references is provisioned to a site or web, on feature activation, the lists referenced by the workflow must already exist in the web the workflow is being provisioned to (note I said Provisioned To) – for Globally Reusable workflows this means the root web.
Workflow ALM and the Developer Story
So following a typical developer ALM story, we may follow a mix of the following steps;
- Initial (SPD) authoring
- Local Test/Debug/Develop cycle
- Export workflow for placement into Visual Studio provisioning solution
- Integration Test/Debug/Develop cycle
- Iterate steps 4 & 3, perhaps including regression cycles in steps 2/3
Whatever your ALM process, you’ll almost certainly be engaged in ongoing development of your workflow, and since we’re dealing with reusable workflows authored using SPD, its preferable for ongoing development of the workflow to remain in SPD.
After initial authoring of a workflow with SPD, you have the option of importing that workflow into a Visual Studio solution using the “SharePoint 2010 – Import Reusable Workflow” project wizard – using this project template will convert your declarative workflow into a coded workflow and any ongoing development will have to be done using Visual Studio.
For one thing it allows for a much more compressed develop/test cycle. Another reason is that, for non trivial workflows which tap into an items surrounding IA/Taxonomy we need to be able to continue development of the workflow even as its surrounding IA changes.
What this means is that the developer faces the seemingly endless task of saving the workflow as a template, and (re)placing it into the Visual Studio solution, and while this is not difficult, its certainly an onerous and grungy task where mistakes are easily made.
The good news is that this task can be made easier given certain conditions, so for example, presuming you have provisioned your workflow to a site using a feature, you need to make further changes;
If you make no changes to the workflows set of Association/Initiation or Task parameters (and therefore the association/initiation/task forms), after saving the workflow as a template in SPD, the only files you need to drop (overwrite) back into your Visual Studio solution are the .xoml and .xoml.rules files, after resolving any List reference issues (see above).
So your process might go something like;
- Edit workflow in SPD
- Test/Debug
- Iterate steps 1 & 2
- Save workflow as Template
- Extract the .xoml and .xoml.rules files from the exported workflow .cab
- Fixup any list references in the .xoml file (if necessary)
- Drop the 2 files into the Module folder for the workflow in your Visual Studio solution
Workflows Provisioned by a Feature and Saving the Workflow as a Template
So, remember that files (Module elements) provisioned by a Feature onto a site, are considered to be Ghosted/Un-customized, when you edit those workflows with SPD, 1 or more files in the set of workflow files will become Unghosted/Customized.
When you now save this workflow as a template, the resultant .CAB file will contain element manifests which reference the Customized/Unghosted copies of the workflow files which changed from the location on the site/web where the workflow resides; either Workflows or _catalogs/wfpub. The element manifest for other files of the workflow which didn’t change (maybe your .xsn files) will be provisioned from the original feature folder.
Published by