In my last blog posts I described how to connect to a generic WCF service from LightSwitch. In this blog post I will build on the previous postings to describe how to connect to a Workflow (WF) Service.
Like my earlier posts describing how to interface to a WCF service from LightSwitch, this will also be a multiple part posting.
This first part will describe how to create a simple WF service. There is a lot of configuration to describe, for even a simple WF service. So I want to limit this first post to just focus on getting the WF service ready for deployment and testing.
The second post will describe how to deploy the WF service, create a proxy to the service, and implement a simple Console application to test the service.
The third post will describe the code to write in LightSwitch to use the WF service.
Why Windows Workflow?
I think Windows Workflow adds value to a business application. You can use a workflow to define and implement business logic for a variety of business related problems such as order processing, document management and approval, defining how to flow data through a customer contact process.
What I like about workflow is it allows you to decouple your business logic from your application.
One advantage of decoupling is you can make changes in your workflow logic without the necessity of updating your application. As long as the interfaces to your workflow remain consistent, you can make internal changes to your workflow without needing to recompile, retest, and redeploy your business application.
Another advantage of using WF is you can have a library of WF implementations used by different applications in your organization. You may have ASP.NET applications currently using a WF library. If you can reuse the business logic in your WF in another application, that’s less code you need to write and test as you migrate to another application platform such as LightSwitch.
Technology changes, and LightSwitch will be version 1.0 when it goes RTM. Will the interfaces and internal implementations change for V2.0? Will some other product based on different tooling replace what you do in LightSwitch? By decoupling business logic, you can reduce the risk of future work.
Finally, a current issue with LightSwitch it is difficult to perform automated testing. Business logic tends to be hard to get correct due to multiple conditions and paths which need verified. It tends to change based on business needs. Business logic (rules / workflow) often needs persistence, tracking, and bookmarking to resume the logic.
Putting that logic into LightSwitch becomes problematic. You can build the business logic into a DLL and test the library via automated tests. But likely, that implementation will have dependencies which preclude usage in other application environments. WF is designed to solve these kinds of problems for business logic. And testing workflows in WF is arguably easier than testing equivalent ‘home grown’ logic hosted within LightSwitch.
Defining the Workflow
At first I thought it possible to create a workflow called from LightSwitch by creating a simple WF library. I quickly found that can’t work. The problem is LightSwitch, being a Silverlight based application, only allows you to run a subset of the .NET 4.0 assemblies. I found it necessary to create a Workflow Service and call that service from LightSwitch—similar to calling a vanilla WCF service.
To start off, I wanted a very simple WF example. In future posts I plan to create a more complex workflow. But for this first example, I want something easy to understand which demonstrates how to wire up a WF service and use it from LightSwitch without getting hung up in workflow complexities.
I’ve been reading Bruce Bukovics book, Pro WF Windows Workflow in .NET 4, published by Apress (ISBN 978-1-4302-2721-2) to learn more about WF.
For this example, I’m using an example workflow described his book which calculates shipping charges on an order. Since I’ve using the AdventureWorks database which contains a OrderHeader table, this is a reasonable starting point.
I’ll call the workflow CalculateShipping. The business rules are as follows:
If the shipping method is ‘normal’ is specified, the following rules apply:
- If the total order exceeds 75.00, the calculated shipping is 0. (free shipping).
- Else the calculated shipping is the weight * 1.95.
If the shipping method is ‘express’, the following rules apply:
- There is always a shipping charge for an express order
- The shipping is calculated as weight * 3.5
There is a minimum shipping charge:
- If the shipping charge is > 0 and less than 12.5, the shipping charge is 12.50.
Data provided to the workflow to calculate the shipping charge comes from the AdventureWorks OrderHeader table. OrderHeader.ShipMethod contains the data for the Shipping Method. In the database, the table is populated with ‘CARGO TRANSPORT 5’. For expediency, I implemented the workflow to process ‘CARGO TRANSPORT 4’ as ‘normal’ and ‘CARGO TRANSPORT 5’ as ‘express’
There is no Weight field in OrderHeader, so for expediency in demonstration, I’m using the data in the Freight column as ‘weight’
OrderHeader.TotalDue provides the data for “total order”
Implementing the CalculateShipping Workflow
The first step to implementing the workflow is to create a Visual Studio 2010 Workflow Services project and call it ServiceLibrary.
First I delete the ReceiveRequest and SendResponse activities in the designer.
Next, under the Messaging category in the Toolbox, I drag the ReceiveAndSendReply activity onto the design service, inside the Sequential Service activity:
Next I rename the xamlx file from Service1.xamlx to OrderProcessing.xamlx
Next I add three class files to the project:
This is the implementation for OrderProcessingRequest:
This is the implementation for OrderProcessingResponse:
As you can see, both of these classes are simple containers.
CalculateShipping implements the business rules described earlier for calculating the shipping rate.
The implementation for the CalculateShipping activity is:
Key points of the implementation:
1. I want CalculateShipping to be a custom code activity which returns a decimal value of the calculated shipping rate. You can do this by deriving the class from CodeActivity<decimal> as seen in Line 22 of the source.
2. This custom activity has three input parameters: Weight, OrderTotal, and ShipVia (which is the Shipping Method). These parameters are defined as InArgument<decimal> as shown in lines 24 – 26 of the source.
3. The work for this custom activity is done in the overridden Execute() method, starting at line 33.
4. To get the value of the passed in argument in a workflow, you need to use the CodeActivityContext passed as a parameter to the Execute() method. For example, to get the value of the ShipVia argument, you use ShipVia.Get(context) as seen in line 39.
5. The implementation of the business rules is straight forward– a simple switch statement, and some calculations based on the Shipping Method, Weight, and OrderTotal.
6. When the CalculateShipping activity ends, it returns the calculated shipping amount that’s contained in the result variable, at line 67.
7. On line 37, the commented Debugger.Break()is one way to easily debug the service when you deploy it to IIS. When you uncomment this line and deploy the WF service to IIS, a dialog prompting you to attach debugger to the IIS thread pops up when the client executes the workflow.
With these classes defined, you should get a clean compile.
Having built the CalculateShipping custom activity built as part of the project, double click the xamlx file again to view the designer and open the Toolbox. You should see the CalculateShipping now included in the ServiceLibrary category of the Toolbox. Drag and drop the CalculateShipping activity onto the designer followed by dragging and dropping an Assign activity, located in the Primitives category of the toolbox.
Your workflow should appear similar to the following:
Now that you have the classes built and the activities laid out in the designer, you need to do more configuration!
In the lower left of the designer, click the Variables tab. You need to define variables which the workflow uses as it runs the activities contained in the Sequence activity. Define a Request, a Response, and a CalculatedShipValue variables.
The Request variable is a OrderProcessingRequest type. It contains the data received from the client, in the Receive activity.
The Response variable is a OrderProcessingResponse type. It contains the data sent back to the client by the SendReplyToReceive activity
The CalculatedShipValue variable is a decimal type. It holds the calculated result from the CalculateShipping custom activity.
The Assign activity added after the CalculateShipping custom activity assigns the value in CalculatedShipValue variable to the Response.CalculatedShipping.
After you have defined your workflow variables, your designer should look similar to the following:
Some key points to note when you’re defining the variables:
· The Response variable needs to be New’d from the OrderProcessingResponse.
· The decimal type- you can browse to mscorlib [188.8.131.52], then System, then scroll down to Decimal. If there’s a faster / better way of doing this, let me know. It took me a while to figure that out and it was a PITA to get it defined as a decimal type the first time around.
Ok, now that you have the variables defined for the workflow activities, you have to configure the activities.
In the designer, click on the outer most activity. In the property window, set the DisplayName property to Sequence. (this is just so we’re on the same page if you’re configuring the service as in the attached archive to this post.)
Next, click on the Receive activity.
Set the properties as follows:
Next, click the Content text box of the Receive activity to display the Content Definition dialog. In the Message Data field, enter the name of the variable containing the data received from the client—Request.
Next, click the CalculateShipping custom activity. The property dialog contains the input and output variables of the custom activity. You need to set the input variables to the value of the data contained in Request. And set the Result (the output variable for this activity) to the variable CalculatedShipValue:
In the screen shot above, when you click the button with three dots, the designer will pop up a dialog like the following where you can enter your expression. Note that the dialog does indicate the type, such as Decimal.
Next, click the Assign activity. The Assign activity is where you copy data from the CalculatedShipValue variable set by the workflow to the Response.CalculatedShipping variable.
Finally, click the SendReplyToReceive activity and configure the Request property to get its data from the Receive variable.
And click the text box next to Content to display the Content Definition dialog. Set the Message Data to use the Response variable:
Next, open the Web.Config file and verify it appears as follows:
The default setting for serviceDebug is false. I changed it to true in my configuration file so that the client would receive fault diagnostic data during testing.
One more configuration: close the designer and open it by double clicking the xamlx file. In the property window, make sure you have the ConfigurationName and Name properties set correctly:
That completes the implementation and configuration of the CalculateShipping WF service.
The next blog post will describe how to deploy this service, create a test Console application and generate a proxy for use by the Console test application.
Once you have the WF service deployed and tested, you’ll be ready to start using it from LightSwitch!