Chapter 6. Using the Windows Communication Foundation and the Windows Workflow Foundation Together

<feature><title>In This Chapter</title> </feature>

Introduction

The Windows Communication Foundation and the Windows Workflow Foundation are naturally complementary to one another. The Windows Communication Foundation provides an elegant way to expose functionality as services, across an enormous number of different protocols and transports, while allowing the developer to focus on the message. Windows Workflow Foundation provides a way to rapidly compose application logic in a declarative fashion, allowing rapid expression of execution logic. Windows Workflow Foundation is a fantastic way to implement the services that the Windows Communication Foundation exposes. The compositional, activity-based model of development also provides a simple way to compose calls to Windows Communication Foundation services.

This chapter will focus on the basic problem of consuming Windows Communication Foundation services within Windows Workflow Foundation, as well as exposing processes implemented in Windows Workflow Foundation as Windows Communication Foundation services. There will also be a discussion of the built in activities designed to expose a workflow as an .asmx, WS-I Basic Profile 1.1 web service, and to consume those services as well.

Consuming Services

Readers of the previous chapter are aware of the fundamental building block of a workflow, the activity. Activities can perform any task that can be coded in their Execute() method, or, in the case of composite activities, execute other activities. As such, the delightfully simple answer to the question, “How do I consume the Windows Communication Foundation services discussed elsewhere in this book?” is, “Just as one would consume them from code, albeit wrapped inside of an activity.”

The Simple Case

There exists an even easier way to consume web services from within a workflow, but it has the limitation that the web service must be one that implements the WS-I Basic Profile 1.1 or .asmx web services. If one consumes an .asmx web service, or more generally, a web service that implements WS-I Basic Profile 1.1, the InvokeWebService activity can be used.

Dropping the InvokeWebService activity onto the workflow design surface invokes the familiar Add Web Reference dialog within Visual Studio as shown in Figure 6.1. Inside this dialog, one points to an existing web service, acquires the Web Service Definition Language, and generates a simple proxy class to call the service. Properties that correspond to the input parameters as well as the output parameter are then added to the activity as shown in Figure 6.2. These properties are dependency properties and can be bound to other activity properties or workflow properties.

The Add Web Reference dialog.

Figure 6.1. The Add Web Reference dialog.

The property grid for InvokeWebService after selecting a web service and method.

Figure 6.2. The property grid for InvokeWebService after selecting a web service and method.

The InvokeWebService activity also exposes a URL property that allows the workflow developer to point the activity to the correct address.

This works with a Windows Communication Foundation service exposed as a WS-I Basic Profile 1.1 web service, as well as exposing the metadata exchange (MEX) endpoint.

The General Case

The majority of services will not implement the WS-I Basic Profile 1.1. Rather, as evidenced by the rest of this book, they will use many different transports, with different types of security, across the endless spectrum of bindings. Fortunately, the model that serves Windows Communication Foundation developers well works equally as well when consumed within a service. Consider the contract in Listing 6.1, which describes a system to return the pricing of stock options.

Example 6.1. Simple Interface for WF Consumption of WCF

[ServiceContract(Namespace="http://wcfunleashed/optionsInWFandWCF")]
public interface IOption
{
    OptionListing[] GetOptionChain(string symbolName);
}

Listing 6.2 shows the definition of OptionListing.

Example 6.2. OptionListing Definition

[DataContract]
public class OptionListing
{
    string symbol;
    string optionSymbol;
    int expirationMonth;
    int expirationYear;
    double strikePrice;
    double optionPrice;
    int dailyVolume;

    [DataMember]
    public string Symbol
    {
        get { return symbol; }
        set { symbol = value; }
    }
    [DataMember]
    public string OptionSymbol
    {
        get { return optionSymbol; }
        set { optionSymbol = value; }
    }
    [DataMember]
    public int ExpirationMonth
    {
        get { return expirationMonth; }
        set { expirationMonth = value; }
    }
    [DataMember]
    public int ExpirationYear
    {
        get { return expirationYear; }
        set { expirationYear = value; }
    }
    [DataMember]
    public double StrikePrice
    {
        get { return strikePrice; }
        set { strikePrice = value; }
    }
    [DataMember]
    public double OptionPrice
    {
        get { return optionPrice; }
        set { optionPrice = value; }
    }
    [DataMember]
    public int DailyVolume
    {
        get { return dailyVolume; }
        set { dailyVolume = value; }
    }
}

