I am finally getting around to completing this two part series on what we have done for you as far as workflow communication goes inside Aware Server v3.0.
If you remember from Part 1 I simply demonstrated what it takes using plain vanilla Windows Workflow to have a workflow instance wait on an correlated event. To fire an event back into the right workflow instance you need the instance id to load it and fire the event. This poses a problem when an event may not come in for quite some time. Where do you store the instance id?
You can either store the instance id in one of your own application tables and look it up every time before firing events or you can use are somewhat simplified approach with Aware Server. I say simplified because we took it as far as we could. In our SDK there is an order processing sample so I will refer to that for the rest of this blog entry.
So what have we done for you to make this easier? For starters we provide our own implementation of the WorkflowSubscriptionService that ships with WF. The WF runtime detects if an instance of this service is added to the runtime and calls you to tell you about every Activity in an instance that requires correlation. This allows us to store this information in SQL Server so we can handle the lookups for you at a later time.
Next we assume that anything you are correlating on must be unique. For example if your system generates an OrderId and you tell the WF runtime that this is the attribute you want to correlate on using [CorrelationParameter("OrderId")] attribute then we take a hash of your order id and store it for later lookups.
So again lets assume we have the following workflow service interface. Except now notice we have added some new attributes.
1: /// <summary>
2: /// The [ExposedAutoCorrelationEvent] tells the client generation engine to
3: /// expose these events so the client can call the directly
4: ///
5: /// This service must be exposed to the Aware correlation engine via the
6: /// [ExposedWorkflowService(typeof(IOrderWorkflowService), "OrderEngine")] attribute located
7: /// in the OrderProcessingProcess.cs file attached to the OrderProcessingProcess.
8: /// </summary>
9: [ExternalDataExchange]
10: [CorrelationParameter("OrderId")]
11: public interface IOrderWorkflowService
12: {
13: /// <summary>
14: ///
15: /// </summary>
16: [CorrelationAlias("OrderId", "e.OrderId")]
17: [ExposedAutoCorrelationEvent]
18: event EventHandler<OrderWorkflowServiceEventArgs> OrderCanceled;
19:
20: /// <summary>
21: ///
22: /// </summary>
23: [CorrelationAlias("OrderId", "e.OrderId")]
24: [ExposedAutoCorrelationEvent]
25: event EventHandler<OrderWorkflowServiceEventArgs> OrderShipped;
26:
27: /// <summary>
28: ///
29: /// </summary>
30: [CorrelationAlias("OrderId", "e.OrderId")]
31: [ExposedAutoCorrelationEvent]
32: event EventHandler<OrderWorkflowServiceEventArgs> OrderProcessed;
33:
34: /// <summary>
35: /// This event will be fired from the Host.cs file in the ProcessStarted event.
36: /// This is necessary to initialize the Correlation engine for a particular workflow instance
37: /// inside WF. Note this event is not exposed to the outside world as it will only be invoked
38: /// internally.
39: /// </summary>
40: [CorrelationAlias("OrderId", "e.OrderId")]
41: [CorrelationInitializer]
42: [ExposedAutoCorrelationEvent(ExposedExternally=false)]
43: event EventHandler<OrderWorkflowServiceEventArgs> OrderCreated;
44: }
Every event you want us to automatically expose to the outside world for you needs to be marked with the [ExposedAutoCorrelationEvent] attribute. The OrderCreated event (an internal event used to initialize the correlation) is marked as ExposedExternally=false.
Next we need to update our ExternalDataEventArgs class and add 2 attributes in the constructor.
1: /// <summary>
2: ///
3: /// </summary>
4: [Serializable]
5: public class OrderWorkflowServiceEventArgs : ExternalDataEventArgs
6: {
7: private Guid orderId;
8:
9: /// <summary>
10: ///
11: /// </summary>
12: public Guid OrderId
13: {
14: get { return orderId; }
15: set { orderId = value; }
16: }
17:
18: /// <summary>
19: /// The [AutoCorrelationInstanceId] tells the client generation engine which constructor parameter
20: /// should be mapped to the instance id to get workflow instance.
21: ///
22: /// The [AutoCorrelationParameter] tells the correlation engine which [CorrelationParameter] attribute to map
23: /// to on the IOrderWorkflowService. You can have multiple correlation values. The combination of them must be unique.
24: /// </summary>
25: /// <param name="instanceId"></param>
26: /// <param name="orderId"></param>
27: public OrderWorkflowServiceEventArgs([AutoCorrelationInstanceId] Guid instanceId, [AutoCorrelationParameter("OrderId")] Guid orderId)
28: : base (instanceId)
29: {
30: this.orderId = orderId;
31: }
32: }
Notice the [AutoCorrelationInstanceId] attribute. This is used to tell the Aware runtime which parameter is the unique WF instance id Guid. The other attribute [AutoCorrelationParameter("OrderId")] must be applied to each [CorrelationParameter("XXX")] you applied to your IOrderWorkflowService above. In this case we only have one correlation parameter so [AutoCorrelationParameter("OrderId")] is applied to the "orderId" parameter in the constructor. Again nothing new yet. Our engine will use all these attributes to automatically generate a typed proxy for any clients (ASP.NET, Winforms, etc.) that want to trigger events externally.
Lastly we need to implement our IOrderWorkflowService interface.
1: /// <summary>
2: /// You must derive from CorrelationWorkflowService
3: /// </summary>
4: public class OrderWorkflowService : CorrelationWorkflowService<IOrderWorkflowService>, IOrderWorkflowService
5: {
6: //
7: // All these events will be automatically wired up by Aware and fired when called.
8: //
9: #region IOrderWorkflowService Members
10:
11: public event EventHandler<OrderWorkflowServiceEventArgs> OrderCanceled;
12:
13: public event EventHandler<OrderWorkflowServiceEventArgs> OrderShipped;
14:
15: public event EventHandler<OrderWorkflowServiceEventArgs> OrderProcessed;
16:
17: public event EventHandler<OrderWorkflowServiceEventArgs> OrderCreated;
18:
19: #endregion
20: }
Notice we don't need to actually implement the events. By deriving from our CorrelationWorkflowService we will automatically implement those for you. When you build you will get CS0067 warnings, but your can wrap those with a #pragma to remove those.
In your Host.cs file of your Aware Business Process project make sure you add your OrderWorkflowService to the runtime.
1: /// <summary>
2: ///
3: /// </summary>
4: /// <param name="sender"></param>
5: /// <param name="e"></param>
6: void Host_InitializeWorkflowRuntime(object sender, InitializeWorkflowRuntimeEventArgs e)
7: {
8: //
9: // It is important that you add your services to the ExternalDataExchangeService and not the
10: // runtime directly. The ExternalDataExchangeService handles interception of your events
11: // your service and ensures they get scheduled properly inside the WorkflowRuntime
12: //
13: ExternalDataExchangeService dataExchangeService = e.WorkflowRuntime.GetService<ExternalDataExchangeService>();
14: dataExchangeService.AddService(new OrderWorkflowService());
15: }
16:
17: /// <summary>
18: ///
19: /// </summary>
20: /// <param name="sender"></param>
21: /// <param name="e"></param>
22: void Host_ProcessStarted(object sender, ProcessEventArgs e)
23: {
24: //
25: // Grab the OrderDetails provided to the process
26: //
27: OrderDetails orderDetails = e.BusinessProcessContext.BusinessProcessRequest.Parameters["OrderDetails"] as OrderDetails;
28:
29: //
30: // This must be fired to initialize the correlation. WF requires either a method
31: // or and event on the [ExternalDataExchange] based service to initialize the correlation via the [CorrelationInitialization] attribute
32: //
33: OrderWorkflowService orderService = GetWorkflowService<OrderWorkflowService>();
34: orderService.FireEvent("OrderCreated", new OrderWorkflowServiceEventArgs(e.BusinessProcessContext.BusinessProcessId, orderDetails.OrderId));
35: }
Next go to your workflow and view code. At the top of your class add the following attribute.
1: [ExposedWorkflowService(typeof(IOrderWorkflowService), "OrderEngine")]
2: [BusinessProcessScheduledInitializer(typeof(ScheduledInitializer))]
3: [BusinessProcessHost(typeof(Host))]
4: public sealed partial class OrderProcessingProcess : StateMachineWorkflowActivity
5: {
6: private OrderDetails orderDetails;
The [ExposedWorkflowService(typeof(IOrderWorkflowService), "OrderEngine")] attribute tells our proxy engine to generate a typed class for you that exposes all the [ExposedAutoCorrelationEvent] on the IOrderWorkflowService externally and call it "OrderEngine".
If you add an Aware Reference from your client.
You will end up with a generated client that looks like the following. The guts of each fired routine is omitted for brevity. Notice the name of the typed class "OrderEngine" and the static routines on the OrderProcessingProcessClient.
1: public class OrderEngine : Gridgistics.Aware.BusinessProcesses.Clients.GeneratedBusinessProcessExposedServiceClient
2: {
3:
4: public OrderEngine(Gridgistics.Aware.Workflow.CorrelationValues correlationValues, Gridgistics.Aware.Workflow.CorrelationHostReference correlationHostReference) :
5: base(correlationValues, correlationHostReference)
6: {
7: }
8:
9: public void FireOrderCanceled()
10: {
11: ...
12: }
13:
14: public void FireOrderShipped()
15: {
16: ...
17: }
18:
19: public void FireOrderProcessed()
20: {
21: ...
22: }
23: }
And
1: public class OrderProcessingProcessClient : Gridgistics.Aware.BusinessProcesses.Clients.GeneratedBusinessProcessClientBase
2: {
3:
4: public static OrderEngine GetOrderEngine(System.Guid orderId)
5: {
6: return OrderProcessingProcessClient.GetOrderEngine(orderId, new Gridgistics.Aware.Services.ServiceAttributes());
7: }
8:
9: public static OrderEngine GetOrderEngine(System.Guid orderId, Gridgistics.Aware.Services.ServiceAttributes searchAttributes)
10: {
11: ...
12: }
13: }
To use this generated client you will use the following code.
1: using (OrderEngine orderEngine = OrderProcessingProcessClient.GetOrderEngine(detail.OrderId))
2: {
3: orderEngine.FireOrderProcessed();
4: }
No matter what server your WF instance is running on and no matter which version it is the events will be routed back to the right instance using the right assembly versions, etc. by simply providing us with your correlated value (order id in this case).
So as you can see we have done our best to take some of the sting out of using correlated workflow services inside WF and easily exposing these events externally to any .NET client.
Be the first to rate this post
- Currently 0/5 Stars.
- 1
- 2
- 3
- 4
- 5