In the world of SharePoint Workflows, there are really only two options for creating custom workflows (excluding 3rd parties), SharePoint Designer and Visual Studio (Office Visio is just a toy at this point). Figure 1 below shows the trade offs, where with SharePoint Designer you get more maintainability because you don't need low level, highly technical developers implementing your business processes. With SharePoint Designer, even your grandma can do it!
Figure 1, SharePoint Designer offers more maintainability, but Visual Studio offers more flexibility.
The trouble with SharePoint Designer is flexibility to meet complex business requirements, and this is where Visual Studio workflows typically come into view. I always like to point out three main "platform busters" with SharePoint Designer workflows that necessitate Visual Studio workflows, and they are:
Buster #1) Sequential Workflows vs State Machine Workflows: SharePoint Designer only supports Sequential workflows, in that the workflow executes in a specific sequence (eg, A -> B -> C -> D). Whereas Visual Studio workflows can execute in any sequence, eg A -> C -> B -> A -> D. The image below shows the two different types and how they would look on a diagram:
Figure 2, SharePoint Designer workflows can only do sequential workflows. You need Visual Studio to do state machines.
Buster #2) External Data and/or Integrate with Line of Business Applications: SharePoint Designer workflows can't really integrate well (out-of-the-box) with other systems external to SharePoint. Now, you can use BCS with Workflows but really all you can do is CRUD (create, review, update, and delete) data, YOU CAN'T RESPOND TO EXTERNAL EVENTS! This is a significant limitation to SharePoint Designer workflows. For instance, look at Figure 3 below. This figure shows several workflow instances in SharePoint that interact with some Line of Business application. They call into the system, and then sit and wait for a response from that system. The red arrows reflect the response, where the line of business app wants to notify the waiting instance that it has either finished processing or that a human has performed some action. You can't cross this boundary in SharePoint Designer workflows.
Figure 3: Wouldn't it be great to have a SPD workflow leverage external systems? It's possible!
Buster #3) Parent Child workflows: In SharePoint Designer there is no way (out-of-the-box) to have a parent workflow spawn child workflows and wait for those workflows to finish executing. I see so many companies build a huge workflow that tries to do everything and fails. Plus, you then get no reusability in your workflows. Rather, I suggest building smaller, reusable workflows and having parent workflows invoke those child workflows, as seen in Figure 4:
Figure 4, REUSABILLITY!!!! Plus, there are many cases where parent/child workflows are the ONLY way that works.
So back to figure 1. There's an obvious chasm between SharePoint Designer workflows and Visual Studio workflows. Is there a sweet spot? I believe there most certainly is!!!!
The question is this... do you have capital to spend on buying 3rd party software? Typically 3rd parties try to fill the gap. In my opinionNintex Workflows is the best 3rd party in this space. However, if you can't/won't buy a third party, you can still build it, and I think in this post you'll see it is entirely reasonable for a good .NET/SharePoint Developer to extend their workflows into this sweet spot. The goal for this post is to show how to extend SharePoint Designer workflows with custom .NET code to solve platform busters #2 and #3. Specifically, my walkthrough shows how to build #3 in SharePoint Designer! (once you see how to build #3 you can easily translate that into how to build #2)
As far as buster #1 goes, we're still up a creek there. Really the only way in SharePoint Designer to "mimic" a sate-machine is to have a complicated set of nested loops... ick! This unfortunately is essentially what even Nintex does... mimic state machines :( But hey, let's celebrate that we can accommodate #2 and #3 at least :)
The "how" is quite simple. SharePoint Designer allow you to deploy your own custom Actions and Conditions. You can then therefore build your custom actions in Visual Studio, and then deploy them to your SharePoint Designer users. This is in essence how you can "bridge the chasm" between Designer and Visual Studio, because now your developers can build actions that do very advanced things like connect to external systems, and your power users can use those actions when they model their business processes in SharePoint Designer.
This is done by placing a custom XML file into the "SharePoint Root", "Workflow" directory (Figure 3). This XML file will need a .ACTIONS extension. When SharePoint Designer loads, it goes to this folder and downloads all the .ACTIONS files it sees there, and uses those actions (and conditions) to populate the Actions drop down in the workflow designer interface.
So let's walk through Buster #2 for a bit. How exactly does this work? What we're going to leverage is a workflow technique called Pluggable Workflow Services, or otherwise called External Data Exchanges. In Visual Studio, this translates into essentially three things:
-
Custom External Data Exchange Service: This service is really just a class/interface. Don't get scared! It's easy. Simply implement the interface and add your code that connects to your external system! EDE more or less does the rest!
-
CallExternalMethodActivity Activity: This activity is used to call into your custom EDE service. AKA, your workflow uses this activity to send some data to your external application, and then can optionally sit and wait for a response.
-
HandleExternalEventActivity Activity: This activity is used to handle a event that was fired externally to SharePoint. In our example, our SharePoint Designer workflow will use this activity to sit and wait for the response from the external system. Our custom service code will raise the event, and this activity will respond to that event being raised.
So with those three key pieces, let's expand on figure 3 by adding those 3 pieces, as seen in figure 4 below:
As you can see, we added two red arrows and two green boxes into the existing figure. The first "Local Service" green box is our custom service (component #1). The two red arrows are the two activities (components #2 and #3). The "Call" arrow calls the service, which then the service does some junk like call into the external system. Then, our "Handle" arrow handles the event raised from the external system.
The only other piece is the "Custom WCF Service" box. This is only needed if a external line of business application needs to call into SharePoint. Essentially, to raise the event to the handle method, the event needs to be raised on the SharePoint Farm. Well, how does the external system get on the SharePoint farm? The best way to do this is to have the external system invoke a WCF service that resides on the SharePoint Farm. From that WCF service, the event to which the workflow is waiting, can be raised. Again, this WCF service is ONLY needed when receiving messages/events externally to the SharePoint farm.
So how do we build this custom EDE service?
The first step is to create an interface marked with the [ExternalDataExchange] attribute. What's in the interface is up to you. Essentially, keep two things in mind:
-
What data do you want the workflow to send to the external system?
-
What data do you want the external system to send back to the workflow when it's done?
The answer to those questions help you create the interface. Notice my example in Figure 5. I have one method, and one event. The workflow can call the method which takes some parameters (answer to #1). Secondly, the workflow can sit and wait for the external system to respond which when it does sends some arguments through the raising of the event (answer to #2).
Figure 5
With the interface in place, the next step is to implement that interface on a class. Notice our implementation in figure 6. We simply extend the SPWorkflowExternalDataExchangeService class, and then we implement our custom interface. The only curve ball is the CallEventHandler method that satisfies the SPWorkflowExternalDataExchangeService implementation. When our custom WCF service raises the event, this method is called. The method checks to see what event the WCF service want's to raise, and then this method actually raises the event which then notifies the appropriate workflow instance that is waiting on the event to be raised.
The last bit of code is the code in the WCF service. How do we raise the event? It's very simple, in fact it's pretty much one line of code using the SPWorkflowExternalDataExchangeService.RaiseEvent method, as can be seen in Figure 7:
Step by Step Walkthrough - Creating Parent Child Workflows in SharePoint Designer
Up to this point we've just be theoretical. How do you actually do this? The following is a smattering of steps that can walk you through the somewhat complicated process. Figure 8 is a diagram that shows exactly what we're building. We're going to create a parent workflow that runs in Farm #1 that starts a workflow in Farm #2. The parent workflow then sits and waits for its child workflow in farm #2 to finish. When the child finishes, it will then call a WCF service in Farm #1 which then notifies the parent workflow that the child has finished. And the best part - BOTH THE PARENT AND CHILD WORKFLOWS ARE SHAREPOINT DESIGNER WORKFLOWS!!!
-
Right click the project and choose Add, Class. Call the class something like ParentChildService.cs
Add the following using statements to the top:
using System.Workflow.Activities;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WorkflowActions;
using System.Workflow.ComponentModel;
using System.ComponentModel;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.Client; -
Create your interface and event args in this same cs file, something like the following:
[ExternalDataExchange]
public interface IParentChildService
{
// CallExternalMethod
void StartSomeWorkflow(string Message, Guid CallbackWorkflowId);
// CallExternalMethod
void UpdateParentWorkflow(string Message, Guid CallbackWorkflowId);
// HandleExternalEvent
event EventHandler<UpdateParentWorkflowEventArgs> UpdateParentWorkflowEvent;
}
[Serializable]
public class UpdateParentWorkflowEventArgs : ExternalDataEventArgs
{
public UpdateParentWorkflowEventArgs(Guid workflowIdtoUpdate) : base(workflowIdtoUpdate) { }
public string Response;
}
-
Make the ParentChildService class public, and extend SPWorkflowExternalDataExchangeService and your interface, eg IParentChildService. Afterwards, implement each interface which will stub out the necessary methods.
-
In the StartSomeWorkflow method, paste the following code:
using (ClientContext context = new ClientContext("http://intranet.pdd.com"))
{
List list = context.Web.Lists.GetByTitle("Some List");
context.Load(list);
ListItem newitem = list.AddItem(new ListItemCreationInformation());
newitem["Title"] = Message;
newitem["Instance"] = CallbackWorkflowId.ToString();
newitem.Update();
context.ExecuteQuery();
}
This code create a list item in a list titled "Some List" in Farm 2. In a bit, we'll create a SharePoint Designer workflow that sits on this list and waits for this item to be created. Note - you'll need to add a reference to the Microsoft.SharePoint.Client and Microsoft.SharePoint.Client.Runtime DLLs since this code is using the Client object Model to connect to the external Farm to create the list item. This wouldn't be necessary if the child workflow is on the same farm as the parent.
Also note the "Instance" column. The child workflow needs to know the ID of it's parent. If it doesn't know the ID, it won't know which running workflow is waiting for it to complete, so the parent workflow has to pass it's ID to the child so the child can respond when its done. -
Add the following code into the CallEventHandler method:
switch (eventName){
case "UpdateParentWorkflowEvent":
var args = new UpdateParentWorkflowEventArgs(workflow.InstanceId);
args.Response = eventData[0].ToString();
this.UpdateParentWorkflowEvent(null, args);
break;
}
-
In the IService1 interface in the new project, paste the following service method that our child workflow can invoke:
[OperationContract]
void UpdateParentWorkflow(string Message, Guid CallbackWorkflowId);
-
Rename the IService1.cs class and class file to IChildWorkflowCallback and IChildWorkflowCallback.cs respectively.
Rename the Service1.cs class and class file to ChildWorkflowCallback and ChildWorkflowCallback.cs respectively. -
Re-Implement the interface on ChildWorkflowCallback which will generate our UpdateParentWorkflow method, then paste the following code into that method:
using (SPSite site = new SPSite("http://dev.farm.com/")){
using (SPWeb web = site.RootWeb)
{
// handled by the CallEventHandler callback, which
// in turn invokes the [in] event that then communicates
// with the workflow instance
SPWorkflowExternalDataExchangeService.RaiseEvent(web,
CallbackWorkflowId,
typeof(IParentChildService),
"UpdateParentWorkflowEvent",
new object[] { Message });
}
}
Note - you'll need to add a reference to the SharePoint DLL and to the other (SharePoint) project to get this code to work. Since this code will run on the Parent workflow's SharePoint farm, we can simply use the standard object model. We get our site and web, and then raise the event to the EDE service, and pass in the Parent's ID to notify.
-
Next we need to deploy this WCF Service. Create a new IIS site in the Parent's farm. Ensure that the new site's Application Pool is set to an App Poo where the service account HAS DBO TO THE SHAREPOINT DATABASE AS WELL AS FULL CONTROL TO THE PARENT's SHAREPOINT SITE.
-
Next we need to right click the WCF project and choose Publish. Enter the name of the IIS site and the URL and click publish.
-
With this WCF service in place, the next step is to invoke the service from the UpdateParentWorkflow method that is called when the child workflow finishes executing. To do this, add a Web Reference to the project and enter the url to the WCF service you just deployed. Name the service reference ChildWorkflowCallback and then paste in the following code into the UpdateParentWorkflow method:
ChildWorkflowCallback.ChildWorkflowCallbackClient client = newChildWorkflowCallback.ChildWorkflowCallbackClient();
client.UpdateParentWorkflow(Message, CallbackWorkflowId);
-
Next we need to create the action that the parent workflow calls when they want to start the child workflow. Back on the SharePoint Project, create a new class titled StartWorkflow. Make this class a public class and extend "SequenceActivity". Add the following using statements:
using System.Workflow.Activities;
using System.Workflow.ComponentModel;
using Microsoft.SharePoint.WorkflowActions;
using System.ComponentModel;
Next add the following properties inside the class:
public StartWorkflow()
{
InitializeComponent();
}
public static DependencyProperty SomeDataToSendProperty =
System.Workflow.ComponentModel.DependencyProperty.Register(
"SomeDataToSend", typeof(string), typeof(StartWorkflow));
public static DependencyProperty __ContextProperty =
System.Workflow.ComponentModel.DependencyProperty.Register(
"__Context", typeof(WorkflowContext), typeof(StartWorkflow));
[Description("SomeDataToSend")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string SomeDataToSend
{
get { return ((string)(base.GetValue(StartWorkflow.SomeDataToSendProperty))); }
set
{
base.SetValue(StartWorkflow.SomeDataToSendProperty, value);
}
}
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public WorkflowContext __Context
{
get
{
return ((WorkflowContext)(base.GetValue(StartWorkflow.__ContextProperty)));
}
set
{
base.SetValue(StartWorkflow.__ContextProperty, value);
}
}
These are two properties, one called __Context, and the other called SomeDataToSend. The parent workflow will pass a string into the SomeDataToSend which the child will reference. The __Context property is simply there so we can grab the parent workflow ID. -
Next double click SomeWorkflow.cs in the project explorer to open the activity in design mode. Drag and drop the CallExternalMethod activity onto the surface from the toolbox under WWF v3.0. Next edit the properties of this method:
1) Edit the InterfaceType property and change it to the IParentChildService interface.
2) Edit the MethodName property and change it to StartSomeWorkflow.
3) Edit the ClientCallbackWorkflowID and change it to the __Context.WorkflowInstanceId property.
4) Edit the Message property and change it to the SomeDataToSend property.
-
Next we need to create another activity. When the child workflow is done executing, it needs to call the custom WCF service. Create a new class just like in step 14 but this time called UpdateWorkflow.cs, paste in the following properties like before:
public UpdateWorkflow()
{
InitializeComponent();
}
public static DependencyProperty InstanceProperty =
System.Workflow.ComponentModel.DependencyProperty.Register(
"Instance", typeof(Guid), typeof(UpdateWorkflow));
[Description("Instance")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Guid Instance
{
get { return ((Guid)(base.GetValue(UpdateWorkflow.InstanceProperty))); }
set
{
base.SetValue(UpdateWorkflow.InstanceProperty, value);
}
}
public static DependencyProperty ResponseDataToSendProperty =
System.Workflow.ComponentModel.DependencyProperty.Register(
"ResponseDataToSend", typeof(string), typeof(UpdateWorkflow));
[Description("ResponseDataToSend")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string ResponseDataToSend
{
get { return ((string)(base.GetValue(UpdateWorkflow.ResponseDataToSendProperty))); }
set
{
base.SetValue(UpdateWorkflow.ResponseDataToSendProperty, value);
}
}
public static DependencyProperty __ContextProperty =
System.Workflow.ComponentModel.DependencyProperty.Register(
"__Context", typeof(WorkflowContext), typeof(UpdateWorkflow));
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public WorkflowContext __Context
{
get
{
return ((WorkflowContext)(base.GetValue(UpdateWorkflow.__ContextProperty)));
}
set
{
base.SetValue(UpdateWorkflow.__ContextProperty, value);
}
}
The only difference with these properties is now instead of SomeDataToSend, we have a property called ResponseDataToSend, aka the data the child is going to send to the parent.
-
Just like before, in the solution explorer double click UpdateWorkflow.cs to load the activity in the designer. Drop the CallExternalMethod activity onto the surface, and set the properties:
1) InterfaceType should again be IParentChildService.
2) MethodName should be UpdateParentWorkflow
3) CallbackWorkflowId should be set to the Instance Property.
4) Message should be set to the ResponseDataToSend property.
Lastly, right click on the activity and choose Generate Handlers. Add the following code into the GH method to set the Instance property to that of the parent's workflow ID:SPListItem item = __Context.Web.Lists["Some List"].GetItemById(__Context.ItemId);
Instance = new Guid(item["Instance"].ToString());
-
With that activity done, we need one final activity. The parent workflow needs an action to make it wait for the child to finish, and then to do something with the data that the child sends. Add another activity but this time with the name of HandleWorkflowUpdate.cs and paste in the following properties:
public HandleWorkflowUpdate()
{
InitializeComponent();
}
public static DependencyProperty ResponseReceivedProperty =
System.Workflow.ComponentModel.DependencyProperty.Register(
"ResponseReceived", typeof(string), typeof(HandleWorkflowUpdate));
[Description("ResponseReceived")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public string ResponseReceived
{
get { return ((string)(base.GetValue(HandleWorkflowUpdate.ResponseReceivedProperty))); }
set
{
base.SetValue(HandleWorkflowUpdate.ResponseReceivedProperty, value);
}
}
-
Instead of the CallExternalMethod activity, drag and drop the HandleExternalEvent activity onto the new activity's design surface. Set the following properties:
1) InterfaceType again to IParentChildService.
2) EventName to UpdateParentWorkflowEvent
Like the last activity, right lick the activity and choose Generate Handlers and paste the following code to set the Response property to that of the argument's value:UpdateWorkflowEventArgs args = (UpdateWorkflowEventArgs)e;
ResponseReceived = args.Response;
-
The last step in Visual Studio is to create the .ACTIONS file that makes these .NET activities available as Actions in SharePoint Designer. To do this, right click the project and choose Add->Mapped folder and select the Template->1033->Workflow folder:
Next, within the newly mapped folder, create a new XML file and paste in the following code (note, name of the file doesn't matter, just make sure it has a .ACTIONS extension):<?xml version="1.0" encoding="utf-8"?>
<WorkflowInfo Language="en-us">
<Actions Sequential="then" Parallel="and">
<Action Name="StartWorkflow"
ClassName="[AssemblyName].StartWorkflow"
Assembly="[AssemblyName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=[TOKEN]"
AppliesTo="all"
Category="Custom Actions">
<RuleDesigner Sentence="Send %1 data to a new workflow instance on intranet.pdd.com.">
<FieldBind Field="SomeDataToSend" Text="this" Id="1" />
</RuleDesigner>
<Parameters>
<Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" Direction="In"/>
<Parameter Name="SomeDataToSend" Type="System.String, mscorlib" Direction="In" />
</Parameters>
</Action>
<Action Name="UpdateWorkflow"
ClassName="[AssemblyName].UpdateWorkflow"
Assembly="[AssemblyName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=[TOKEN]"
AppliesTo="all"
Category="Custom Actions">
<RuleDesigner Sentence="Update parent workflow and send %1 this data.">
<FieldBind Field="ResponseDataToSend" Text="this" Id="1" />
</RuleDesigner>
<Parameters>
<Parameter Name="__Context" Type="Microsoft.SharePoint.WorkflowActions.WorkflowContext, Microsoft.SharePoint.WorkflowActions" Direction="In"/>
<Parameter Name="ResponseDataToSend" Type="System.String, mscorlib" Direction="In" />
</Parameters>
</Action>
<Action Name="HandleWorkflowUpdate"
ClassName="[AssemblyName].HandleWorkflowUpdate"
Assembly="[AssemblyName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=[TOKEN]"
AppliesTo="all"
Category="Custom Actions">
<RuleDesigner Sentence="Wait for child workflow to complete and save response in this %1.">
<FieldBind Field="ResponseReceived" Text="variable" Id="1" DesignerType="ParameterNames" />
</RuleDesigner>
<Parameters>
<Parameter Name="ResponseReceived" Type="System.String, mscorlib" Direction="Out" />
</Parameters>
</Action>
</Actions>
</WorkflowInfo>
Notice in the code that you'll have to replace [AssemblyName] and [TOKEN] with your actual values.
-
Lastly, build the project and if you'll deploy to your local dev workstation, right click and choose "Deploy". Otherwise take the WSP solution package and upload it on your SharePoint farm to deploy (through powershell - Add-spsolution). Since my parent workflow runs on the local farm, "Deploy" will suffice. However, since my child is on a different farm, I'll need to manually move the WSP and run Add-SpSOlution.
However, before we can create your SharePoint Designer workflows we need to do a bunch of stuff to the web.config files on both the parent and child farms. -
WCF Service web.config (note [AssemblyName], [NAMESPACE], and [TOKEN] tags):
in the <configSections> tag:<sectionGroup name="System.Workflow.ComponentModel.WorkflowCompiler"type="System.Workflow.ComponentModel.Compiler.WorkflowCompilerConfigurationSectionGroup, System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
<section name="authorizedTypes"type="System.Workflow.ComponentModel.Compiler.AuthorizedTypesSectionHandler, System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<section name="authorizedRuleTypes"type="System.Workflow.ComponentModel.Compiler.AuthorizedTypesSectionHandler, System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</sectionGroup>
in the <configuration> tag:<System.Workflow.ComponentModel.WorkflowCompiler>
<authorizedTypes>
<authorizedType Assembly="System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Workflow.*" TypeName="*" Authorized="True" />
<authorizedType Assembly="System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Workflow.*" TypeName="WhileActivity" Authorized="False"/>
<authorizedType Assembly="System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Workflow.*" TypeName="ConditionedActivityGroup"Authorized="False" />
<authorizedType Assembly="System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Workflow.*" TypeName="ReplicatorActivity"Authorized="False" />
<authorizedType Assembly="System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Workflow.*" TypeName="*" Authorized="True" />
<authorizedType Assembly="System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" Namespace="System.Workflow.Runtime" TypeName="CorrelationToken"Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System" TypeName="Guid" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System" TypeName="DateTime" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System" TypeName="Boolean" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System" TypeName="Double" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System" TypeName="String" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System" TypeName="Enum" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System.Collections" TypeName="Hashtable" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System.Collections" TypeName="ArrayList" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System.Diagnostics" TypeName="DebuggableAttribute" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System.Runtime.CompilerServices" TypeName="CompilationRelaxationsAttribute" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System.Runtime.CompilerServices" TypeName="RuntimeCompatibilityAttribute" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System" TypeName="Int32" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System" TypeName="TimeSpan" Authorized="True" />
<authorizedType Assembly="mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"Namespace="System.Collections.ObjectModel" TypeName="Collection`1" Authorized="True" />
<authorizedType Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.Workflow"TypeName="SPWorkflowActivationProperties" Authorized="True" />
<authorizedType Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.Workflow"TypeName="SPWorkflowTaskProperties" Authorized="True" />
<authorizedType Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.Workflow"TypeName="SPWorkflowHistoryEventType" Authorized="True" />
<authorizedType Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.Workflow" TypeName="SPItemKey"Authorized="True" />
<authorizedType Assembly="Microsoft.SharePoint, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.Workflow" TypeName="SPWorkflowUserContext"Authorized="True" />
<authorizedType Assembly="Microsoft.SharePoint.WorkflowActions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.WorkflowActions" TypeName="*"Authorized="True" />
<authorizedType Assembly="Microsoft.Office.Access.Server.Application, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.Office.Access.Server.Macro.Runtime"TypeName="*" Authorized="True" />
<authorizedType Assembly="Microsoft.Office.Access.Server.Application, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"Namespace="Microsoft.Office.Access.Server.Quickflow.Runtime" TypeName="*" Authorized="True" />
<authorizedType Assembly="Microsoft.Office.Workflow.Actions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.Office.Workflow.Actions" TypeName="*"Authorized="True" />
<authorizedType Assembly="[AssemblyName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=[TOKEN]"Namespace="[NAMESPACE]" TypeName="*" Authorized="True" />
</authorizedTypes>
<authorizedRuleTypes>
<authorizedType Assembly="Microsoft.Office.Access.Server.Application, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"Namespace="Microsoft.Office.Access.Server.Quickflow.Runtime" TypeName="*" Authorized="True" />
<authorizedType Assembly="Microsoft.SharePoint.WorkflowActions, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Namespace="Microsoft.SharePoint.WorkflowActions"TypeName="WorkflowCodeTypeReferenceExpression" Authorized="True" />
<authorizedType Assembly="System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" Namespace="System.Runtime.CompilerServices" TypeName="ExtensionAttribute"Authorized="True" />
</authorizedRuleTypes>
</System.Workflow.ComponentModel.WorkflowCompiler>
-
In the SharePoint site's web.config:
In the <AuthorizedTypes> tag:<authorizedType Assembly="[AssemblyName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=76b48d68efbe7cc6"Namespace="[NAMESPACE]" TypeName="*" Authorized="True" />
In the <WorkflowServices> tag:<WorkflowService Assembly="[AssmeblyName], Version=1.0.0.0, Culture=neutral, PublicKeyToken=46f02ef46891fd72" Class="[NAMESPACE].ParentChildService">
</WorkflowService>
-
In the External farm's web.config (in addition to what's found in step 24). Note, this can be found in the App.config file in the project AFTER you add the service reference:
In, <system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IChildWorkflowCallback" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false"hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true"
algorithmSuite="Default" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://dev01.pdd.com:12345/ChildWorkflowCallback.svc"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IChildWorkflowCallback"
contract="ChildWorkflowCallback.IChildWorkflowCallback"name="WSHttpBinding_IChildWorkflowCallback">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
-
Now create the SharePoint Designer Workflow!!!!! Create a new site workflow in the parent farm. The first thing you'll notice is our custom actions in the Actions drop down!
For the parent workflow, I simply did the following:
This will start the workflow, and the StarWorkflow action will create a list item in Farm #2. When the list item is created, we want a child workflow to automatically start (next step). Until that workflow finishes the parent is waiting with it's status set to "Waiting for response". The response is then logged to the history list when it's received.
Lastly, create the child workflow which will look something like this:
With the two workflows deployed, its now time to test! Below is a screen shot of the 8 steps:
-
Parent workflow is started in Farm #1.
-
Parent workflow creates list item in Farm #2 and sets its own status to "Waiting for Response".
-
The list item the parent created shows up in Farm #2 with the parent workflow's ID.
-
The child workflow either starts automatically, or in this case manually.
-
ditto.
-
The child workflow calls the WCF service notifying the parent.
-
The custom service raises the event that the parent is waiting for. The parent awakes from its slumber and writes the child's message to the log.
-
The parent then finishes executing now that all it's child workflows are done.
Hi! My name is Phil Wicklund, and this is my blog! Take a spin and leave a comment! Also, check out my book on SharePoint workflows, 