This contains all the information one would like to retrieve about a specific option contract (apologies for the overloaded term). Calling GetOptionChain() and passing in a stock symbol, such as MSFT, retrieves all the options currently available.

A simple proxy is generated as well, in this case by hand in Listing 6.3, but the svcutil.exe tool could be used.

Example 6.3. Simple IOption Proxy

public class OptionServiceProxy: ClientBase<IOption>, IOption
{
    public OptionServiceProxy(string config) : base(config)
    {
    }
    public OptionListing[] GetOptionChain(string symbolName)
    {
        return this.Channel.GetOptionChain(symbolName);
    }
}

All that is left to do is to encapsulate this proxy call inside the Execute() method of an activity. Dependency properties are created to allow the parameters and results to be bound to other values in the workflow as seen in Listing 6.4.

Example 6.4. GetOptionChainActivity

public class GetOptionChainActivity: Activity
{
  public GetOptionChainActivity()
  {
    this.Name="GetOptionChainActivity";
  }

  public static DependencyProperty SymbolNameProperty =
   System.Workflow.ComponentModel.DependencyProperty.Register(
     "SymbolName",
     typeof(string),
     typeof(GetOptionChainActivity));


[Description("SymbolName")]
[Category("Input")]
[Browsable(true)]
[DesignerSerializationVisibility(
  DesignerSerializationVisibility.Visible)]
public string SymbolName
{
  get
  {
   return ((string)(base.GetValue(
     GetOptionChainActivity.SymbolNameProperty)));
  }

  set
  {
   base.SetValue(
     GetOptionChainActivity.SymbolNameProperty,
     value);
  }
}

public static DependencyProperty OptionListingsProperty =
  System.Workflow.ComponentModel.DependencyProperty.Register(
    "OptionListings",
     typeof(OptionsService.OptionListing[]),
     typeof(GetOptionChainActivity));

[Description("Option Listings")]
[Category("Output")]
[Browsable(true)]
[DesignerSerializationVisibility(
  DesignerSerializationVisibility.Visible)]
public OptionsService.OptionListing[] OptionListings
{
  get
  {
    return((OptionsService.OptionListing[])(
      base.GetValue(
         GetOptionChainActivity.OptionListingsProperty)));
   }

   set
   {
     base.SetValue(
       GetOptionChainActivity.OptionListingsProperty,
       value);
   }
  }

  protected override ActivityExecutionStatusExecute(
    ActivityExecutionContext executionContext)
  {
    using (OptionsService.OptionServiceProxy prox =
      new OptionsService.OptionServiceProxy("OptionsConfig"))
    {
      OptionListings = prox.GetOptionChain(SymbolName);
    }
    return ActivityExecutionStatus.Closed;
  }
}

The Execute() method wraps the call to the proxy (and ensures its proper disposal) and then returns a status of closed.

Activities can be quickly built to encapsulate and leverage Windows Communication Foundation services. The activity model gives the activity developer the ability to wrap a more advanced user interface around the configuration of the service parameters, as well as to provide validation of the activity, potentially allowing a “client-side” validation of the parameters during design time. The application will provide the configuration settings for the proxy as needed.

Orchestrating Services

The ability to invoke a single remote service is interesting, but it does not provide much of an advantage beyond simply using the proxy in code. The power of the declarative model begins to surface as one uses the workflow to orchestrate the calls to a number of different services. Imagine that every service one wants to consume is wrapped into an activity. It now becomes easy to create a composition of those services:

  • Arrange execution of the service calls in a parallel fashion, proceeding only after all service calls have been made

  • Logically determine which service to call using rules

  • Aggregate the results of a number of calls to similar services

  • Chain together a number of related service calls and potentially couple that with user interaction in the process

In a case where services are one-way (that is, a message is sent but no reply is required), a case for workload distribution is possible where the workflow is the coordinator of tasks performed by a farm of other services. There have been some interesting applications in high-performance computing of this approach. The workflow in this case is responsible for assigning tasks in discrete chunks of work to processing machines. By using a one-way messaging pattern, the workflow can assign the entire work up-front, throttle based on detected load of machines in the environment, handle task timeouts and work reassignment, as well as aggregate the results. This is an interesting application of Windows Workflow Foundation that will be investigated further in the future. The references section at the end of this chapter lists an article by Paventhan, Takeda, Cox, and Nicole on the topic of Windows Workflow Foundation and high-performance computing.

