Monday, September 04, 2006 3:34 PM bart

WF - Exposing a workflow via WCF

Introduction

In my previous post on Windows Workflow Foundation I covered how to expose a workflow via .NET 2.0 web services. You saw how easy it is to do this thanks to the built-in support in the Visual Studio 2005 Extension for Workflow. However, the next-generation service platform is Windows Communication Foundation (WCF), and so we'll take a look into the basics of hosting a workflow in WCF.

Defining a workflow

We'll start this demo by creating a new Empty Workflow Project, as shown below. The reason to choose a workflow project is to have the VS2005 Extension for Workflow loaded, which we wouldn't have if another non-WF project type is chosen.

Next, add a new Sequential Workflow to the project (State Machine Workflows will be covered in a later post) and call it Adder. Needless to say the use of WF (and the calculator service around it) is useless for this kind of applications, but hey that's what demos are all about isn't it? We'll just examing how to host a workflow inside a WCF host, dot.

Tip: Choose the Sequential Workflow (with code separation) item. You can compare this with code-behind separation in ASP.NET. One file will contain markup (in this case that will be the .xoml file - XOML stands for eXtensible Object Markup Language) and another one (the code-behind file if you want) contains the C# code you'll be adding (.xoml.cs extension).

On the workflow, add one single CodeActivity called add. Next, create an event handler for ExecuteCode with the following piece of code in it:

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

In here a, b and result are private members that have a corresponding (starting with upper-case) property as shown below (prop-prop-prop):

private int a;

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

private int
b;

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

private int
result;

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

Adding the WCF service

The next step is to add a WinFX Service (in later builds this will likely be changed to reflect the .NET Framework 3.0 branding), which we'll call CalculatorService.

In the CalculatorService.cs file you'll find an interface called ICalculatorService and a class called CalculatorService implementing that interface. The interface is called the service contract and has to changed as follows:

[ServiceContract()]
public interface
ICalculatorService
{
   [
OperationContract
]
   int Add(int a, int
b);

   [
OperationContract
]
   int Subtract(int a, int
b);

   [
OperationContract
]
   int Multiply(int a, int
b);

   [
OperationContract
]
   int Divide(int a, int
b);
}

We'll only be implementing the Add method, you can create an implementation for the other methods yourself if you want. Tip: Divide is somewhat more tricky because you have to cope with a "division by zero" situation. To solve that problem, go and find out about FaultContracts in WCF and play around a bit.

Next, change the CalculatorService class as outlined below. Tip: Put the cursor somewhere in the ICalculator part of the class declaration, click on the smart tag and choose to implement the interface (implicitly) - or press SHIFT-ALT-F10. Leave the Subtract, Multiply and Divide methods as they are now, i.e. just throwing an exception (I still wonder why the VS 2005 IDE folks didn't choose the NotImplementedException for this piece of auto code generation; or not?):

public int Subtract(int a, int b)
{
   throw new Exception("The method or operation is not implemented."
);
}

public int Multiply(int a, int
b)
{
   throw new Exception("The method or operation is not implemented."
);
}

public int Divide(int a, int
b)
{
   throw new Exception("The method or operation is not implemented."
);
}

The Add method is the most interesting one of course:

public int Add(int a, int b)
{
   int
res = 0;

   using
(WorkflowRuntime wr = new WorkflowRuntime())
   {
      AutoResetEvent waitHandle = new AutoResetEvent(false
);

      wr.WorkflowCompleted +=
         delegate(object sender, WorkflowCompletedEventArgs
e)
         {
            res = (
int)e.OutputParameters["Result"
];
            waitHandle.Set();
         };

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

      WorkflowInstance wi = wr.CreateWorkflow(typeof(Adder
), arguments);
      wi.Start();

      waitHandle.WaitOne();
   }

   return
res;
}

This should be familiar from previous posts. One thing that might be new is the use of output parameters:

res = (int)e.OutputParameters["Result"];

It might be a little hard to read the code from top to bottom because the result is grabbed from the anonymous method that's hooked in to the WorkflowCompleted event.

In order to make our service functional, we have to create a configuration file first:

As you should have seen already, the CalculatorService.cs file contains a comment section on how to create the configuration file. Uncomment the configuration XML and cut-paste it into the <configuration> section of the app.config file. Next, make some changes (indicated in bold and underlined - 3 changes in total):

<?xml version="1.0" encoding="utf-8" ?>
<
configuration
>
   <
system.serviceModel
>
      <
services
>
         <!--
Before deployment, you should remove the returnFaults behavior configuration to avoid disclosing information in exception messages
-->
         <
service name="WFviaWCF.CalculatorService" behaviorConfiguration="returnFaults"
>
            <
endpoint contract="WFviaWCF.ICalculatorService" binding="wsHttpBinding"
/>
            <
endpoint contract="IMetadataExchange" binding="mexHttpBinding" address="mex"
/>
         </
service
>
      </
services
>

      <
behaviors
>
         <
serviceBehaviors
>
            <
behavior name="returnFaults"
>
              <
serviceMetadata httpGetEnabled="true"
/>
              <
serviceDebug includeExceptionDetailInFaults="true"
/>
            </
behavior
>
         </
serviceBehaviors
>
      </
behaviors
>
   </
system.serviceModel
>
</
configuration>

The first change is required because of a configuration schema change (the service's type attribute has changed to become a name attribute); the other two changes enable MEX (Metadata EXchange) that allows us to create a web service proxy (read: retrieve the WSDL service definition over HTTP).

A console application host

Next on our to-do list is the creation of a hosting application. You could choose for a Windows Service or walk the path of IIS hosting (slightly more difficult ways) but we'll stick with a simple console application. So add a new class file to the project and call it Program.cs:

Change the code of the Program.cs file like this:

using System;

namespace
WFviaWCF
{
   class
Program
   {
      public static void
Main()
      {
         MyServiceHost
.StartService();
         Console
.ReadLine();
      }
   }
}

This code uses the MyServiceHost class that was auto-generated as part of the WinFX Service creation process (see CalculatorService.cs for its definition) and just starts the service and waits for user input to stop the service host.

Before we can launch the application we'll need to set the project properties to compile to a console application with a given entry point:

Running the application

Time to press F5 and see the Console application service host in action. Whoops, something goes wrong (assuming you're running on Vista and you haven't elevated yourself to run VS2005 with administrator privileges). No worries, I wouldn't post this if I hadn't a solution in mind...

What happens is this: Windows Vista has a kernel-mode listener called http.sys (just like Windows Server 2003 and Windows XP SP2). All HTTP traffic passes through it. But before an app can start listening on an HTTP address, it needs to get registered with http.sys. More specifically the user account running the service host needs to have permission to use a URL to start listening on. In Windows Server 2003 and Windows XP SP2 there's a tool called httpcfg.exe (see TechNet; see Support Tools for Windows XP). However it doesn't ship with Windows Vista. Geeks could compile httpcfg.exe from the code comes with the Platform SDK (folder Samples\NetDS\HTTP\ServiceConfig).

However, the god news is that httpcfg.exe is dead. It's successor is called netsh http. So, go to a command prompt, elevated using "Run as administrator", and do the following:

The command used in here is

add urlacl url=http://+:8080/WFviaWCF/CalculatorService user=VISTA-9400\Bart

and contains the url as displayed in our AddressAccessDeniedException, plus the user who is allowed to use the URL (replace it by your DOMAIN\user). Alternatively you can also allow access to the URL to BUILTIN\Users or \Everyone but please understand the possible security risks when doing so.

Try to launch the application again, it should work correctly now. Time to open up a browser and go to http://localhost:8080/WFviaWCF/CalculatorService. It should look like this:

If it looks like the screenshot below, you've not configured MEX correctly as I outlined earlier on. Notice however that WCF helps you out with the problem so you don't need to know the XML configuration syntax by head.

A client application

In order to test our service we'll have to build a client application now. Just open another instance of Visual Studio 2005 and create a new console application called WFviaWCFClient. Next, go to the solution explorer, right-click and choose Add Service Reference. This the WCF-equivalent of Add Web Reference and calls svcutil.exe behind the scenes (instead of wsdl.exe). It fully supports WCF channels and the contract methodology but you can inspect this yourself if you like in the Service References\localhost.map\localhost.cs file that will get generated.

Enter the Service URI and accept the default Service reference name. Finally click on OK:

Last but not least, change the main method in program.cs like this:

static void Main(string[] args)
{
   int
a = 1, b = 2;

   localhost.
CalculatorServiceClient svc = new localhost.CalculatorServiceClient
();
   Console.WriteLine("{0} + {1} = {2}"
, a, b, svc.Add(a, b));
   Console.ReadLine();
}

Time to run the application (almost ashame to show you such a trivial calculation, so that explains the little image below :-)):

Exercise: Make multiple calls to the Add method and notice you don't end up with an exception like we did in the ASP.NET 2.0 Web Service scenario. Why is this? (Tip: think about persistence, sessions and our WCF host implementation).

Conclusion

Hosting our workflow behind a service facade using WCF shouldn't be difficult at all (try to experiment with other transport mechanisms, check out the WCF documentation). However, keep in mind this was a trivial stateless example. Also notice the lack (today) of equivalents to the WebServiceInput/Output/Fault activities. More complex scenarios (supporting state etc) will require more work; I'll come back to this later on, so keep an eye on my blog.

See you again in the WF space soon!

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

Filed under: ,

Comments

# re: WF - Exposing a workflow via WCF

Thursday, September 07, 2006 4:09 PM by Anders Jacobsen

Well writtin articles. I´m also going to write about WWF in my Thesis starting 15. sep this year. Interesting stuff...If they just didnt release a new RC every other day! My dev envoriment is more or less fucked by now ;) Beta is beta though..

# 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...

# 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