Sunday, September 12, 2010

SharePoint 2010 Pluggable Workflow Services – Part 2

This is the second post in the series SharePoint 2010 Pluggable Workflow Services. In the first post I demonstrated creating a very basic sequential workflow and exposing a business object which we’ll be leveraging to communicate the information from the workflow. In this post we will dive into the nitty-gritties of a Pluggable Workflow Service.

Creating a Pluggable Workflow Service

Here comes the crux of this post of as how we can create pluggable workflow service.

1. We will add new class file to our project and name is OrderTrackingService.

2. Add the following namespace deceleration to the class.

using System.Workflow.Activities;
using Microsoft.SharePoint.Workflow;

3. Now we will declare an interface IOrdertrackingService and add the following to the body of the interface.

[ExternalDataExchange]
public interface IOrderTrackingService
{
event EventHandler<OrderTrackingEventArgs> OrderTrackingEvent;
void DispatchOrder(Order order);
}

So what we did above is we defined a method signature DispatchOrder which when implemented on a class would do the needful task of creating an order entry in the database. This method would then be further called from the workflow service CallExternalMethod Activity. Also after executing the DispatchOrder method raises the OrderTrackingEvent to notify the workflow that the service has completed its execution. The HandleExternalEvents workflow activity then takes care of the event arguments as passed by the OrderTrackingEvent.

4. Add the following OrderTrackingEventArgs class.

[Serializable()]
public class OrderTrackingEventArgs : ExternalDataEventArgs
{
public OrderTrackingEventArgs(Guid id) : base(id) { }
public string DeliveryStatus;
}

The above code indicates that we will be passing DeliveryStatus as an argument to HandleExternalEvents activity back in the workflow.

5. Next on our OrderTrackingService class we will implement SPWorkflowExternalDataExchangeService and IOrderTrackingService interface.

class OrderTrackingService : SPWorkflowExternalDataExchangeService,IOrderTrackingService
{

public event EventHandler<OrderTrackingEventArgs> OrderTrackingEvent;

public void DispatchOrder(Order order)
{
throw new NotImplementedException();
}

public override void CallEventHandler(Type eventType, string eventName, object[] eventData, SPWorkflow workflow, string identity, System.Workflow.Runtime.IPendingWork workHandler, object workItem)
{
throw new NotImplementedException();
}

public override void CreateSubscription(MessageEventSubscription subscription)
{
throw new NotImplementedException();
}

public override void DeleteSubscription(Guid subscriptionId)
{
throw new NotImplementedException();
}
}

Now let’s write the logic for DispatchOrder method. Here we will use LINQ to SQL and connect to the local ContosoOrderTracking database which you restored using the db script.

6. Add a new item to the project of the type LINQ to SQL class and name is ContosoOrders.LinqToSqlClass

7. Using the server explorer create a connection to the ContosoOrderTracking db.ServerExpplorer

8. Drag and drop the ContosoOrder db table to the design surface.ContosoOrderTable

9. Back in the OrderTrackingService class, update the DispatchOrder method to like this.

public void DispatchOrder(Order order)
{
const string connection = @"Data Source=SHAREPOINT2010;Initial Catalog=ContosoOrderTracking;Integrated Security=True";

using (ContosoOrdersDataContext contosoDataCntxt = new ContosoOrdersDataContext(connection))
{
try
{
ContosoOrder corder = new ContosoOrder();
corder.Title = order.Title;
corder.Quantity = order.Quantity;
corder.Amount = order.Amount;
corder.CutomerName = order.CustomerName;
corder.ID = new Guid(order.ID);

contosoDataCntxt.ContosoOrders.InsertOnSubmit(corder);
contosoDataCntxt.SubmitChanges();

order.DeliveryStatus = "Delivered";
}
catch
{
order.DeliveryStatus = "Returned";
}
}

RaiseEvent(this.CurrentWorkflow.ParentWeb, this.CurrentWorkflow.InstanceId,
typeof(IOrderTrackingService), "OrderTrackingEvent", new object[] { order.DeliveryStatus });
}

In the above code, we leveraged the LINQ to SQL to create a new entry in the ContosoOrder database. The order object as used in the above code would be supplied by the calling workflow. Also we are setting the DeliveryStatus property of the order object to Delivered if the database entry is created successfully else we set it to Returned indicating a failure.

At the end we call the RaiseEvent method to notify the calling workflow of the completion of the service activity.

10. The final piece to complete this puzzle is to implement the CallEventHandler method as follows.

public override void CallEventHandler(Type eventType, string eventName, object[] eventData, SPWorkflow workflow, string identity, System.Workflow.Runtime.IPendingWork workHandler, object workItem)
{
if (string.Equals(eventName, "OrderTrackingEvent", StringComparison.OrdinalIgnoreCase))
{
var args = new OrderTrackingEventArgs(workflow.InstanceId);
args.DeliveryStatus = eventData[0].ToString();
this.OrderTrackingEvent(null, args);
}
}