The reader is encouraged to see Hohpe, Woolf for a more thorough discussion of patterns that relate to service composition in enterprise integration patterns, especially the message-routing patterns.

Exposing Workflows as Services

If workflows are used to compose services, it becomes quite useful to in turn expose those workflows as services for others to consume or to compose. Just as a simple set of activities can be composed into more powerful activities, so too can services. The experience around exposing a workflow as a service involves more work than consuming an already-existing service. What follows are some guidelines to make that a little easier.

Ultimately, the pattern of hosting a workflow as a service is really no different than hosting the workflow as a process within another application. In this case, the host is responsible not just for creating workflows, routing messages to the workflow, and monitoring their execution status, but also for handling the exposure as a service. In this way, the host application and its service implementation become a thin façade, receiving messages from the service calls and in turn routing them to the workflow.

The HandleExternalEvent activity ends up sitting directly behind each of the service calls specified by the interface. When the host receives a message for that service call, it sends a message to the (hopefully) awaiting HandleExternalEvent activity in the form of the expected event type. This pattern gives an additional layer of abstraction where the host layer can perform some additional tasks, such as looking up the workflow identifier based on some of the contents of the inbound message.

Publishing as a Web Service

As in the case of service consumption, there is support in the out of the box activity set to expose a workflow as an .asmx-based web service. The only requirement to do this is a contract describing the methods exposed by the service. The WebServiceInput and WebServiceOutput activities can then be used to receive the input, process the message in the form of a workflow, and return the output.

The following steps will expose a workflow as a web service:

  1. Craft an interface defining the methods to be called.

  2. Create a new Sequential Workflow Library project type.

  3. Make sure that the new project can reference the interface created.

  4. Drag a WebServiceInput activity to the workflow and set the IsActivating property to true.

  5. Specify the InterfaceType (as seen in Figure 6.3) and MethodName; notice that the property grid now displays the inputs to the web service (Figure 6.4).

    Interface Selection dialog.

    Figure 6.3. Interface Selection dialog.

    The property grid of WebServiceInput.

    Figure 6.4. The property grid of WebServiceInput.

  6. Create the remainder of the workflow, which is able to reference the input parameters through binding.

  7. Place a WebServiceOutput activity in the process and set the InputActivityName to the corresponding WebServiceInput activity.

  8. Notice the (ReturnValue) property (in Figure 6.5). This value has to be set in the workflow.

    The property grid of WebServiceOutput.

    Figure 6.5. The property grid of WebServiceOutput.

  9. Right-click on the workflow project file in the Solution Explorer and select Publish as Web Service (in Figure 6.6).

    Context menu to publish as web service.

    Figure 6.6. Context menu to publish as web service.

Selecting Publish as Web Service will not result in one being asked any questions. It will insert an ASP.NET Web Service project into the solution with a web.config file and an .asmx file. The .asmx file is very simple and points to a class defined in an included assembly. This does not provide any room for customization, including specifying a namespace that is not tempuri.org. This occurs because the file generated to create the assembly is deleted as part of the Publish as Web Service process. At the time of writing, there is discussion of publishing a tool that would allow customization of the .cs file that generates the assembly easier. By configuring a persistence service, the workflow will be able to persist to the persistence store and be picked up whenever the next call gets made. In a load-balanced setting in which multiple application servers expose the same workflow as a web service, this negates the need for providing “sticky sessions,” where there is an enforced affinity to a given server.

The WebServiceInput and WebServiceOutput activities can be used in more complex scenarios than the one described earlier. By chaining a series of these activities, the interactions with the various steps in a process can be modeled and exposed as a web service. In an ordering process, there could be a variety of steps that have to occur:

  • Place order

  • Confirm items in stock

  • Ship the order

At each stage of the process, the workflow relies on various third-party systems notifying the workflow that each step has been completed. This process can be modeled with a series of web service inputs and outputs. In this way, not only can the details of a service be specified on a method level, the modeling specifies the valid order in which the methods may be called. This can give a system the capability to focus on modeling the process and enforce the desired method call semantics external to the process.

