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:

2 comments:

  1. Nice demonstration !!!

    ReplyDelete
  2. "reveal" or "unravel". not "un-reveal" :) and "its" not "it's" :)

    ReplyDelete