This document was uploaded by user and they confirmed that they have the permission to share
it. If you are author or own the copyright of this book, please report to us by using this DMCA
report form. Report DMCA
Overview
Download & View Step By Step Tutorial - Part 2 as PDF for free.
Step 9: 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: Untitled Page 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:
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:
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 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>, , ); 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;
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(" 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 =
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.
Step 10: Reuse, Modify and Debug a Sharepoint Designer workflow in Visual Studio 1.Scenario Microsoft Sharepoint Designer 2007 is a very interesting tool that allows developers and users to create workflows running in Sharepoint 2007 without writing code. The workflows generated by Sharepoint Designer cannot be debugged and reused in another site unless we import it in Visual Studio.Net.This tutorial will show you how to achieve this. I assume that your have the correct setup for creating workflows for Sharepoint . 2.Hands-on • Create a team site. By default a task list ("tasks") is generated. • Create another task list and call it “Issues”. • Now start Sharepoint Designer and open your web site (Menu “File-Open Site”). • Let’s create a very simple workflow that will copy a task list item from the "Task" list to the "Issues" list if the Title column of the record contains the word “problem”. • In Sharepoint Designer, start the Menu “File”-“New Workflow”; keep the default workflow name and select the list you want the workflow to be associated with: Task; unselect the option “allow this workflow to be manually started from an item” and select the two others.
Click on the Next button of the form and the Workflow Designer will show up :
Click on the Conditions button and select the option Compare Tasks field.
In the condition, click on the “field” hyperlink and select Title in the combobox. In the condition, click on the “value” hyperlink and type “problem” (without the quotes). In the condition, click on the “equals” hyperlink and select “contains”.
In the Action, select “Copy List Item”.
Click on the Finish button of the form; Sharepoint Designer will generate the xoml code (which is not xaml activated code !!!) , it will compile the code ( xaml activated code must not be compiled) and it will associate the workflow to the corresponding list. 3.Testing the workflow Add a new item in the task list: set the Title to “problem in Belgium”: since the Task's Title column contains the word “problem”, it will be copied to the Issues list by the workflow . 4.Importing the workflow in Visual Studio In Sharepoint Designer, go to the Workflows folder, select Workflow1 and right click on “Publish Selected Files” :
Select the option “File System” (specify a file location) :
Click on OK. The following files will be generated at the selected location:
Rename the file Workflow1.xoml.rules to Workflow1.rules. In our specific case, the .rules file contains the test that will check if the title column contains the word “problem”; this test is expressed in the CodeDom language. Create a new Sharepoint Workflow project that will host the generated files ; name it MySPWorkflow :
Add the files Workflow1.rules and Workflow1.xoml into the new project and delete Workflow1.cs :
Click on Workflow1.xoml and the workflow will show up in the Workflow Designer :
•
The first activity (ID1) is a Microsoft.Sharepoint.WorkflowActions.OnWorkflowActivated activity and makes the link between the workflow and the Sharepoint context (like the list and the list item among many other things).
•
The activity ID6 is a Microsoft.Sharepoint.WorkflowActions.CopyItemActivity . The workflow itself is derived from Microsoft.SharePoint.WorkflowActions.RootWorkflowActivityWithData which is derived from SequentialWorkflowActivity.
•
The following code shows the real content code of Workflow1.xoml:
5.Xoml code 1.We can notice that this not what Workflow Foundation afficionados called “xaml activated” code which is xaml code that can run on the fly without having to be compiled. This code must be compiled: indeed the x:class attribute provides the compiler with a class name for the workflow :
<ns0:RootWorkflowActivityWithData x:Class="Microsoft.SharePoint.Workflow.ROOT" x:Name="ROOT" The workflow itself is an activity named Root and derived from the class RootWorkflowActivityWithData.
2. According to the Sharepoint sdk, the RootWorkflowActivityWithData class has a WorkflowFields property which is a collection of key-object pairs where the key is a string (describing the property name) and the object a type (the type of the property); this is an handy way to dynamically add properties of any type to a class.
The following xoml fragment shows how Sharepoint Designer feeds this WorkflowFields property : <ns0:RootWorkflowActivityWithData> … <ns0:RootWorkflowActivityWithData.WorkflowFields> <ns0:WorkflowDataField Name="__list" Type="System.String" /> <ns0:WorkflowDataField Name="__item" Type="System.Int32" /> <ns0:WorkflowDataField Name="__context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext" /> <ns0:WorkflowDataField Name="__initParams" Type="Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties" /> <ns0:WorkflowDataField Name="__workflowId" Type="System.Guid" />
•
__list: the list Guid.
•
__item: the index of the ListItem.
•
__context: a Microsoft.SharePoint.WorkflowActions.WorkflowContext which is a class that will encapsulate some elements of the well known SPWorkflowActivationProperties (stored in the __initParams field). In our example, the instance of this class will be used internally by the CopyItem activity to provide some necessary information like the SPWeb for instance.
•
__initParams: a Microsoft.SharePoint.Workflow.SPWorkflowActivationProperties ; this class provides many information like the SPWeb, the SPList, the SPListItem etc…
The reason for using a WorkflowContext instead of directly a SPWorkflowActivationProperties object is not clear here. Another way to visualize these properties is to select the workflow object in the workflow designer and to display the associated property page :
Here is what we get if we click on the collection:
This will initialize the informations the CopyItemActity needs (see later).
Now, let’s take a look at the CopyItemActivity properties :
In order to copy one ListItem to an other list, the CopyItemActivity needs some informations:
1. 2. 3. 4.
The original list Guid: ListId property . The destination list Guid: ToListid property. The list item index: ListItem property ; databinding from the __item property of the workflow. The SPWeb object : __Context property; databinding from the __context member of the workflow.
6.Deployment and test of the workflow
We will deploy the workflow as a feature, therefore we need to modify the install.bat file : 1. 2. 3. 4. 5. 6.
Sign the project Retrieve the strong name In install.bat , replace http://localhost with your site collection url in install.bat, replace myFeature with the assembly name run install.bat test the workfow
7.Error This workflow generates an error :
8.Fault Handler To get more detailed information about this error, it is necessary to add a Fault Activity (equivalent of a catch block) and to log it. Select the workflow in the (Visual Studio) Workflow Designer, click on the lower left side of the Designer and click on the Fault Handler:
Another view of the Workflow Designer will show up:
Drag and drop a FaultHandler activity from the Workflow Foundation toolbox into fautlHandlersActivity1 :
In the property page of this last activity select the FaultType property by browsing the mscrolib assembly:
Select System.Exception:
Now we will log the StackTrace into the workflow history log: drag and drop a LogHistoryList activity into the handler (if you don’t find this activity drag and drop the microsoft.Sharepoint.WorkflowAction.dll assembly to a new Visual Studio toolbox tab) :
Select the logToHistoryListActivity1 and set its HistoryOutcome property to the stack trace value of faultHandlerActivity1:
Rebuild the solution , redeploy (install.bat) and test the workflow. The trace will show up in the workflow status :
Remove the activity ID2 : this activity (class ApplyActivation) is the source of the error. Let’s Click on the ID1 activity to generate the code behind and paste the following code in the generated handler: private void ID1_Invoked(object sender, ExternalDataEventArgs e) { __context = new WorkflowContext(); __context.Initialize(__initParams); __list = __initParams.List.ToString(); __item = __initParams.ItemId; __workflowId = __initParams.WorkflowId; }
Recompile the workflow, and test it. 9.Debugging our workflow Reset the web server, refresh the web site, open the project in Visual Studio, set a breakpoint in ID1_Invoked and attach the browser to the w3wp.exe host:
Invoke the workflow by changing a value in the title property, you should hit your breakpoint. Reusing the workflow in another site The good news is that the CopyItemActivity can use both list Guid or list name. Select its property page and set the ListId and ToListId properties respectively to “Tasks” and “Issues”:
Recompile the workflow, deploy and test it to make sure it works again ;-) The same workflow can be reused in another site if the appropriate list (“Tasks” and “Issues”) are present.
10.Modify the .rules files The condition that evaluates if the list item contains the word "problem" is stored in a rule file which is compiled with the application (in another post I will show you how to store and retrieve it dynamically from a database, and how a user can create this rules). The rules generated by Sharepoint Designer are rather cryptic. Select the activity ID3 in the Workflow designer and click on the Condition property in the property page:
Select the Expression property, you’ll see something like this:
Replace the whole content of this windows with the following code :
That’s it ! Recompile, deploy and test the application .
Step 11: Modification Forms Introduction Modification forms allow us to reassign a workflow task to another user. 3 parts in this article: 1. What are Modification Forms 2. Introduction to the EventHandlingScope activity 3. Adding a Modification form to an existing workflow Part 1. What are Modification Forms To illustrate this concept, I will run a workflow provided with the downloadable version of the Sharepoint Sdk (1.3 in my case). By default, the samples are installed in the following location :
•
Try to open the ModificationSample project;if you try to compile it, you will get a compilation exception since this project references another assembly provided with the sdk as well: ECMActivities; recompile the ECMActivities project and resolve the reference in ModificationSample.
•
If you doubleclick on the Workflow1 class you'll get the following model(I will describe the different elements of this workflow in the second part of this post, don't worry).
•
If we compare this model to the workflows we did in the previous tutorials, we'll notice 2 new activities: EnableWorkflowModification (a Sharepoint activity) and EventHandlingScopeActivity (which is part of the base activity library of Workflow Foundation).
•
Let's click on the manifest file (workflow.xml), you will notice a couple of new tags :
Several Infopath forms are also provided with this example and one of them which is referenced in the Modification element of the manifest is the Modification form:
• •
In the Install.bat file, replace http://localhost with our site collection url and run it. Associate the workflow with a Sharepoint list and start the workflow. You'll notice that the Reviewer is U2UCOURSE\serge :
The workflow is in progress; if you click on its status:
You'll be redirected to this form where you have the option to reassign the task:
A task has been created and assigned to serge:
•
Let's reassign the task to john by clicking on the "Update task owner" hyperlink described above:
•
If you get back to the task list, you will notice that the task owner has been changed appropriately:
•
Now, if John click on its task, the task form will show up; if he clicks in the "completes" field, the workflow will complete; there is a small bug in the application, however : john's task doesn't complete, it is still necessary to add a Sharepoint "CompleteTask" activity :
..and to link it to the task Id
...and the correlation token to "taskToken1" :
Recompile, and (re)install the workflow.
Part 2. Understanding the EventHandlingScopeActivity The EventHandlingScopeActivity is an activity that allows a workflow (or part of a workflow) to run while listening to events; it is a kind of (but just a kind of) ListenActivity or ParrallelActivity. Ok, I'll give you a quick tutorial on it:
•
Create a Workflow Console application (Sequential workflow) and drag and drop a EventHandlingScopeActivity from the toolbox into the workflow surface:
•
Drag and drop a Sequence activity into the EventHandlingScope activity
•
Drag and drop a Delay activity followed by a code activity:
•
Set the TimeoutDuration of the delayActivity to 20 seconds and in the codeActivity1 associated code, display "Main Application Completed". These activities can be considered as part of the main flow of the workflow sequence. In the background however, the workflow can listen to events while the main flow is not completed: in the case of our Sharepoint workflow the potential event that could come up is the OnWorkflowModified event which is triggered when a task is reassigned to another owner for instance.
•
To keep it simple, I will use another delay activity (it must be any activity implementing the IEventActivity interface and that's the case of the delay activity but also the case of all "green" Sharepoint activities : these green activites are derived from HandleExternalEvent activity, which implement IEventActivity). The activity must be encapsulated in an EventDriven activity (the first activity of an EventDriven activity must implement IEventActivity); it is like a catch for an event (instead of a catch for an exception). Lets go for it.
•
To show the event side of the EventHandlingScope activity, right click on it and select "view Event Handlers" as following:
You'll see this :
•
Drag and drop an EventDriven activity (or several if you need to listen to several events).
• •
•
Lets drag and drop a Delay activity (and set its timeout property to 10 seconds, to make sure the event will fire before the main flow completes). Just after the Delay activity, add a code activity and display the message "delay event fired in the background" in the associated code:
Run the application and you will get something like this
Part 3. Using a Modification form in an existing workflow
•
•
I will assume that you know how to create sharepoint workflows and how to link Infopath Forms to the workflow association, Initiation and task changing state; if it is not the case, follow the previous versions of my sharepoint workflow tutorials. Add an existing Sharepoint workflow project to the current solution
Step 1.Add to your solution the HelloWorldSequential workflow provided with the sdk ; in its install.bat file, replace http://localhost with your site collection url, deploy it and play with it. Step 2.Add the ModSimpleModificationForm.xsn Infopath Form of ModificationSample (sdk) to the location of the HelloWorldSequential project. Step 3.Register the Modification forms in workflow.xml:
-generate a Guid with Guidgen (Visual Studio-Tools-Create GUId menu) -after the elements, add 2 elements: <Modification_xxx_FormURN> and <Modification_xxx_Name> where xxx is the generated Guid like this: <Modification_bf35e820-5070-4e33-bc91-de30388e0b7e_FormURN> urn:schemas-microsoft-com:office:infopath:ModSampleModificationForm:-myXSD05-17T01-22-06 <Modification_bf35e820-5070-4e33-bc91-de30388e0b7e_Name> Update task owner
2006-
Step 4.Run Install.bat, associate the workflow with a list and run it on a list item. If you click on the workflow status (when the workflow is "in progress", you will notice that the "update task owner" message is not visible; we need to add another functionality (next step). Step 5.Add the EnableWorkflowModification activity just before the CreateTask activity :
• • • •
set its correlation token to the current token, "modifToken" and the OwnerActivityName to HelloWorldSequential. set its ModificationId property to the Guid defined before: bf35e820-5070-4e33-bc91-de30388e0b7e compile, install and test the workflow : the workflow will crash and you will get an "an error has occured" message. try to visualize the error message by setting up a FaultHandler activity in the Fault Handler view of the workflow and log the error message in the workflow history by using a LogToHistoryList activity :
•
Set the FaultType property of the FaultHandler activity to System.Exception and the HistoryOutCome property of the LogToHistoryList activity to the exception message property as following:
You will get a rather unclear exception (if you click on the workflow completed status hyperlink):
Step 6. Add a sequence activity in yhe EventhandlingScope activity and move the EnableWorkflowModification activity and the other activities that follow (disable the SendMail, LogToHistoryList and CodeActivity) into an EventHandlingScope activity (group them all in a sequence activity):
• •
•
in the CorrelationToken property of the grouped activities, set the owner activity property to the EventHandlingScope activity name Build and test, you will get the following (same) error:
In the EventHandlingScope activity Event Handler, add an EventDriven activity and a OnWorkflowActivated activity like this:
• • • •
•
Set the OnWorkflowModified activity CorrelationToken property to "modifToken". Set the ModificationId to the value defined above. Build, run and test the workflow, and this time the workflow status will be "in progress" which is a good sign ! If you click on the workflow status, you will see the "Update task owner" hyperlink:
However, if you click on the "Update Task owner" hyperlink, you will get an Infopath Form exception; indeed the contextData, which is supposed to contain the data to be transferred to/from the modification form is not yet ready. We will manage this in the next step. Step 7. Sending data to the modification form As we did it in one of the the tutorial, let's use xsd.exe to generate a class that will be mapped to the modification form.
• • • •
Open the modification form with Infopath and use the "save as source" menu; this will generate (among others), an .xsd file. generate the mapping .net class : xsd.exe myschema.xsd /c rename the generated class file as "modificationForm.cs" add the file to the workflow project
•
create an helper function to serialize an Infopath form schema: private string SerializeModificationData(ModificationForm form) { using (MemoryStream stream = new MemoryStream()) { XmlSerializer serializer = new XmlSerializer(typeof (ModificationForm)); serializer.Serialize(stream, form); return Encoding.UTF8.GetString(stream.GetBuffer()); } }
•
select the EnableWorkflowModifcation actvity and bind its ContextDate property to a new workflow field that we will call _ContextData. at the end of the OnWorkflowActivated event handler, add the following lines : ModificationForm modifForm = new ModificationForm(); modifForm.taskOwner = assignee; this._ContextData = this.SerializeModificationData(modifForm);
•
•
Build, install and test the workflow : click on the "Update Task owner" link and the modification form will show up:
We still need to be able to take the modification data into account. Step 8. Sending data from the modification form • • •
Everytime the modification form data will be submitted, the OnWorkflowModified activity will be triggered. To get the new task owner, we need to deserialize to data from the Modification form. To manage this, create a function to deserialize Infopath form data: private ModificationForm DeserializeFormData(string xmlString) { using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes (xmlString))) { XmlSerializer serializer = new XmlSerializer(typeof (ModificationForm));
ModificationForm data = (ModificationForm)serializer.Deserialize (stream); return data; } } •
Define a newAssignee data member in the workflow class : private string newAssignee = default(string);
•
Switch to the EventHandler of the EventHandlingScope activity Add an eventHandler to the Invoked event of the OnWorkflowModified activity Deserialize the Infopath form as following: private void onWorkflowModified1_Invoked(object sender, ExternalDataEventArgs e) { ModificationForm modForm = this.DeserializeFormData(this._ContextData); this.newAssignee = modForm.taskOwner; }
• •
•
We still need to update the existing task(s) by using an UpdateTask activity: drag & drop an Update Task activity just after the onWorkflowModified1 activity:
•
Link the UpdateTask activity to the created task activity by "databinding" its TaskId property to the workflow taskId property and by "databinding" its TaskProperties to the workflow member afterProps (which is databound to the afterProperties property of the onTaskChanged1 activity). Set also its correlation token to modifToken. Set an event handler to the updateTask1 activity with the following code that will modify the Assigned to user : private void updateTask1_MethodInvoking(object sender, EventArgs e) {
Build deploy and test the workflow; it is very important to make sure the task data are updated even when you redirect the form to another user:
Get to the Tasks list
Click on the title hyperlink and modify the instructions in the form as following (here I've added "modified by admin") :
Click on the Submit button. Get back to the original list, click on the workflow status ("in Progress)
Update the task owner ("here to Serge)
Go to the Tasks list, make sure the task owner has been modified
Click on the Title hyperlink, should get the modified data in the form :
Congratulations !!!
Step 12: Managing several approvers/reviewers Introduction The Out-of the box approval workflow provided with MOSS allows to specify several approvers. In this post, I will illustrate how to do this. In a first time (part 1) , I will show you how to use the Replicator activity that you will need, and in a second time (part 2), you will apply this knowledge to our Sharepoint problem. If you know how the Replicator activity works you can directly go to part 2.
Part 1. Using the Replicator activity The Replicator activity is part of the Workflow Foundation Base Activity Library, is somewhat similar to the While activity. • •
Create a Sequential Workflow console application project. Add a Replicator activity into the workflow surface
•
Add a custom Activity to the project; call it "ManageTask":
•
Define a public string member called Approver in the custom activity :
•
Drag and drop a Code activity in the custom activity
•
Display the Approver member in its Event handler:
Recompile the project and drag & drop the custom activity into the Replicator:
•
In the workflow code add an ArrayList data member and name it Approvers :
•
In the Workflow constructor, add 2 users to the ArrayList
•
In the Replicator activity, databind the InitialChildData property to the ArrayList:
In the Replicator activity, double click on the ChildInitialized event to generate an event handler :
•
Each item in the collection will be associated with a child activity (the child activity here is ManageTask): each time a child activity will be created, the ChildInitialized event will be triggered, which give us the possibility to pass the associated data :
Run the application:
You can download the code here. Part 2. Using the Replicator activity in the Approval workflow
•
as I did in the previous post, I will start by using the HelloWorldSequential sample provided with the MOSS sdk. Don't forget to replace the string http://locahost in the install.bat file with you own web site/site collection url. (See step 11 of this tutorial for more infos about this sample).
•
The strategy is to replicate the following selected activities in a Replicator activity :
We will group these activities in a custom sequential activity. •
Add a new project (Sharepoint-Sequential Workflow Library ) to the current solution
•
Remove the following files from the project :
•
In the same project, add a new custom activity, name it ManageApproval:
•
You can either drag & drop the activities from Workflow1 to the new custom activity or adding new activities in the ManageApproval activity (my option) :
•
Link each of these activity to the same correlation token : TaskToken and ManagerApproval for the OwnerActivityName
•
Select the CreateTask activity and databind its TaskId and TaskProperties property to new fields : TaskId and TaskProperties
•
Databind the TaskId prperty of the OnTaskChanged and CompleteTask activities to the newly created taskId member. On the OnTaskChanged activity, databind the AfterProperties and BeforeProperties to new fields: AfterProperties and BeforeProperties
•
•
Define the following members (make sure Assignee is public since it will get the task owner from the Replicator activity) :
Double click on the CreateTask activity to generate a handler and add the following code: (most of the code can be copied from Workflow1.cs)
•
Set the Condition property of the WhileActivity to Code Condition and name the function TaskNotFinished :
•
Define a bool data member, isFinished , and check its status in the TaskNotFinished function:
•
Double click on the TaskChanged activity to generate a handler and add the following code:
•
Sign you assembly Get back to the workflow project and double click on the workflow code to display it in the designer; clean it up by deleting the activities you don't need anymore; you must have something like this:
•
• •
Recompile the solution to make sure your custom activity will show up in the toolbox. Add a Replicator activity just after the OnWorkflowActivated activity and drag & drop you custom activity in it.
•
In the workflow code, define a new member to host the assignees:
•
Store the assignees into this ArrayList: click on the OnWorkflowActivated activity and add the following code in the handler:
•
Bind the Replicator activity to the Assignee member. Add a handler to the ChildInitialized event of the Replicator and insert the following code:
•
•
•
Modify the install.bat file in order to register the custom activity in the Gac : "%programfiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" -uf MyApprovalActivities "%programfiles%\Microsoft Visual Studio 8\SDK\v2.0\Bin\gacutil.exe" -if bin\Debug\MyApprovalActivities.dll
Test the workflow: rebuild, install the workflow, add the association and start a workflow ; assign to user separated with a ";" , for instance U2UCOURSE\Administrator;U2UCOURSE\serge, like this :
A first task will be created for the user "Administrator":
•
If the Administrator clicks on its task :
•
Then, if the user "Administrator" select the "I am finished reviewing" and click the Submit button, a new task will be created for the user "serge":
•
The reason Serge's tasks is created after the Administrator's task completes is that the ExecutionType property of the Replicator activity was set to Sequence. Let's change it to parallel, rebuild and test the workflow .
Congratulations !!!!
Step 13: Fixing and running the ASPXCollectFeedback sample (sdk) Introduction I’ve received tons of emails related to workflows & aspx forms. Indeed, I’ve faced many situations where Infopath forms in Sharepoint workflows are not flexible enough or not stable enough; therefore we need the full power and stability of the Asp.net framework (and moreover, some applications need to be based on the free WSS without Infopath Form server).
For instance, many people want to see the history list in the task edit form, they want to use Silverlight in the workflow forms,… ; I’ve recently worked on a project where the association form must automatically associate several informations to each CreateTask activity, the form uses reflection to retrieve each CreateTask activity… I’ll show you some other examples in the next posts. The first natural step for a Sharepoint developer is to check the WSS/MOSS sdk to find a good sample. There is a pretty good one , the ASPXCollectFeedback, which unfortunately doesn’t work as expected unless we fix it; that’s what I’m going to do in this post : fixing this sample. I’m very surprised that no one in the Google/LiveSearch universe has complained about this sample (this sample was provided since 2006…). Let’s fix this for the Sharepoint community.
Open the solution Install the downloadable WSS sdk 1.4, backup the projects located in c:\Program Files\2007 Office System developer Resources\ASPXCollectFeedback\ and open the CollectfeedbackWorkflow.sln solution.(fig 1).
[Fig 1.ASPXCollectFeedback Project location in the WSS 1.4 sdk ]
The problems and the solutions Problem 1. One project (TaskWorkflowContentType) is not compiled by default. Solution: very easy, right click on the solution property page, select Configuration PropertiesConfiguration and check the Build option of the TaskWorkflowContentType project (fig 2).
[Fig2.Build options ] Rebuild the solution. Problem 2. Gacutil in install.bat
•
The sdk sample is based on VS2005 where gacutil was in a different location. • Edit the InstallAll.bat file to take the gacutil.exe location into account ("%programfiles%\Microsoft SDKs\Windows\v6.0A\Bin\gacutil.exe"). • Update installAll.bat with your site collection url to the stsadm instructions. • Now, run the InstallAll.bat. Normally the workflow and its associated features haven been installed and activated. • You can associate the workflow with a list and start the workflow on a list item. Problem 3.The custom task form doesn’t show up in display mode
•
Indeed when you click on the workflow task, only the standard task list shows up instead TaskWCT.aspx (the custom form) (fig 3).
[Fig 3.The standard task form shows up instead of the custom form]
• •
The reason for this is the Content type manifest file provides only a custom edit behavior , but no custom display behavior.The standard one is used instead. Indeed, if you click on the Edit Item menu, the custom aspx form will show up (fig 4):
[Fig 4.Custom task form]
•
To fix this, edit the manifest file TaskWorkflowContentType.xml in the TaskWorkflowContentType project and add a display option (fig 5) : _layouts/TaskWFCT.aspx
[Fig 5.Adding a custom Display behavior]
•
Make sure your new content type definition will replace the previous one by deactivating/uninstalling the existing content type and verify that the old content type is removed it by looking at the content type site gallery. Problem 4. “Require Manage Lists permission to start the worklow” option is not used : The association page doesn’t take the “Require manage Lists permissions to start the workflow” option into account (Fig 6).
[Fig 6. This Permission is not used correctly in the sample] To fix this, you need to add an additional hidden field in the WFAssoc.aspx page (Fig 7);
(More details about this in my next tutorial)
[Fig 7. Missing hidden field]
•
Run InstallAll.bat and re-associate and test your workflow. • In the next post I will describe the project. Problem 5. The List Content Type case is not handled Indeed there are 3 kinds of workflow associations: 1. Workflow association to a list 2. Workflow association to a content type 3. Workflow association to a content type in a specific list (this one is missing in the sample) In the SPWorkfowAssociation class provides the following methods: public static SPWorkflowAssociation CreateListAssociation ( SPWorkflowTemplate baseTemplate, string name, SPList taskList, SPList historyList ) public static SPWorkflowAssociation CreateSiteContentTypeAssociation ( SPWorkflowTemplate baseTemplate, string name, string strTaskList, string strHistoryList ) public static SPWorkflowAssociation CreateListContentTypeAssociation ( SPWorkflowTemplate baseTemplate, string name,
SPList taskList, SPList historyList Anyway, I will fix this problem in a next post.
Step 14 : Easy ASPX Association Forms-(my small generic framework)
Download the startup code Download the final code Download the framework code Introduction There are many reasons to associate workflows with aspx forms in Sharepoint instead of Infopath forms (IP): •
aspx forms are more robust • more flexible than IP forms • can be debugged • support very well javascript/Ajax/Silverlight • work with Windows Sharepoint Service which is free. The black sides of aspx forms in Sharepoint workflows are : •
they are not integrated in Office 2007 rich client • they are more complex to develop than IP forms • the main sample in the Sharepoint sdk doesn’t work (see my previous post where I show how to fix it). The first kind of form you need to create is the association form; I’ve encapsulated and abstracted the complexity of association forms in a small framework; this framework will be upgraded in the future to handle other kind of forms and will be available in CodePlex. There will be 3 parts in this post :
•
In parts 1 and 2, I will show you how to quickly create an association form by using my framework. • In part 3, I will detail how association forms work under the cover. Part 1. Creating a basic association form Creating the workflow
•
Let’s create a very basic Sharepoint workflow by using the Sharepoint sequential workflow template and name the project DemoAssocFormWorkflow (Fig 1).
[Fig 1. Basic workflow project] •
Untick the “Automatically Associate…” option in the third form form” (Fig 2).
[Fig 2. Don’t automatically associate the workflow] •
Add a reference to the SergeLuca.WorkflowTools.WorkflowAssociation.dll (Fig 3) (this dll can be grabbed from the startup files).
[Fig 3.Add a reference to my small framework] • •
Set the Copy Local property of this assembly to true. Create the classic TEMPLATE-LAYOUTS folder with you custom sub folder: DemoAssocFormWorkflow
[Fig 4.Create the “12 like” folders for the Layout page (association form)] Add a FEATURES folder with a DemoAssocFormWorkflow custom folder under the TEMPLATE folder and move the feature.xml and workflow.xml over there :
[Fig 5.Add the Features folder and the features files] •
•
Add the file WFAssoc.aspx into this folder; you can get it from my startup code; this page is very primitive WorkflowAssociation page; I’ve kept it simple on purpose. This page just displays an Ok button. Add the WFAssoc.cs page(from the startup code) to your project root; this file is the association page code behind (fig 6).
[ Fig 6.Generic Page class] •
GenericWFAssocPage is an abstract class; implements its methods :
[Fig 7.Implementing the Abstract class] •
Remove the generated throw new NotImplementedException() statements and return null in the FromAssocDataToPage member :
[Fig 8.Basic generic page implementation]
[Update 11/24 : in the next release the Function FromAssocDataToPage will only have an AssocFormData parameter]
•
Compile the project and use Reflector to retrieve the assembly strong name.Modify the assembly directive of WFAssoc.aspx (Fig 9).
Fig 9.Modify the @Assembly directive to point to your code behind assembly
•
You still need to specify somewhere that you want WFAssoc.aspx to become your association form: do it in workflow.xml :
[Fig 10. Defining the association forms in the feature manifest] That’s it! Now it’s just a matter of installing the workflow, the page layout and the Framework assembly. Installing the workflow Add the install.bat file into the project root; double check the file to make sure everything is ok (in the Install.bat file we assume the application pool name is SharepointPool), change the url (which here is http://blog.redwood.com) ; add a Post-Build event to the project :
[Fig 10.Install.bat file in the Post-build event] •
Rebuild the project and check the output windows for any error (if you don’t find this window, you can display it from the View menu). The final code can be found here.
Association forms testing
Normally the install.bat file activates the feature for the whole site collection. We are going to test the 3 kinds of workflow associations Association type 1 : workflow associated with a list
•
Go to a list or document library and add the association. Since an association form has been provided, the next button will show up :
[Fig 11. Standard workflow association form] •
Click on the Next button and our form will show up :
[Fig 12. Custom workflow association form]
• •
Click ok and the the association will be created. You can modify the association by asking to select new Task and History list (Fig 13):
[Fig 13.Modification of a workflow association] •
Modify the association and View the site content : you will see your new Task and History lists (Fig 13):
[Fig 14.New Task and History lists ] Association type 2 : workflow associated with a content type • • •
Create 2 item content types : Vehicule and Car which inherits from Vehicule Associate these content types to a generic list named Parking. Associate the DemoAssocFormWorkflow with the Vehicule content type (in the Site Content Type gallery)
[Fig 15.Associate a workflow with a content type] • •
Add a new Vehicule in the parking list Start the workflow in the list item ECB (menu linked to the list item).
•
If you add a new Car (not just a Vehicule), the workflow association will aslo show up. Association type 3 : Workflow associated with a content type in a specific list
•
In the parking list, create a new association with the Vehicule content type (Fig 16).
[Fig 16.Workflow associated with a content type in a specific list] This association will be available on every Vehicule in this list, but not on the cars ! Part 2. Extending the association form We want to be able to create the following association form (Fig 17) :
[Fig 17.The final association form]
•
Add the following control declaration in WFAssoc.aspx (get the code from snippet1.txt in the startup files). <%@ Register TagPrefix="wssuc" TagName="InputFormSection" src="/_controltemplates/InputFormSection.ascx" %> <%@ Register TagPrefix="wssuc" TagName="InputFormControl" src="/_controltemplates/InputFormControl.ascx" %> <%@ Register Tagprefix="wssawc" Namespace="Microsoft.SharePoint.WebControls" Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
•
In WFAssoc.aspx, replace the existing code in “Placeholdermain” just above the hidden fields with code provided in snippet2.txt in the starter files (keep the hidden fields, remove everything else). • Rebuild you workflow and test the association form to make sure it can show up. • In WFAssoc.cs define the form fields as protected members (snippet3.txt). • Define the fields namespace (Resolve) : using System.Web.UI.WebControls; using Microsoft.SharePoint.WebControls; • •
We need to add a class that will allow data transfer between the association form and the workflow; Add the file AssocFormData to your project from the starter files, and examine the code. since AssocData is the data transfer class, replace the