Sunday, September 03, 2006 10:06 AM bart

WF - Exposing a workflow through a web service

Introduction

Previously in my WF blog series I've been talking about the WF basics as well as dynamic updates to workflow instances at runtime. These posts had one thing in common: all of the demo applications were hosted in a simple console application, that was for the lion's part generated by the Visual Studio 2005 WF Extension. As you probably know by now, WF is about an in-process workflow engine where hosting the WF runtime is required to get it to work. In this post, you'll see how to host WF in ASP.NET 2.0 and expose it through a web service.

A simple workflow library

Creating the project

In the previous posts, we started coding by creating a Workflow-enabled console application. This time however, we need to create a library that defines a workflow. Later on, this workflow definition will be used by a web service in an ASP.NET website. So, open Visual Studio 2005 and create a new Sequential Workflow Library project entitled "WorkflowViaWS":

In order to concentrate on the real topic of this post, i.e. web service hosting of workflows, we'll create a trivial workflow: a divider. So, rename Workflow1.cs to Divider.cs first.

A webservice 'contract'

Now, when talking about web services one word should pop up in your brain almost instantaneously: the contract. Although we're not yet working with WCF over here, WF also has kind of a contract notion. Basically, we need to create an interface that will act as the interface to the workflow and hence the workflow that encapsulates our workflow. Add a new interface to the project called IDivider:

namespace WorkflowViaWS
{
   interface
IDivider
  
{
      double Divide(double a, double
b);
   }
}

Accepting input == WebServiceInputActivity

Now we're ready to define the workflow itself. In order to accept input from the web service, we need to use a WebServiceInputActivity from the Toolbox.

When you drag and drop this activity to the workflow's designer surface, the designer will tell you to set the IsActivating property of the activity to true. This is required to tell the system that this web service input actually activates the (new) workflow instance. Think about this for a while ... you can indeed have more than one WebServiceInputActivity in your workflow: welcome to the world of stateful webservices and state hydration/dehydration. This falls outside the scope of this post however, but stay tuned to discover this too!

Next, the designer will ask you to set the InterfaceType property. This is where IDivider enters the scene. Setting the property couldn't be a simpler job thanks to the dialog support:

Next you'll have to set the MethodName with the assistance of a dropdown list in the Properties pane. Needless to say we'll choose the only method available: Divide.

Now pay attention to the Properties pane: a set of Parameter fields will come out of the blue:

Setting these fields is quite easy again, but there's some work to do first: adding properties to the Divider workflow class:

private double a;

public double
A
{
   get { return
a; }
   set { a = value
; }
}

private double
b;

public double
B
{
   get { return
b; }
   set { b = value
; }
}

Tip: use the prop code snippet in Visual Studio 2005 as shown below (type 'prop' without quotes and press TAB).

These properties will act as inputs to the workflow, just like we did in the previous posts with our console-hosted workflows and the arguments dictionary passed to CreateWorkflow. This is how the properties would be used in a console-based application (or another non-WS hosting environment):

Dictionary<string, object> arguments = new Dictionary<string, object>();
arguments.Add("A",
10.0);
arguments.Add("B"2.0);

WorkflowInstance
instance = workflowRuntime.CreateWorkflow(typeof(WorkflowViaWS.Divider), arguments);

Binding a parameter to a property is assisted by the following dialog that shows all of the valid candidates for binding. Bind parameter 'a' to property 'A' and parameter 'b' to property 'B':

Now we have completed all the required properties of the WebServiceInputActivity, the system still complains about something else...

What goes in must go out - WebServiceOutputActivity

A web service hosted workflow won't do much if it couldn't produce results (so, there's no built-in concept of "one-way web methods" available, although you can think of workarounds for that).

Therefore, add a WebServiceOutputActivity activity to the designer (of course below the WebServiceInputActivity) and set its InputActivityName to webServiceInput1, the name of the corresponding input activity (note: I've been lazy with the acitivy naming in here, but since we only have one input and one output activity this doesn't matter much - btw, you're absolutely right to discard this excuse).

Next, we have to set the (ReturnValue) property of the output activity. Again, we'll add a property to the workflow class:

private double result;

public double Result
{
   get { return result; }
   set { result = value; }
}

Selecting the return value is done using exactly the same dialog as we used to set the input parameter bindings. The result should look like:

Division by zero? Fault! - Some additional logic

In Kindergarten (or a bit later) everyone learned that dividing by zero isn't possible. The .NET Framework however takes a more mathematical approach and the result of dividing some double value by zero will be NAN (not-a-number). Let's take the former approach which gives us the opportunity to add some additional logic to the workflow and illustrate the WebServiceFaultActivity.

Perform the following jobs:

  1. Drag and drop an IfElseActivity to the designer surface in between the input and output activities. Rename it to divisionByZeroCheck.
  2. As you can see the IfElseActivity has two branches. Select the left branch and rename it to isZero. Then select the right branch and rename it to nonZero.
  3. The designer now complains on the left (isZero) branch and asks to set the Condition property.
    • In the Properties pane, select Declarative Rule Condition.
    • Next, expand the Condition property and set the ConditionName to CheckForZero.
    • Now click the ellipsis (...) to create a new rule:


    • The result should look like:


    • Tip: Take a look at the Divider.rules file in the Solution Explorer. This file contains the declarative rule. The reason I've chosen for a "declarative rule condition" is to show you this feature (and the underlying XML); however, in this situation a "code condition" would be better because the condition won't ever change. Declarative rule conditions and code conditions both have pros and cons, but this falls outside the scope of this post.
  4. Drag and drop a WebServiceFaultActivity into the left (isZero) branch.
    • Set its InputActivityName to webServiceInput1.
    • Next, you'll have to set the Fault property. In order to do this, first switch to the code view and add the following:

      private ArgumentException divisionByZero = new ArgumentException("Division by zero.");

      public ArgumentException
      DivisionByZero
      {
         get { return
      divisionByZero; }
         set { divisionByZero = value
      ; }
      }

    • Now you can set the Fault property to DivisionByZero.

  5. In the right branch (nonZero), add a CodeActivity and call it divide. Add an ExecuteCode handler with the following piece of code:

    private void divide_ExecuteCode(object sender, EventArgs e)
    {
       result = a / b;
    }

  6. Move the webServiceOutput1 activity below the divide activity in the right branch (nonZero).

If you've done the jobs above correctly, you should see this:

Get it published

Time to start the web service creation and publication. Right-click the WorkflowViaWS project in the Solution Explorer and choose Publish as Web Service:

This will create an ASP.NET Web Site called WorkflowViaWS_WebService and add it to the current solution:

As you can see a web.config file was created (it should pop up immediately after completion of the "Publish as Web Service" action). In there you'll see some interesting regions such as:

<WorkflowRuntime Name="WorkflowServiceContainer">
   <
Services
>
      <
add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
/>
      <
add type="System.Workflow.Runtime.Hosting.DefaultWorkflowCommitWorkBatchService, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
/>
   </
Services
>
</
WorkflowRuntime>

More information about these two services (which have to do with threading) can be found in the Windows SDK. You'll also see an HttpModule being hooked in:

<httpModules>
   <
add type="System.Workflow.Runtime.Hosting.WorkflowWebHostingModule, System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="WorkflowHost"
/>
</
httpModules>

This module (WorkflowWebHostingModule) is responsible to maintain (read/write) a client cookie that holds the workflow's instance identifier (a GUID that uniquely represents the workflow instance the user is connected to). This allows multiple web method calls to be part of one workflow instance.

Finally there are references to the System.Workflow.* assemblies:

<add assembly="System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>
<
add assembly="System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
/>
<
add assembly="System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

The web service file (called WorkflowViaWS.Divider_WebService.asmx but renameable) has a fairly short definition:

<%@WebService Class="WorkflowViaWS.Divider_WebService" %>

Geeky readers might consider to ildasm the WorkflowViaWS.dll file that was dropped in the Bin folder:

Hit F5 and run

Eager to see what you've created? Just hit F5 (when you're somewhere inside the ASP.NET Web Site in order to have the website as startup project) and choose the add debugging configuration to web.config (this might be useful if you decide to play with breakpoints in the workflow). The built-in ASP.NET Development Server will get launched:

Finally the web service page will be displayed in the browser:

Click on Divide to test the web service and enter the values 10 and 20. The result should be - help where is my calc.exe? - 0.5:

Note: If you try to invoke the web service more than once in the same session, you'll see the following error.

System.InvalidOperationException: The workflow hosting environment does not have a persistence service as required by an operation on the workflow instance &quot;21b04980-9926-4b35-a87f-3d1c98b3a987&quot;.

This is normal because of our configuration. Recall the WorkflowWebHostingModule HttpModule that keeps track of the workflow instance identifier in a cookie on the web client. In between calls from the same session the system needs to be able to persist the workflow instance data to disk. Because we didn't configure a persistence service, this fails. Persistence services will be covered in a separate post later on.

What about the fault? Restart your browser and open the web service page again. Now try to make a call with parameters a=10 and b=0. When invoking the service, you'll end up with an HTTP 500 Internal Server Error. If you want to see the exception that was sent in the fault, create a little web service client application (e.g. a console application) like this:

class Program
{
   static void Main(string
[] args)
   {
      localhost.
Divider_WebService ws = new localhost.Divider_WebService
();

      try
      {
         double
res = ws.Divide(10, 0);
      }
      catch (Exception
ex)
      {
      }
   }
}

I assume everyone knows how to add a web service reference to an existing web service and how to use the proxy object. There's one caveat however: if you restart the ASP.NET Development Server that comes with Visual Studio 2005, it will likely listen on another random port. The web service proxy will then be invalid, unless you change the Url property on the proxy object to reflect the current URL to the web service (http://localhost:<port>/WorkflowViaWS_WebService/WorkflowViaWS.Divider_WebService.asmx).

You can copy the application to an IIS machine too of course, but keep in mind the same version (= CTP build) of WF (and hence, .NET Framework 3.0) needs to be present on the web server machine. If you're using your own pc as IIS web server, there shouldn't be a problem if configuration is correct (ASP.NET 2.0 is registered - cf. aspnet_regiis.exe - and - in W2K3 - the web service extension is allowed). For Vista users some experience with IIS 7 might be helpful although it's pretty plug-and-play.

That said, the result of a debugging session should be like this:

Conclusion

Exposing a workflow through simple ASP.NET 2.0 web services is no rocket science. In this post you saw the basics of this; further posts in this workflow series will cover more advanced scenarios with workflow persistence and the use of WCF as a WF host.

Keep up the good WF-work!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

# Workflow Foundation Learning - I DID IT!

Saturday, September 16, 2006 11:59 PM by while(availableTime>0) {

Well, today (4th of September 2006, almost the independence day of my country) I decided to learn about...

# re: WF - Exposing a workflow through a web service

Friday, September 22, 2006 1:29 PM by Daniel

Excellent !
Can't wait for configuring Persistence services post.

# Learning Windows Workflow Foundation &laquo; Angel &#8220;Java&#8221; Lopez on Blog

# Aprendiendo Windows Workflow Foundation

Monday, October 09, 2006 11:58 AM by Angel "Java" Lopez

En estos dias, he ordenado algunos enlaces y recursos sobre el Windows Workflow Foundation, el motor

# Iulian Tab??r??: Blog &raquo; Blog Archive &raquo; Windows Workflow Foundation

Pingback from  Iulian Tab??r??: Blog  &raquo; Blog Archive   &raquo; Windows Workflow Foundation