The CallEventHandler method gets called each time when the workflow service requests an event. Here we create the OrderTrackingEventArgs instance and pass in the workflow's instance ID to so the event knows which workflow it's invoking the event with. We next pass in the status message from the event receiver and finally invoke the event.

This completes the workflow service here. Now we need to make our Order Tracking workflow be able to call this service.

Updating the Workflow to call Pluggable Workflow Service

1. Back in our Order Tracking workflow design surface; add the following activities as shown after logTOHistoryActivity1.

WorkflowDesign


2. After adding the above activities, select callExternalMethodActivity1 and set the following properties in the property pane as shown below.callExternalMethodActivity_1

3. Now right click handleExternalEventActivity1 and Generate Handlers. Also set the properties as shown below.handleExternalEventActivity1

4. For the property with the name e, we will bind it to a new activity field.bind_e

Back in the OrderTracking.cs file we will remove the highlighted code.

removecode

5. Update the handleExternalEventActivity1_Invoked as shown below.

public OrderTrackingEventArgs handleExternalEventActivity1_e1;
private void handleExternalEventActivity1_Invoked(object sender, ExternalDataEventArgs e)
{
logToHistoryListActivity2.HistoryDescription = string.Format("Order delivered to the customer sucessfully.");
}

6. Back in our Order Tracking workflow design surface, right click codeActivity2 and click Generate Handlers.

7. Update the codeActivity2_ExecuteCode method as follows.

private void codeActivity2_ExecuteCode(object sender, EventArgs e)
{
SPListItem item = workflowProperties.Item;
item["DeliveryStatus"] = handleExternalEventActivity1_e1.DeliveryStatus;
if (String.Equals(handleExternalEventActivity1_e1.DeliveryStatus,
"Delivered", StringComparison.OrdinalIgnoreCase))
{
item["InvoiceStatus"] = "Invoiced";
}
item.Update();
}

In the above code, we have used the value of the event arguments DeliveryStatus property to set list item’s delivery status field. Also we are checking if the order was delivered then raise the invoice to the customer.

8. The final step in this entire flow is to add some configurations to the target web applications web.config file to make the web application aware of our pluggable workflow service.

Add the entries below to the WorkflowServices tag under SharePoint section in the target web application.

<WorkflowService Assembly="OrderTrackingSystem, Version=1.0.0.0, Culture=neutral, PublicKeyToken=YOUR_ASSEMLBLY_PKT" Class="OrderTrackingSystem.OrderTrackingService">
</WorkflowService>
webconfigchanges
9. Hit F5 and deploy the solution.

10. Running the workflow on the item we created previously.

ItemStatus_final

The history list shows the following updates.

workflowhistoryupdate The Database receives a new entry via the Pluggable Workflow Service


DatabaseEntry

Hope this series helped you to understand some basic concepts of creating a Pluggable Workflow Services.


The associated code and other resources can be downloaded from here:

SharePoint 2010 Pluggable Workflow Services – Part 1

Along with the many advancements and new features, SharePoint 2010 introduces a yet another important feature of Pluggable Workflow Services. While Pluggable Workflow Services have been around since Windows Workflow Foundation in .Net 3.5, its support was missing in SharePoint 2007. But now the SharePoint 2010 workflow engine supports it.

So what’s a Pluggable Workflow Service all about?

Here is what MSDN says:

Pluggable workflow services provide a mechanism that allows external applications or components to programmatically communicate with workflow instances currently running on the server.

This means that workflows in SharePoint 2010 now can interact with a wide variety of external events and allows a developer to control up to which point the workflow gets executed and waits for information from an external process.

Now when you say that the workflow instance waits for information from an external process, it doesn’t mean that the workflow instance needs to wait in an active state and consume server resources (like CPU, RAM etc.) till the time it gets the response back. The workflow instance would be dehydrated (aka the state of the workflow instance would be written to the database) and rehydrated back (aka workflow instance state read back from the database) once the external activity gets completed.

In this post I will attempt to explain a simple pluggable workflow service taking an order tracking system as an example. The order tracking system implements a sequential workflow which gets started as soon as a new order request is created. This workflow tracks the delivery status and the invoice status for a particular order.

When a new order request is created (via SharePoint list), the workflow calls a pluggable workflow service which tracks whether the order was delivered to the customer or not (creating a new database table entry) and accordingly updates the workflow about this information.

For running this example, you would need to

  • Restore ContosoOrderTracking.sql script for creating the ContosoOrderTracking database.
  • Create an Orders list using the Orders.stp list definition.

Create a Sequential Workflow

  1. Open visual Studio 2010, Select New->Project.
  2. Under the installed templates, select the SharePoint -> 2010.
  3. Select the Empty SharePoint Project Template and enter the project name as OrderTrackingSystem and click OK.
  4. Enter the target site collection name for debugging purpose. Now, since we are going to create a Visual Studio workflow, we need to deploy this solution as a farm solution. DeploymentType