The problem becomes slightly more difficult because something is needed to route subsequent messages back to the proper workflow instance. In the case of the WebServiceInput and WebServiceOutput activities, provided the client supports cookies to maintain some session information inside the caller’s knowledge, subsequent calls to the web service can use the cookie that contains the workflow instance identifier and other information needed to successfully route the message back into the executing workflow. This cookie can be saved and used by other clients to send later messages to the workflow instance.

Hosting Inside a WCF Service

The approach just described is a useful approach to quickly generate a web service environment to host the workflow runtime and expose the functionality of a workflow as a web service. But for a number of reasons very dear to readers of this book, WS-I Basic Profile 1.1 or .asmx web services do not provide the right way to expose services. The model described earlier is a very nice, contract-first way to model a process and expose it as a web service, but there is no way to specify the use of a TCP, socket-based transport with message-level encryption. The ideal model would be to keep the same, contract-first approach just described, but to decouple it by not exposing the workflow via an .asmx web service.

To do this, the pattern described in the introduction to this section must be used. This approach calls for a very thin hosting layer acting as a façade to route messages received by a service call to the waiting workflow. There are two primary issues: how messages are routed into the workflow and where the runtime is hosted.

Message Routing

Putting aside the discussion of where to host the runtime, the issue of how to route incoming messages to the workflow is the area where the most consideration needs to be paid in terms of workflow design.

The simple case is the first call, which needs to initialize the workflow and commence its execution. Here the workflow host receives a message, extracts the relevant parameters, gets the runtime, and initiates the workflow. There are two patterns for making sure that the caller will know the workflow instance identifier created. The first approach is to return the workflow instance identifier to the caller after calling CreateWorkflow(). In a situation using one-way messaging, the caller can submit a Guid as one of the service parameters and use the overload of CreateWorkflow(), where the last parameter sets the WorkflowInstanceId rather than allowing it to be set by the workflow runtime. Care must be given that a workflow instance cannot be created with the same identifier. In this case, it is not the best idea to use the order identifier because of the potential that the workflow might get started again for the same order, causing a conflict. The code sample in Listing 6.5 shows how to use the overload to pass in the workflow identifier.

Example 6.5. Initiating a Workflow

public void PlaceOrder(Order order)
{
    if (Guid.Empty.Equals(order.WorkflowID))
    {
        order.WorkflowID = Guid.NewGuid();
    }
    Dictionary<string, object> paramDict = new Dictionary<string, object>();
    paramDict.Add("IncomingOrder", order);
    WorkflowInstance instance = workflowRuntime.CreateWorkflow (typeof(ProcessOrder),
Initiating a Workflow paramDict, order.WorkflowID);
    instance.Start();
}

Subsequent method calls will need to contain enough information to route the message to the workflow. One way to accomplish this is to include the workflow instance identifier as a parameter on the method. This allows the event to be created directly from the submitted workflow instance identifier. This does add additional information to the messages that need to be sent, and in some cases, one may not have control over the structure of the message. Additional work may also have to be completed prior to raising the message to the workflow. A common scenario is one in which properties of the message arriving on the service are used to query a database to determine the workflow instance identifier. This commonly occurs because the system sending the message to the service has no idea that a workflow is executing behind this service, and has no reason to be aware of a workflow identifier. The order delivery system might simply drop a message onto a Microsoft Message Queuing (MSMQ) queue that contains the order number and the delivery time. It is the responsibility of the service to translate this into an event that the workflow runtime can use by correlating the facts of the message with the workflow instance identifier.

One design decision is the nature of the methods themselves. Does the method need to send a message to the workflow and wait for some signal from the workflow before returning a response to the client? Does the method need to simply raise the event and return a void or limited response to the client? The decision made here will influence the design of how a message is routed into the workflow.

The case in which the method needs to raise the event and return to the client without a response from the workflow is the easier of these two scenarios to code. In this case, the method implementation obtains the workflow runtime and raises the event. This is not very different from the pattern seen in the HandleExternalEvent discussion in the last chapter. To refresh, the HandleExternalEvent activity is configured to wait for an event handler (defined in the interface) to receive an event. It is the responsibility of the host to create this event and raise it into the workflow. In Listing 6.6, there is a reference to the local service added to the ExternalDataExchangeService that is used to raise events into the workflow itself. This is used inside the method to easily raise the message to the workflow.

