How should you use WF4 and WCF with ASP.NET?
Download Sample Code - Windows Workflow Foundation (WF4) - How to use Workflow from ASP.NET
endpoint.tv - ASP.NET WF4 / WCF and Async Calls
For this post I’ve created a really simple workflow and WCF service that delay for a specific amount of time and then return a value. Then I created an ASP.NET page that I can use to invoke the workflow and WCF service to test their behavior
First off – let’s get one thing straight. When you create a workflow, you are creating a workflow definition. The workflow definition still has to be validated, expressions compiled etc. and this work only needs to be done once. When you hand this workflow definition to WorkflowInvoker or WorkflowApplication it will create a new instance of the workflow (which you will never see).
To make this clear, in this sample I have a workflow file SayHello.xaml but I named the variable SayHelloDefinition.
1: private static readonly Activity SayHelloDefinition = new SayHello();
If you want to do something really simple, you can just invoke workflows and services synchronously using WorkflowInvoker
1: private void InvokeWorkflow(int delay)
2: {
3: var input = new Dictionary<string, object> { { "Name", this.TextBoxName.Text }, { "Delay", delay } };
4:
5: this.Trace.Write(string.Format("Starting workflow on thread {0}", Thread.CurrentThread.ManagedThreadId));
6:
7: var output = WorkflowInvoker.Invoke(SayHelloDefinition, input);
8:
9: this.Trace.Write(string.Format("Completed workflow on thread {0}", Thread.CurrentThread.ManagedThreadId));
10:
11: this.LabelGreeting.Text = output["Greeting"].ToString();
12: }
Likewise you can invoke the WCF service synchronously
1: private void InvokeService(int delay)
3: this.Trace.Write(string.Format("Calling service on thread {0}", Thread.CurrentThread.ManagedThreadId));
5: var proxy = new TestServiceClient();
7: try
8: {
9: var result = proxy.DoWork(delay);
10: proxy.Close();
11: this.Trace.Write(
12: string.Format(
13: "Completed calling service on thread {0} delay {1}", Thread.CurrentThread.ManagedThreadId, result));
14:
15: this.LabelDelay.Text = result.ToString();
16: }
17: catch (Exception)
18: {
19: proxy.Abort();
20: throw;
21: }
22: }
With the default delay of 1000ms this page will take about 2.5 seconds to load. 1 second for the workflow, 1 second for the WCF service and .5 seconds for everything else.
If you are going to write code that runs on a server you should get used to writing async code. Yes I know it is generally a pain and more complex but what can I say… the result is far better.
ASP.NET won’t allow async work unless you set the Async directive.
1: <%@ Page Title="" Language="C#" ... Async="true" Trace="true" %>
In this simple workflow I’m not using persistence or bookmarks (which would make the code slightly different if I did). One of the more confusing elements of doing Async work is that as you search the web you will see the history of how async was done in the past and you might get confused. In fact I found that it is quite difficult to pin down what the correct method is today.
What you want to do is to tell the Workflow Runtime that you are working with a SynchronizationContext from ASP.NET. Once you have done this, you can inform ASP.NET (via the context) when your operation begins and ends. This code gets a little tricky but follow along.
1: private void InvokeWorkflowAsync(int delay)
3: // Create the inputs
4: var input = new Dictionary<string, object> { { "Name", this.TextBoxName.Text }, { "Delay", delay } };
5:
6: var workflowApplication = new WorkflowApplication(SayHelloDefinition, input)
7: {
8: // Tell the runtime we are using the ASP.NET synchronization context
9: SynchronizationContext = SynchronizationContext.Current,
11: Completed = (args) =>
12: {
13: this.Trace.Write(
14: string.Format("Completed workflow on thread {0}", Thread.CurrentThread.ManagedThreadId));
15:
16: // Update page controls here
17: this.LabelGreeting.Text = args.Outputs["Greeting"].ToString();
18:
19: // Tell ASP.NET we are finished
20: SynchronizationContext.Current.OperationCompleted();
22: };
23:
24: this.Trace.Write(string.Format("Starting workflow on thread {0}", Thread.CurrentThread.ManagedThreadId));
25:
26: // Tell ASP.NET we are starting an async operation
27: SynchronizationContext.Current.OperationStarted();
28:
29: workflowApplication.Run();
30:
31: // Don't try anything here - it will run before your workflow has completed
32: }
When you add a Service Reference the default is to not generate async operations.
Now you have a super-charged proxy that can do async. There are two methods for async the old APM (Begin/End) and the newer Event based async model (EPM EAP). In this code I’m using the EPM EAP model. Note that I don’t have to call OperationStarted/Completed because WCF will do it for me.
1: private void InvokeServiceAsync(int delay)
7: proxy.DoWorkCompleted += (o, args) =>
9: if (!args.Cancelled)
10: {
13: "Completed calling service on thread {0} delay {1}",
14: Thread.CurrentThread.ManagedThreadId,
15: args.Result));
16: this.LabelDelay.Text = args.Result.ToString();
17: }
18: };
19:
20: proxy.DoWorkAsync(delay);
I’ve enabled the ASP.NET Page Trace on my service so we can see what is happening.
As you can see we started the workflow and the service call on thread 37. Then ASP.NET was able to do some additional work before our workflow and service call which completed later. ASP.NET will wait on both of these to complete before it finishes rendering the page and returning to the caller.
With Workflows you
With WCF you
Happy Coding! Ron Jacobs http://blogs.msdn.com/rjacobs Twitter: @ronljacobs http://twitter.com/ronljacobs
Ron,
More posts related to WF4 and ASP .Net / MVC is really appreciated. Also I'm still looking forward to see some tips related to using WF4 in Azure environment, particularly utilizing WF persistence.
TX
Thanks - I've got some ideas for more things on ASP.NET.
As for Azure - we've got some things in the works that are going to be really cool once I can share them with you.