ASP.NET WF4 / WCF and Async Calls
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
The Workflow Definition
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();
The Easy Way
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)
2: {
3: this.Trace.Write(string.Format("Calling service on thread {0}", Thread.CurrentThread.ManagedThreadId));
4:
5: var proxy = new TestServiceClient();
6:
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.
The Fast Way
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.
Step 1 – Tell ASP.NET you want to do Async
ASP.NET won’t allow async work unless you set the Async directive.
1: <%@ Page Title="" Language="C#" ... Async="true" Trace="true" %>
Step 2 – Use the ASP.NET Synchronization Context
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)
2: {
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,
10:
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();
21: }
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: }
Step 3 – Configure your Service Reference for Async Operations
When you add a Service Reference the default is to not generate async operations.
Step 4 – Call your Service Async
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)
2: {
3: this.Trace.Write(string.Format("Calling service on thread {0}", Thread.CurrentThread.ManagedThreadId));
4:
5: var proxy = new TestServiceClient();
6:
7: proxy.DoWorkCompleted += (o, args) =>
8: {
9: if (!args.Cancelled)
10: {
11: this.Trace.Write(
12: string.Format(
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);
21: }
Step 5 – Test Your Service
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.
Summary
With Workflows you
- Set the WorkflowApplication.SynchronizationContext to the ASP.NET synchronization context
- Call OperationStarted/Completed to let ASP.NET know when the workflow is done
With WCF you
- Configure your service reference for async operations
- Use async operations in your code
- Don’t have to worry about SynchronizationContext
Happy Coding!
Ron Jacobs
https://blogs.msdn.com/rjacobs
Twitter: @ronljacobs https://twitter.com/ronljacobs
Comments
Anonymous
March 29, 2011
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. TXAnonymous
March 29, 2011
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.