Example 6.6. Raising an Event and Returning

public void OrderDelivered(Order order)
{
    orderLocalService.RaiseDelivered(order.WorkflowID, order.DeliveryTime);
}

This is the easiest approach, but there are scenarios in which one wants to wait for the workflow to output some response to the submitted event, such as a partial result based on the processing or the result of a query that occurs at that stage of the workflow. If the result has to wait for the workflow to be idle, that is, waiting for the next event, the ManualWorkflowSchedulerService provides a clean way to implement this. As discussed in the previous chapter, the ManualWorkflowSchedulerService executes the workflow on the thread of the caller as opposed to on a new thread. Therefore, the call to the workflow will block as the workflow executes. On reaching an idle point, control will be yielded back to the caller; in this case, the service. At this point, any additional work can be done, including querying the tracking store for information specific to the workflow to return.

Example 6.7. Using ManualWorkflowSchedulerService to Wait to Return a Value

public bool OrderProcess(Order order)
{
    ManualWorkflowSchedulerService schedulerService =
    workflowRuntime.GetService<ManualWorkflowSchedulerService>();
    orderLocalService.RaiseProcessing(order.WorkflowID);
    schedulerService.RunWorkflow(order.WorkflowID);
    // RunWorkflow completes when the workflow completes or goes idle
    return true;
}

In the preceding code, the RaiseProcessing() method simply raises a processing event into the workflow runtime for the workflow instance to pick up. Note that while using the ManualWorkflowSchedulerService, raising an event into the workflow will not cause it to execute. Because it relies on being given a thread to execute on, it will not process the event until the RunWorkflow() method is called.

A special case of this pattern would be when the workflow is expected to run until completion. In the WorkflowCompleted event, one of the properties of the event arguments passed are output parameters of the workflow. It might be desirable to access these parameters and return them as part of the last service call. The WorkflowCompleted event handler will be executed as the last part of the RunWorkflow() method if the workflow does in fact complete. This could be used to place the output parameters into member variables that the service method could then access to perform meaningful work, such as returning the completed order information or the detailed history of a document approval process.

If one is not using the ManualWorkflowSchedulerService, in order to get the behavior described earlier, the service method would need to wait to return. The workflow, which is executing on a separate thread, will do its work and send some signal back to the service method that it is safe to continue, either through an event like WorkflowIdled or by using the CallExternalMethod activity. This approach, although more complicated and involves coordinating multiple threads, affords a greater flexibility within the service method. Using the ManualWorkflowSchedulerService puts the service at the mercy of the workflow. If the implementation changes and suddenly a long-running activity is placed in the workflow, there is no recourse, no way to send a response to the service method, except to wait for the workflow to go idle. In the case where the service method is waiting to be signaled to continue, there are other behaviors that could be programmed, such as a timeout after some number of seconds, at which point a “Work Pending” message could be sent back as the response. Because the workflow instance is executing on a separate thread, there is no harm in returning a response and freeing up the thread the service method was executing on.

Runtime Hosting Options

Prior to this section, it has been assumed that a reference to the WorkflowRuntime itself will be available as these service methods execute. Furthermore, the same WorkflowRuntime needs to be made available; otherwise multiple runtimes are being used inside the same application domain, consuming resources and introducing additional, unneeded overhead. Even if overhead was a complete non-issue, additional management would be needed to prevent error conditions where two different runtimes were trying to send messages to the same workflow. In the case of a single runtime, the messages will be delivered in the order they are received by the runtime. In the case of multiple runtimes, the second message could be delivered only if the workflow had processed the first message, gone idle waiting for the second, and persisted to the persistence store. Otherwise, the second runtime will try to deliver the event and discover that the first runtime still has the workflow instance locked.

The singleton pattern is the simplest and best way to solve the problem of ensuring that there is only one runtime handling requests per instance of a service. The conversation quickly becomes one of the instancing behavior of the ServiceModel. There is a very simple model for enforcing singleton behavior on the service itself by applying the ServiceBehavior attribute to the service:

Example 6.8. Specifying a Singleton Service

[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode =
Specifying a Singleton Service ConcurrencyMode.Multiple)]

The WorkflowRuntime can then be added as a private member of the class, and can be accessed knowing the same instance will always be accessed. That said, ConcurrencyMode.Multiple allows multiple threads to call the service object at the same time, so thread safety and concurrency are left to the developer. In the scenario discussed here, most operations with the workflow runtime are communications based, the underlying representations of which are internal queues to deliver the messages. Internally, the operations accessing those queues are thread-safe, minimizing the need for the developer to focus on concurrency issues involving the shared workflow runtime. If additional granularity is required to ensure thread safety—for instance to ensure that two events raised without any potential interruption by another thread—then typical concurrency constructs can be used to marshal access to the runtime. Listing 6.9 is an example of an entire service implementation.

Example 6.9. Singleton Service with Shared Workflow Runtime

[ServiceBehavior(
  InstanceContextMode = InstanceContextMode.Single,
  ConcurrencyMode = ConcurrencyMode.Multiple)]
public class OrderService : IOrderService
{
  private WorkflowRuntime workflowRuntime;
  private OrderLocalService orderLocalService
    = new OrderLocalService();
  public OrderService()
  {
    if (null == workflowRuntime)
    {
      workflowRuntime = new WorkflowRuntime();
      ExternalDataExchangeService dataService =
        new ExternalDataExchangeService();
      workflowRuntime.AddService(dataService);
      dataService.AddService(orderLocalService);
      dataService.AddService(cdnws);
      workflowRuntime.WorkflowCompleted +=
        new EventHandler<WorkflowCompletedEventArgs>(
          workflowRuntime_WorkflowCompleted);
      workflowRuntime.WorkflowTerminated +=
        new EventHandler<WorkflowTerminatedEventArgs>(
          workflowRuntime_WorkflowTerminated);
      workflowRuntime.WorkflowAborted +=
        new EventHandler<WorkflowEventArgs>(
        workflowRuntime_WorkflowAborted);
    }
  }

  void workflowRuntime_WorkflowAborted(
    object sender,
    WorkflowEventArgs e)
  {
    throw //handle aborted event
  }

  void workflowRuntime_WorkflowTerminated(
    object sender,
    WorkflowTerminatedEventArgs e)
  {
    //handle terminated event
  }

  void workflowRuntime_WorkflowCompleted(
    object sender,
    WorkflowCompletedEventArgs e)
  {
    Console.WriteLine(
      "Workflow {0} of type {1} has completed",
      e.WorkflowInstance.InstanceId,
      e.WorkflowDefinition.QualifiedName);
  }


  public void PlaceOrder(Order order)
  {
    if (Guid.Empty.Equals(order.WorkflowID))
    {
      order.WorkflowID = Guid.NewGuid();
    }
    Dictionary<string, object> paramDict =
      new Dictionary<string, object>();
    paramDict.Add("IncomingOrder", order);
    WorkflowInstance instance =
      workflowRuntime.CreateWorkflow(
        typeof(ProcessOrder),
        paramDict,
        order.WorkflowID);
    instance.Start();
  }

  public void OrderDelivered(Order order)
  {
    dnwls.RaiseDelivered(order.WorkflowID, "Order Delivered");
  }
}

In cases in which the service itself cannot be declared as a singleton, other methods are required to encapsulate the workflow runtime as a singleton for the Windows Communication Foundation service methods to access.

The previous paragraphs focused on how one can access the WorkflowRuntime to create and interact with workflows. The larger question of where to host the Windows Communication Foundation service itself is outside the scope of this chapter. However, the use of workflow as the implementation of the logic of the service does not restrict where the service can be hosted.

Looking Ahead

This chapter discussed ways to incorporate the synergy between Windows Communication Foundation and Windows Workflow Foundation. At the time of this writing, the Windows Communication Foundation and Windows Workflow Foundation teams have joined together. This team is responsible for developing the future versions of this technology, and one of the highest priority items for the next release is the integration of the two technologies. Many of the techniques discussed in this chapter will be abstracted into activities and into the runtime to further enhance the productivity of service and workflow developers.

This is not to discourage developers from using the two technologies together today. The patterns shown in this chapter can be used today to both consume services from workflow, as well as to host workflows as services. Indeed, this is the focus of many of the customer engagements the authors have been involved in to date.

The next release of Visual Studio, codenamed “Orcas,” will contain enhancements to Windows Workflow Foundation and Windows Communication to allow developers to more easily create workflow-enabled services. These are Windows Communication Foundation services that have an underlying implementation written using Windows Workflow Foundation. This allows developers to take advantage of the infrastructure present in Windows Workflow Foundation, such as persistence, tracking, and a declarative process model, while maintaining the flexibility in service endpoints and other advantages Windows Communication Foundation offers.

The “plumbing” to create workflow-enabled service consists of the same elements discussed earlier in this chapter—a set of activities to send and receive messages, and an environment to host these workflows and take care of issues such as instancing and message routing. The remainder of this chapter will focus on what is currently planned for inclusion in the Visual Studio “Orcas” release. The screenshots and code samples are based on the March community technology preview (CTP) and are subject to change before the final version is released.

The two activities related to creating workflow-enabled services are the Send and Receive activities. The Send activity is used to consume another service, and the Receive activity is used to either instantiate a workflow when a message arrives, or to wait at some point in the workflow for a message to arrive. To be more precise, the Send activity sends a request message and then receives a response message (in the case of a one-way contract, it will simply send the request), and the Receive activity receives a request and then sends a response back to the caller. Both activities require the developer to select the contract and the operation being consumed or implemented. Based on the details of the contract being implemented, dependency properties will be added that correspond to the parameters and the return value (if applicable).

The Send activity requires two pieces of information: the operation to which it will send a message, and the name of the endpoint configuration that should be used. After dropping the Send activity onto the design surface, as seen in Figure 6.7, it can be double-clicked to bring up a dialog to select the operation, as in Figure 6.8.

A sequential workflow containing the Send activity.

Figure 6.7. A sequential workflow containing the Send activity.

Dialog to select the contract and operation.

Figure 6.8. Dialog to select the contract and operation.

After the operation is selected, dependency properties are added and can be bound to values elsewhere in the workflow, or a property can be promoted. The final configuration for the Send activity is to select the endpoint used. This will need to be an endpoint named in the configuration file or set programmatically. The completed properties pane is seen in Figure 6.9. The ReturnValue parameter is a strongly typed dependency property that corresponds to the type and value returned by the service.

Send activity completed property pane.

Figure 6.9. Send activity completed property pane.

The workflow will be created in the typical fashion; by creating a workflow runtime and then calling CreateWorkflow passing in the type of the workflow to create. The application configuration file can then be used to store the endpoint details. A sample configuration file for this is in Listing 6.10.

Example 6.10. Configuration Information for Send Activity

<configuration>
  <system.serviceModel>
    <client>
      <endpoint
          address="http://localhost:8998/Register/RegistrationService"
          binding="wsHttpContextBinding"
          contract="RegistrationContracts.IRegistration"
          name="httpBinding" />
    </client>
  </system.serviceModel>
</configuration>

The only detail contained in this endpoint definition unfamiliar to Windows Communication Foundation developers is the binding value of wsHttpContextBinding. This is a binding that includes a context channel that is responsible for managing the context information of the workflow. This is the mechanism that allows the runtime to properly route messages to the correct workflow instances. At the most basic level, the context being transmitted includes the workflow instance identifier. In scenarios in which the workflow instance identifier and the operation are not enough to route the message to the correct Receive activity, such as a parallel set of Receive activities, the context will need to contain additional information to route that message.

The other binding currently included is the netTcpContextBinding, which uses the TCP transport to exchange the context information. In the case in which the wsHttpContextBinding and the netTcpContextBinding are not appropriate, one can create a custom binding by mixing in the context channel into the custom binding definition.

The Receive activity requires only one piece of information to configure—the operation it implements. Similar to the Send activity, double-clicking displays a dialog that allows one to choose the operation. In addition to importing a contract, this dialog also allows a developer to define the contract and the details of the operation. This dialog is seen in Figure 6.10. The contract definition tool allows one to define the input parameters and types, as well as the return value.

Dialog to select or define the contract and operation.

Figure 6.10. Dialog to select or define the contract and operation.

After the operation is selected, dependency properties are added to the activity so one can promote the properties to member variables or to bind to another value in the workflow. As seen in Figure 6.11, the Receive activity is a composite activity. The implementation details of the operation are expressed as the activities arranged within the Receive activity. The developer will want to create the return value within these activities. In Figure 6.11, a Code activity is used to create the ReturnValue. The property pane for the Receive activity is displayed in Figure 6.12.