5. Click Finish

At this point we have a bare metal SharePoint solution ready. Now we will add a new Sequential Workflow SPI (SharePoint Items) to this solution.

6. Add a new item and select Sequential Workflow and name it OrderTracking.AddSequentialWF

7. Click Add.
8. Name the workflow as Order Tracking and leave the workflow type as List Workflow.WorkFlowType

9. Click Next.
10. From the library or list dropdown, select the Orders list (as shown below) which you created using the attached stp file and leave the other selections as is.SelectList

11. Click Next.
12. We want this workflow to get started on new item creation as well as manually. Leave the current selections default and click Finish.ConditionToStart

At this point our solution should look like as shown below. Before we move ahead, we would need to make some changes to this solution for consistency.SolutionStructure

13. Rename the Feature1 to a sensible name as OrderTrackingWorkflow.

At this point we are done creating a very basic SharePoint sequential workflow solution. You can optionally hit F5 to ensure that everything is fine which I am sure it would be J.

Creating a Business Object

Now we will need a custom business object “Order” which can encapsulate the order information and makes it easy for us to communicate with our workflow and the Pluggable Workflow Service.

  1. Add a new class Order to our workflow solution.
  2. We will add 6 properties to our order class representing the attributes of an order namely ID, Title, Quantity, Amount, Customer, DeliveryStatus.
  3. Since we are dealing with a workflow where persisting the object state is a must, we would need to make Order class Serializable by implementing ISerializable interface and decorate it with Serializable attribute.
  4. Also we would need to implement the GetObjectData method for serializing the object and the default constructer to deserialize it.
    At this point the Order class should look like this.
  5. using System;
    using System.Runtime.Serialization;

    [Serializable()]
    public class Order : ISerializable
    {
    public string ID { get; set; }
    public string Title { get; set; }
    public string Quantity { get; set; }
    public string CustomerName { get; set; }
    public string Amount { get; set; }
    public string DeliveryStatus { get; set; }

    public Order()
    {}

    public Order(SerializationInfo info, StreamingContext context)
    {
    ID = (string)info.GetValue("ID", typeof(string));
    Title = (string)info.GetValue("Title", typeof(string));
    Quantity = (string)info.GetValue("Quantity", typeof(string));
    CustomerName = (string)info.GetValue("CustomerName", typeof(string));
    Amount = (string)info.GetValue("Amount", typeof(string));
    DeliveryStatus = (string)info.GetValue("DeliveryStatus", typeof(string));
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context)
    {
    info.AddValue("ID", ID);
    info.AddValue("Title", Title);
    info.AddValue("Quantity", Quantity);
    info.AddValue("CustomerName", CustomerName);
    info.AddValue("Amount", Amount);
    info.AddValue("DeliveryStatus", DeliveryStatus);
    }
    }

Adding Life to thee Workflow

Now we will add some activities to the sequential workflow we created above.

  1. Switch to the workflow design view and drag and drop a Code and LogToHistoryList activity on the design surface.WorkflowDesign_part1

2. Right click in codeActivity1 and click Generate Handlers from the menu. This will add a code behind method for this activity.

3. Back in the code create in instance of the Order class.

public Order order = new OrderTrackingSystem.Order();

4. Also the following code to the codeActivity1_ExecuteCode method.

In the code below we are taking the current list item on which the workflow is running and initializing the order object with the required properties. Also we are updating the list item DeliveryStaus choice field to Dispatched indicating that the order has been dispatched to the customer.

private void codeActivity1_ExecuteCode(object sender, EventArgs e)
{
SPListItem item = workflowProperties.Item;
order.Title = item.Title;
order.ID = item.UniqueId.ToString();
order.Quantity = item["Quantity"].ToString();
order.Amount = item["Amount"].ToString();
order.CustomerName = item["CustomerName"].ToString();

item["DeliveryStatus"] = "Dispatched";
item.Update();
}

5. At this point it would be a good idea to update the workflow history list with a status message. Repeat #2 to generate handlers for logToHistoryListActivity1 and add the following code.

private void logToHistoryListActivity1_MethodInvoking(object sender, EventArgs e)
{
logToHistoryListActivity1.HistoryDescription = "Order dispatched to the customer.";
}


6. Put a break point in codeActivity1_ExecuteCode hit F5 to deploy and debug the solution.


Now let’s add a new item to the Orders list and see if the workflow is functioning properly.

NewItem

As soon as you save the above item, the break point in our workflow gets hit where you can further play around and see the runtime properties of the objects. Further hit F5 to come out of debugging mode.

DebugCode

This will complete you workflow on the item you created above and now the DeliveryStatus field shows Dispatched. Also the workflow status field show Completed.

ItemStatus

Also clicking on this Completed link will navigate you to page below where you can see the status page of the workflow where we can see the comments in workflow history.

WorkflowStatus

In the next post we will dive into the details of creating a Pluggable Workflow Service.

The associated code and other resources can be downloaded from here: