Exposing a Workflow as a WCF Service in Visual Studio 2008 beta 1 (Step by step)[Updated for RTM version]

 

image

By Serge Luca

MVP  

This post illustrates the new Send and Receive activities in Visual Studio 2008 beta 1[updated for RTM version] (formerly "Orcas") and in the .Net Framework 3.5; managing workflow context will also be discussed. I

I did this tutorial for my Microsoft Dev Days presentation in Luxemburg last week (June 12  2007).

[The code will be downloadable in a few days from U2U web site]

My intention was to port to Visual Studio 2008 b1 the workflow described by Israel Hilerio (Microsoft) less than 2 years ago .

Here is the scenario :

A user wants to submit its expense report :

 

  • The user will request a token
  • The user will submit the expense report
  • The user/manager can visualize its submitted expense report
  • The  manager must approve/reject
  • If his manager doesn’t answer quickly enough the manager will get an e-mail

This is a long term « transaction »:

 

  • The steps can take minutes/days/weeks
  • There are several steps
  • The manager can reply/approve the expense report after several days
  • The system can escalate the task
  • The logic can be shared across several applications : Web services or WCF services = business facade (sharing & security)

Architecture

The workflow is supposed to be shared by 2 applications: the submitter’s application and the manager’s application; it makes sense to expose it as service; we will implement only one application in this demo. Our workflow fits in the category of the business workflows (not all workflow are business workflows, some of them can be used to implement a page flow or to manage the state of a Windows/WPF form for instance).

I think that  business workflows will be exposed as services most of the time in the future; the pair workflow/service seems very important in my eyes; for your info the Workflow Foundation team and the Windows Communication team have  merged…

Windows Communication Foundation (WCF) is the preferred .Net technology for implementing a service (which could be or not a web service) without forcing us to choose a specific architecture.

Creating the Service contract

We’ll see later that the Workflow Foundation Designer in Visual Studio 2008 allows us to define a Service Contract; however we’ll define the Service contract in another project.

So let’s start Visual Studio 2008 b1 and let’s create a new empty solution with a new (Class Library) project as illustrated in the next picture; make sure the Net Framework 3.5 is selected.

 

Next, add references to System.Runtime.Serialization (we’ll need it for the DataContract, see below) and System.ServiceModel (we’ll need it for the ServiceContract, see below) :

It’s time to create the (WCF) interface that will be exposed as an endpoint:

We also have to define the ExpenseReport class that will be decorated with DataContract in order to use the new WCF serializer. [For  a nice comparison between the previous Serializer, the XmlSerializer, and the DataContractSerializer, read this article.].  

 

 The workflow

Let’s create a new workflow project (Sequential Workflow Library); here again, make sure the .Net Framework 3.5 is selected:

We need to reference the contract’s assembly :

The .Net Framework 3.5 provides 2 new Workflow activities :

ReceiveActivity that allows an (WCF) interaction point between the workflow and the outside world .

SendActivity : for invoking a WCF/Web service.

These activities are going to replace the venerable WebServiceInput and WebServiceOutput activities of the .Net Framework 3.0.

Drag and drop a receive activity into the Workflow Designer :  

If we open the receive activity property page, we’ll see a new ServiceOperationInfo property :

ServiceOperationInfo allows to make the link the activity with the method of the ServiceContract.

If we click on this property we get a new window that allows either to import the contract (if we already have a contract, which is our case here) or to generate a contract :

 

Click on "Import Contract" , and select IApprovalContract :

Then the interface’s methods show up; select "CreateExpenseReportId".

 

You’ll notice the Permissions panel that allows us to associate a role with a service method. When won’t use roles in this tutorial.

 

The CreateExpenseReportid() method returns a Guid : we are therefore requested to handle the return value : we will store the return value in a new workflow  data member :

We have now to specify the fact that calling this method will instanciate a new workflow:

select the receive activity and set its CanCreateInstance property as  True.