The Receive activity in a sequential workflow.

Figure 6.11. The Receive activity in a sequential workflow.

The property pane for a Receive activity.

Figure 6.12. The property pane for a Receive activity.

The CanCreateInstance property indicates whether a workflow should be instantiated when a message is received without a context for that operation. If a message arrives with a context, the host will route that message to the existing instance as specified in the context. The host will inspect the workflow being hosted, and will look for activities with CanCreateInstance set to true to know when a workflow should be created. Typically, the first Receive activity in a workflow will have this set to true.

The other aspect of hosting a workflow-enabled service is the host itself. As discussed earlier in this chapter, there is a fair amount of “plumbing” within the host required to instantiate and route messages to workflows. This functionality is found within the WorkflowServiceHost, a sub-class of the ServiceHost used to create host a Windows Communication Foundation service. The WorkflowServiceHost can create a host based on a type passed in, or a string of XAML containing the workflow definition. The code in Listing 6.11 shows a simple WorkflowServiceHost.

Example 6.11. A Simple WorkflowServiceHost

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Workflow.Runtime;
using System.Workflow.Runtime.Hosting;
using System.ServiceModel;
namespace WCFUnleashed
{
    class Program
    {
        static void Main(string[] args)
        {
            WorkflowServiceHost wsh =
                 new WorkflowServiceHost(typeof(Workflow1));
            wsh.Open();
            Console.WriteLine("Press <Enter> to quit the application");
            Console.ReadLine();
            wsh.Close();
        }
    }
}

This is very similar to the simple hosts of a Windows Communication Foundation service. The implementation has been created within the workflow type passed into the constructor. The configuration is needed to match the mechanics of the messaging with the Receive activities contained within the workflow. The configuration file listed in Listing 6.12 shows the configuration required.

Example 6.12. WorkflowServiceHost Configuration

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="WCFUnleashed.Workflow1"
                       behaviorConfiguration="WorkflowServiceBehavior" >
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8998/
                       WCFUnleashed/Workflow1.svc" />
          </baseAddresses>
        </host>
        <endpoint address="ContextOverTcp"
                binding="wsHttpContextBinding"
                contract="WCFUnleashed.IRegistration" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="WorkflowServiceBehavior"  >
         <serviceMetadata httpGetEnabled="true"
                        httpGetUrl="http://localhost:8999/myMetaData" />
         <serviceDebug includeExceptionDetailInFaults="true" />
         <workflowRuntime name="WorkflowServiceHostRuntime"
                          validateOnCreate="true"
                          enablePerformanceCounters="true">
           <services>
             <add
               type="System.Workflow.Runtime.Hosting.
                       SqlWorkflowPersistenceService, System.Workflow.Runtime,
                       Version=3.0.00000.0, Culture=neutral,
                       PublicKeyToken=31bf3856ad364e35"
               connectionString="Data Source=localhostsqlexpress;
                       Initial Catalog=NetFx35Samples_ServiceStore;
                       Integrated Security=True;Pooling=False"
               LoadIntervalSeconds="1" UnLoadOnIdle="true"  />
           </services>
         </workflowRuntime>
       </behavior>
     </serviceBehaviors>
   </behaviors>
  </system.serviceModel>
</configuration>

This configuration file creates an endpoint using the wsHttpContextBinding and defines the WorkflowServiceBehavior. Within the WorkflowServiceBehavior definition, one could add and configure additional runtime services. The WorkflowServiceHost also exposes a public property, WorkflowRuntime, which allows one to access the workflow runtime programmatically. Here the host could subscribe to the events raised by the runtime or interact directly with the workflow runtime.

The model in Visual Studio codename “Orcas” simplifies the work described earlier in this chapter by providing the WorkflowServiceHost and the Send and Receive activities, which allow one to work with services. This approach makes Windows Workflow Foundation a natural fit to both consume and implement services using Windows Communication Foundation. The capability for Windows Communication Foundation to interoperate with services implemented in other technologies means that Windows Workflow Foundation can be used by any technology that communicates with Windows Communication Foundation. This further expands the set of scenarios in which Windows Workflow Foundation can deliver value by allowing developers to focus on the details of the process and leave the rest (persistence, tracking, instancing, communications) to configuration.

References

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset