Customized Hosting

Intertech Tutorials

Customizing the Hosting Process

The Main() method of a WF host will typically do the following:

When you are hosting workflows within other types of applications, such as a GUI application, you will take a similar approach. Unlike a console-based workflow application, however, you will need to author the code by hand.

Regardless of which type of application is hosting your workflows, you may need to customize the hosting logic even further. For example, you may need to do the following:

Later chapters in this class will address core services and custom local services. However, the next several pages will examine how to build a reusable class type that encapsulates the details of getting the WF runtime up and running. This reusable class could be bundled into a .NET assembly in a generalized fashion for use across multiple applications.

The first step to building a reusable WF engine wrapper is to implement the IDisposable interface on the wrapper class itself. Recall that supporting this interface allows the caller to wrap the object within a using construct to ensure proper cleanup of internal resources. The Dispose() method will be in charge of tearing down the internal WorkflowRuntime object.

If you would like to implement a prim and proper disposable pattern, you will also need to account for garbage collection logic. When set up as shown, your internal resources are cleaned up correctly, even if the caller forgets to call Dispose(). If the caller does call Dispose(), you inform the CLR garbage collector to disregard finalization for the object.


// C#
class WorkflowRuntimeWrapper : IDisposable
{
// Member to represent the workflow runtime.
private WorkflowRuntime wfRuntime = new WorkflowRuntime();

// To detect redundant calls.
private bool disposedValue = false;

protected void Dispose(bool disposing)
{
if (!this.disposedValue)
{
 if (disposing)
 {
   // TODO: Dispose any managed objects.
   wfRuntime.StopRuntime();
   wfRuntime.Dispose();
 }

 // TODO: Free your own state (unmanaged objects).
 // TODO: Set large fields to null.
}
this.disposedValue = true;
}

void IDisposable.Dispose()
{
// Do not change this code.
// Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(true);
GC.SuppressFinalize(this);
}
}



' VB
Class WorkflowRuntimeWrapper
Implements IDisposable
' Member to represent the workflow runtime.
Private wfRuntime As New WorkflowRuntime()

' To detect redundant calls.
Private disposedValue As Boolean = False

Protected Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
 If disposing Then
   ' TODO: Dispose any managed objects.
   wfRuntime.StopRuntime()
   wfRuntime.Dispose()
 End If
 ' TODO: Free your own state (unmanaged objects).
 ' TODO: Set large fields to Nothing.
End If
Me.disposedValue = True
End Sub

Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code.
' Put cleanup code in Dispose(ByVal disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
End Class

Moreover, the wrapper class could define a public helper method to enable the caller to pass in type information or any necessary arguments. You can also embed any threading logic you may need to account for (including none whatsoever). Notice how the following wrapper class is basically providing the same services as the Main() method found within a console-based sequential workflow application. To streamline event handling logic, make use of the C# lambda operator (=>).


// C#
class WorkflowRuntimeWrapper : IDisposable
{
// Members to represent the workflow agents.
private WorkflowRuntime wfRuntime = new WorkflowRuntime();
private AutoResetEvent wait = new AutoResetEvent(false);

public WorkflowRuntimeWrapper() { InitRuntime(); }

// Private helper f(x) to hook into events.
private void InitRuntime()
{
// Handle events with C# lambda expression syntax.
wfRuntime.WorkflowCompleted += (sender, e) => { wait.Set();};

wfRuntime.WorkflowTerminated += (sender, e) =>
{
 Console.WriteLine("***** Error! *****");
 Console.WriteLine(e.Exception.Message);
 wait.Set();
};
}
// To allow caller to pass in args / WF type.
public void StartWorkflowInstance(Type wfType,
Dictionary<string, object> wfArgs)
{
WorkflowInstance wfInst = wfRuntime.CreateWorkflow
 (wfType, wfArgs);
wfInst.Start();
wait.WaitOne();
}
...
}

The VB wrapper class would be very similar. Although VB does have a lambda operator (via the Function keyword), VB lambdas can only process parameters with a single code statement, and the statement must have a return value. Therefore, you will use AddHandler statements to hook into the workflow events.


' VB
Class WorkflowRuntimeWrapper
Implements IDisposable

' Members to represent the workflow agents.
Private wfRuntime As New WorkflowRuntime()
Private wait As New AutoResetEvent(False)
Public Sub New()
InitRuntime()
End Sub

' Private helper f(x) to hook into events.
Private Sub InitRuntime()
AddHandler wfRuntime.WorkflowCompleted, AddressOf WFCompleted
AddHandler wfRuntime.WorkflowTerminated, AddressOf WFTerminated
End Sub

' To allow caller to pass in args / WF type.
Public Sub StartWorkflowInstance(ByVal wfType As Type, _
ByVal wfArgs As Dictionary(Of String, Object))
Dim wfInst As WorkflowInstance = _
wfRuntime.CreateWorkflow(wfType, wfArgs)
wfInst.Start()
wait.WaitOne()
End Sub

Private Sub WFCompleted(ByVal sender As Object, _
ByVal e As WorkflowCompletedEventArgs)
wait.Set()
End Sub

Private Sub WFTerminated(ByVal sender As Object, _
ByVal e As WorkflowTerminatedEventArgs)
Console.WriteLine("***** Error! *****")
Console.WriteLine(e.Exception.Message)
wait.Set()
End Sub
End Class

At this point, the hosting application could make use of your WF runtime wrapper class as follows. Recall the using syntax ensures the object’s Dispose() method is called when it drops out of scope.


// C#
class Program
{
static void Main(string[] args)
{
using (WorkflowRuntimeWrapper wfRuntime =
new WorkflowRuntimeWrapper())
{
wfRuntime.StartWorkflowInstance(typeof(MyWorkflow), null);
}
}
}



' VB
Module Program
Sub Main()
Using wfRuntime As New WorkflowRuntimeWrapper()
wfRuntime.StartWorkflowInstance(GetType(MyWorkflow), Nothing)
End Using
End Sub
End Module

A WF runtime wrapper could support a good deal of additional infrastructure beyond what you see here. It could support additional events, core or local service registration, and more. When you are building production-level, WF-enabled applicaions, you will certainly want to build a customized runtime wrapper for use in your projects.


Copyright (c) 2008-2013. Intertech, Inc. All Rights Reserved. This information is to be used exclusively as an online learning aid. Any attempts to copy, reproduce, or use for training is strictly prohibited.