We have to associate the GenerateReportId method with some code : this can be achieved by using a Code activity, a Custom activity or a Policy Activity. Let’s drag and drop a policy activity :

 

And here is the rule :

 

The action here is:

this.ReportId = Guid.NewGuid().ToString();

It’s time to host and to test our workflow before adding new interaction points.

Hosting the workflow

Add a new console project in the solution and make sure the .Net Framework 3.5 is selected:

 

 

The host will need a reference to the workflow and the contract : add a reference to the dedicated assemblies:

In the Main() function of the generated Console application, add an instance of a new .Net 3.5 class : WorkflowServiceHost.

As the WCF ServiceHost class, WorkflowServiceHost is derived from ServiceHostBase and is a kind of Workflow Factory ( encapsulates the workflowRuntime among others, manages the generation of Workflow instances and maps the request to the appropriate workflow instances)

WorkflowServiceHost is a part of a new .Net 3.5 assembly: System.WorkflowService  :

 

 

Now, we can choose a binding : WSHttpContextBinding allows the use of the http protocol in a WS*compatible way.

WSHttpContextBinding contextBinding = new WSHttpContextBinding(); 

As we’ll see later, this class  can manage the Workflow context  at the level of the soap message (like the workflow instance Guid). Other classes like NetTcpContextBinding and BasicHttpContextBinding also manage the workflow context.

We also need to link the binding, the interface and a url :

serviceHost.AddServiceEndPoint(typeof(IApprovalContract), contextBinding, http://localhost:8080/ExpenseReportService);

serviceHost can start listening to requests :

serviceHost.Open();

Here is completed code:

 

The client application

Add a new console project in the current solution and make sure the .Net Framework 3.5 is selected; this version of the framework is necessary (albeit not mandatory) if we want to use the WSHttpContext class for automatically handling the workflow context.

 

We can reference the assembly containing the interface (this is not mandatory: we have the option to re-generate it with the proxy code):

Add an instance of the WSHttContext:

WSHttpContextBinding contextBinding = new WSHttpContextBinding();

Add an instance of a ChannelFactory that will create the proxy to the host :

    ChannelFactory<IApprovalContract> factory =
                new ChannelFactory<IApprovalContract>(binding,
                    "http://localhost:8080/ExpenseReportService");
            IApprovalContract proxy = factory.CreateChannel(); 

Call the CreateExpenseReportId () method :

string reportId = proxy.CreateExpenseReportId();

Here is the whole code:

Start the host project followed by the client project.

Extending the workflow: submitting the expense report

Add a new receive activity below the existing one.

 

 

  Link the ServiceOperationInfo property to the SubmitExpenseReport  method :

 

 

Store the submitted report in a workflow data member :

We want to workflow to stay alive while the expense report has not been accepted or rejected by the manager; this will also give  the opportunity to the user or its manager to visualize the expense Report. Therefore, we need a While activity :

This while activity will run until the workflow is processed; we need to add a data member in the workflow class(flag) : IsNotProcessed :

 

Another functionnality is that if the manager doesn’t approve/reject within a certain period of time, the information should be escaladed or the manager could get an email or something similar. 

Rejecting, approving, reading the expense report and checking if we have to escalade the information means that the workflow will be waiting for such events. We need a ListenActivity in the While activity.

On the left branch of the ListenActivity, we can handle the "timeout" by using a DelayActivity :

 

We won’t implement the escalation process, we will just display an information to the server console : let’s add a policy activity.

Just display "escalate to manager" at the console and set the delayActivity timeout to 00:00:10 .

 

On the right branch, add another Receive Activity that will manage the approval process:

Link this activity with the ApproveExpenseReport method of the contract:

Add a policy activity where you will set the flag IsNotProcessed as false

 

 

You can repeat this for the Rejection process (in another branch):

 

  • add another listen branch with a Receive activity for the retrieval process;
  • link the receive activity with the RetrieveExpenseReport method of the contract
  • do not set the flag IsNotProcessed as false
  • bind the return value of the RetrieveExpenseReport method to the workflow TheExpenseReport data member as illustrated in the next picture

 

The next picture illustrates what’s the whole workflow looks like :

 

Eventually, I suggest to add a codeActivity just before the end of the workflow for tracing the workflow termination.

The following code would be executed :

private void TraceWorkflowEnded_ExecuteCode(object sender, EventArgs e)
{         
            Console.WriteLine("Workflow {0} Finished", this.WorkflowInstanceId);
}

Updating the client application

Let’s update the client application in order to take the next extension points into account:

 


IApprovalContract proxy = factory.CreateChannel();
string reportId = proxy.CreateExpenseReportId();
       
Console.WriteLine("Report ID is {0}", reportId);

//TODO: insert the code after this line
ExpenseReport myExpenseReport = new ExpenseReport();
myExpenseReport.Id = reportId;
myExpenseReport.Amount = 1000;
proxy.SubmitExpenseReport(myExpenseReport);
myExpenseReport = proxy.RetrieveExpenseReport();
Console.WriteLine("ExpenseReport Retrieved: ID:{0}, amount {1}"
myExpenseReport.Id, myExpenseReport.Amount);


if (MessageBox.Show("Do You approve?",
                "expense report", MessageBoxButtons.YesNo) ==
                DialogResult.Yes)
 {
                proxy.ApproveExpenseReport();
 }
 else
 {
                proxy.RejectExpenseReport();
 }

 

Let’s start the server:

 

Let’s start the client :

Here is the server’s console after starting the client application : (after a few seconds the workflow escalades the info).

Comments : the client receives a token and uses it for submitting the expense report; the workflow displays the expense report id (the token); the client application retrieves the expense report and is being asked for approving or rejecting the report; in the meantime, the workflow escalades the information.

 

Here is the server’s console if we approve the expense Report

Comments: the manager (client application) has approved the expense report, the worklfow completes its execution.

The workflow context & client-side

You’ll notice that the system doesn’t have to specify the worklfow Id at each function call, thanks to the WSHttpContextBinding class

The first proxy call:

string reportId = proxy.CreateExpenseReportId();

instanciates the workflow on the server and subsequent calls reuse the workflow context.

This is very different from the .Net Framework 3.0 "tradition" where an HttpCookieContainer had to be used.

This is very nice , but how can we reuse the workflow context (and the workflow Guid) between different application ? For instance the user submit its expense report in application A and the manager approves/rejects/reviews the expense report in application B ?

After several hours of testing, here is what I’ve found :

IContextManager contextMger = ((IChannel)proxy).GetProperty<IContextManager>();

IDictionary<System.Xml.XmlQualifiedName, string>
 context =contextMger.GetContext();

System.Xml.XmlQualifiedName qualifiedName =
                new System.Xml.XmlQualifiedName
                    ("InstanceId", "
http://schemas.microsoft.com/ws/2006/05/context");
string guid = context[qualifiedName];
//You get the workflow Id here

! Update (13/12/2007) .Net 3.5 Release version: the IDictionnary is now IDictionnary<string,string> and the key is"instanceId".

 

Using the Send activity for testing the workflow

In order to play again with my new toys, I decided to use the new Send activity to test my expense Report workflow.

The Send activity allows us to call a WCF service in a WS* compatible way if necessary.

Let’s create a new workflow console project (.Net 3.5).

Add a app.config file with the following configuration :

 <configuration>
  <system.serviceModel>
    <client>
      <endpoint name="ExpenseReport"
                address ="
http://localhost:8080/ExpenseReportService"
                binding ="wsHttpContextBinding"
                contract ="U2U.ExpenseReports.ServiceContracts.IApprovalContract"/>
    </client>
  </system.serviceModel>
</configuration>

The endpoint will be referenced by each Send activity.

The User interface/test workflow looks like this :

 

Here is  the property page of my first Send activity :

 

The ChannelToken property links the Send activity to the appropriate EndPoint (detailed in the app.config).

 


Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s