ASP.NET与WF集成

From: Dalei Zhang

For a short overview of ASP.NET Page Flow:

http://www.microsoft.com/events/EventDetails.aspx?CMTYSvcSource=MSCOMMedia&Params=%7eCMTYDataSvcParams%5e%7earg+Name%3d%22ID%22+Value%3d%221032305055%22%2f%5e%7earg+Name%3d%22ProviderID%22+Value%3d%22A6B43178-497C-4225-BA42-DF595171F04C%22%2f%5e%7earg+Name%3d%22lang%22+Value%3d%22en%22%2f%5e%7earg+Name%3d%22cr%22+Value%3d%22US%22%2f%5e%7esParams%5e%7e%2fsParams%5e%7e%2fCMTYDataSvcParams%5e

http://weblogs.asp.net/scottgu/archive/2006/08/31/Windows-Workflow-Foundation.aspx

http://www.odetocode.com/Articles/465.aspx

 

需要非常HandsOn一步步怎么做的可以看下

http://msdn.microsoft.com/msdnmag/issues/06/04/cuttingedge/default.aspx


For WF+ASP.NET+MVC:

http://www.devx.com/dotnet/Article/29992

(now we can leverage ASP.NET MVC Framework to simply this implementation)

CHS Version: http://dev.yesky.com/msdn/190/2241190.shtml

WCSF also implemented a pageflow via ASP.NET+WF, we can see pageflow part in wcsf documents.
--------------------------------------------------------------------
以下内容转载自http://msdn.microsoft.com/msdnmag/issues/06/04/cuttingedge/default.aspx

Windows Workflow Foundation, Part 2


Download ImageGet the sample code for this article.




Contents



 

In last month's column, I presented a helpdesk workflow sample that focused on Windows® Forms client applications. This month I'll discuss ASP.NET workflow applications and the ability to expose a workflow as a Web service and invoke a Web service from a workflow.


ASP.NET and Workflows

A workflow built on Windows Workflow Foundation is a component that requires an ad hoc runtime environment to function. The workflow runtime environment is represented by the WorkflowRuntime class. To host a workflow library, you create and configure an instance of the WorkflowRuntime class to operate on a particular workflow type. For performance reasons, you normally create the runtime environment only once in the application lifetime and make it serve all incoming requests. In a Windows Forms application, you initialize the workflow runtime in the form's constructor and destroy it with the form when the application shuts down. So how does this work if you're using ASP.NET?

Once you have a workflow component up and running, calling it from within a Web app or Windows Forms shouldn't be an issue. As far as Windows Workflow Foundation-based workflows are concerned, ASP.NET developers have only a few small issues to face that are mostly related to the nature of Web applications.

Just like in Windows Forms, with ASP.NET you need to have just one instance of the workflow runtime created when the application starts. Unlike Windows Forms applications, though, a Web application works by accepting and processing requests. Requests are treated individually and don't know anything about each other. That leads to issues related to workflow persistence and threading.

Here's the persistence problem: the instance of the workflow type you're working with must be persisted across successive requests. You process a request, create an instance of the workflow, and get some results to generate the response for the user. When a successive request arrives, you probably need to resume the workflow from its old state and continue. In Windows, two consecutive user actions occur in the same operational context; in ASP.NET, two user actions take two distinct requests with no state being automatically maintained. In order for a workflow to work properly in the context of ASP.NET applications, you need to be able to persist its state across requests.

Here's the threading problem: by default, the workflow runtime functions asynchronously. When the runtime receives the order to spawn an instance of a given workflow type, the Start method allocates a new thread on which to initiate the workflow. The WorkflowCompleted event gives the caller a chance to detect when the workflow completes so it can refresh the user interface. As you can see, this model raises a structural problem in ASP.NET. Imagine a Web page that places a call to Start in a postback event. The page proceeds as soon as the Start method on the WorkflowRuntime class returns. Since it is asynchronous, the Start method starts a new thread to process the workflow, and then returns immediately to ASP.NET. The page will likely reach its completion before even the world's simplest workflow has completed. As a result, the results of the workflow process can't be incorporated into the page that is sent to the final user. To render the page only after the workflow interaction has completed, you need to synchronize the page rendering with the workflow execution.

Both the persistence and the threading synchronization issues are solved through workflow services. When hosting a workflow in an ASP.NET application, you must employ a richer runtime environment that supports both a synchronous dispatch model for requests and automatic data serialization when the workflow is idle. I'll tackle that requirement here.

Back to top

Consuming a Workflow in ASP.NET

Let's review all the steps required to set up an ASP.NET page that consumes a workflow. I'll begin by building a simple workflow that includes only a Code activity, a workflow item that lets you add .NET-based code into the workflow. The example will be a workflow that implements an authentication service and validates credentials.

While in most cases using a workflow for such a task is overkill, I have worked on a couple of projects where the authentication process was quite complex, justifying the use of a workflow. Unfortunately there was no Windows Workflow Foundation around at the time. Be that as it may, what matters is that you end up with a workflow like the one in Figure 1. The workflow works by receiving a couple of parameters from its host—typically a user name and password—and returns a Boolean value that indicates whether the user has been authenticated. Figure 2 shows the list of parameters defined on the workflow.

Figure 1 Sample Authentication Workflow
Figure 1 Sample Authentication Workflow

You add parameters to a workflow by exposing public properties from the workflow class, typically backed by local members. In Figure 2, I have defined two input parameters to bring in the user name and password to authenticate. I have also defined a Boolean output parameter to return the result of the authentication process. In code, you pass input parameters to a workflow through a name/value dictionary where each entry matches the name of a public property on the workflow class. You pass the dictionary to the CreateWorkflow method that creates and runs an instance of the workflow.

Dictionary<string, object> parameters = new
Dictionary<string, object>();
parameters.Add("UserName", UserName.Text);
parameters.Add("Password", Password.Text);

 

You receive the output of a workflow through the OutputParameters collection exposed by the WorkflowCompletedEventArgs instance provided to handlers for the WorkflowCompleted event. More precisely, the OutputParameters collection contains values from all public properties on the workflow class and doesn't distinguish between read/write and read-only properties. Figure 3 shows the codebehind file of the LoginWorkflow. The event handler associated with the CheckCredentials activity calls an internal function that performs the task of authenticating the provided credentials. The result is stored in the isAuthenticated private member variable. As you can see in Figure 3, this value acts as the data repository for the IsAuthenticated output parameter.

So much for the login workflow. Let's take a look at the ASP.NET application, its pages, and configuration file.The key points to address while building an ASP.NET page that uses workflows can be seen with a relatively simple page that contains user name and password boxes, a Login button, and a message welcoming the user. When the user clicks on the Login button, you need to retrieve or create the workflow runtime and start a new instance of the workflow type you're using.

Back to top

Managing the Execution of the Workflow

Last month, I created a workflow runtime object using the new operator and the WorkflowRuntime class constructor, like so:

WorkflowRuntime wr = new WorkflowRuntime();
Using this code in ASP.NET is fine as long as you place it in Application_Start and then find a way to make the object that is created reachable from within your page code. Instantiating the workflow runtime for each request results in an exception because the workflow runtime can be loaded more than once in the same AppDomain. Two ASP.NET requests have distinct state and context, but run in the same AppDomain. As a result, the first request works while the second throws an exception.

 

Windows Workflow Foundation provides a built-in infrastructure to guarantee that there's exactly one workflow runtime object per AppDomain and that you don't have to worry about its creation and instantiation. In ASP.NET, in fact, you can use the WorkflowWebRequestContext class to access the current and correct instance of the workflow runtime. This code shows how to get a reference to the workflow runtime:

   WorkflowRuntime wr =
WorkflowWebRequestContext.Current
.WorkflowRuntime;

 

Internally, the get accessor of the Current property calls into a public member of a static class that caches the workflow runtime instance, creating a new instance if one cannot be found. Once you've got a reference to the runtime object, you can register the handler for the WorkflowCompleted event and start the actual workflow. This code shows how:

wr.WorkflowCompleted +=
new EventHandler<WorkflowCompletedEventArgs>(LoginCompleted);
Type t = typeof(Samples.LoginWorkflow);
WorkflowInstance instance = wr.CreateWorkflow(t, parameters);
instance.Start();

 

When the workflow returns, the code of LoginCompleted runs (see Figure 4) and checks the IsAuthenticated entry in the OutputParameters collection. OutputParameters is one of the event data arguments you get through the WorkflowCompletedEventArgs class. The contents of OutputParameters is determined by the runtime when the workflow completes.

The code in Figure 4 is not enough for the workflow to work correctly and for the ASP.NET page to serve users pages that incorporate workflow results. As I mentioned, you need to add a couple of services to the runtime—one for state persistence and one for synchronous request dispatching. The first idea that springs to mind is to bind services once you've got the workflow runtime instance. Here's some sample code (note that wr denotes the workflow runtime instance):

String connString = "...";
SqlWorkflowPersistenceService servState =
new SqlWorkflowPersistenceService(connString);
wr.AddService(servState);
When you run this code, you get an exception stating that you can't further configure the runtime once it has started. But who really started the runtime anyway, since there is no code in the page that would?

 

The culprit is the WorkflowWebRequestContext class I mentioned earlier. This class creates an instance of the workflow runtime from within an ASP.NET application. Then it attempts to read a section in the Web.config file named WorkflowRuntime, which is supposed to list all the services you want to bind to the runtime. Can you guess what happens? The reference to the workflow runtime you get through WorkflowWebRequestContext.Current corresponds to an instance that has already been configured with services and started. To control the services that are bound to the runtime in an ASP.NET application you need to tweak the Web.config file, as shown in Figure 5.

Back to top

Configuring ASP.NET Apps for Workflow

Windows Workflow Foundation uses the <workflowRuntime> section in the configuration file to store information for internal use. This section is not a default part of the configuration schema; for this reason, you have to register it explicitly.

<configuration>
<configSections>
<section name="WorkflowRuntime"
type="WorkflowRuntimeSection, System.Workflow.Runtime" />
</configSections>
...
</configuration>

 

In ASP.NET 2.0, when you register a new configuration section, you specify the name and assembly of a class that inherits ConfigurationSection. For the <WorkflowRuntime> section, the class is WorkflowRuntimeSection. The class is defined in the System.Workflow.Runtime assembly as part of the System.Workflow.Configuration namespace. Note that in ASP.NET 2.0, custom configuration sections are created in a different way from ASP.NET 1.x, where you had to create a class and implement the IConfigurationSectionHandler interface. The interface methods required that you parsed the XML markup below the node. In ASP.NET 2.0, the base class contains a default parser and, at least in most cases, requires that you define the expected public interface of the section through properties.

The <WorkflowRuntime> section contains a nested section named <Services> where you list all services that you want to be bound to the runtime. Windows Workflow Foundation comes with a number of predefined services including SqlWorkflowPersistenceService and ManualWorkflowSchedulerService. The former service automatically persists the workflow instance to the specified SQL Server database as soon as the workflow becomes idle (while waiting for human intervention, for instance). Even more relevant for ASP.NET applications is the scheduler service. You need the service to ensure that the ASP.NET pooled thread in charge of executing the current request waits until the workflow is completed. More precisely, the service guarantees that the execution of the workflow is synchronous and that the Start method returns only when the workflow has ended or is idle.

Back to top

Calling Workflow Components from ASP.NET

Calling workflow components from within ASP.NET is definitely possible once you resolve two issues: persistence and dispatching. It is interesting to note that, in general, you can create an instance of the workflow runtime from a configuration section—and not just a section in the ASP.NET Web.config file. You do this by using another overload of WorkflowRuntime constructor:

WorkflowRuntime wr = new WorkflowRuntime(sectionName);
The section is assumed to have the structure of the <WorkflowRuntime> section discussed earlier, but can have any name. The WorkflowWebRequestContext takes advantage of this feature and imposes the name <WorkflowRuntime> on the section of the Web.config file. But the underlying mechanism is quite general.

 

Figure 6 Web Front-End for the HelpDesk Workflow
Figure 6 Web Front-End for the HelpDesk Workflow

You can also use the <WorkflowRuntime> section to register custom services. As Figure 6 shows, the HelpDesk workflow that I created in last month's column works through an ASP.NET client. The HelpDesk workflow is based on a custom service providing external events to the workflow. In a Windows Forms client, you would use the following code:

theHelpDeskService = new HelpDeskService();
wr.AddService(theHelpDeskService);
In an ASP.NET client, you resort to an extra node under the <Services> section of the <WorkflowRuntime> element:
<Services>
<add type="Samples.HelpDeskService, HelpDeskWorkflow" />
</Services>

 

There are two main types of workflow: sequential and state machine. Sequential workflows represent a sequence of activities and terminate only when all activities have been completed. State machine workflows are an event-driven, asynchronous graph of activities in which each activity represents a state and can transition to a well-known subset of states. Both types of workflow can be consumed from within ASP.NET applications. Interestingly, you could even craft a user interface, wrap it up in a Web Part, and host the workflow in a SharePoint® (Service Pack 2) application or in a portal-like ASP.NET 2.0 application.

Sequential workflows, though, might be dangerous for ASP.NET applications because they express potentially lengthy operations. A sequential workflow that doesn't stop waiting for external events might be engaged in long-running transactions and represents a serious threat to the scalability of the overall application.

Asynchronous pages are the perfect countermeasure for pages that need to perform long tasks, as Jeff Prosise explained in his October 2005 Wicked Code column. Asynchronous ASP.NET pages are based on a pair of Begin/End methods. As of Beta 2 of Windows Workflow Foundation, there's no such pair of built-in methods to start a workflow. However, a Windows Workflow Foundation workflow can be published as a Web service and, just like a Web service, can be invoked asynchronously through the ad hoc methods of the Web service proxy class.

Back to top

Exposing a Workflow as a Web Service
Figure 7 Workflow
Figure 7 Workflow

The Windows Workflow Foundation framework makes it possible for you to expose a workflow as a Web service to .NET-based clients as well as to other workflows. To expose a workflow as a Web service, you have to write it in a special manner. Only a workflow that uses the WebServiceReceive activity can be published as a Web service. The idea is that you create a workflow project and, at least in the simplest case, add a single pair of WebServiceReceive and WebServiceResponse activities to it. In more complex cases you might want to use a Listen activity to receive input about which activity, or set of activities, should run. In the simplest common case the workflow is activated by calling a method on the Web service, proceeds to the end, and returns output values.

The interface exposed through the Web service is defined inside the workflow class. After creating, say, a new sequential workflow, you add an interface definition to the codebehind class:

public interface ILoginWorkflow
{
bool Authenticate(string user, string pswd);
}
Only the methods listed in the interface will be callable from external clients through the Web service. The structure of the workflow may be as simple as a pair of WebServiceReceive and WebServiceResponse activities (see Figure 7).

 

The WebServiceReceive activity is bound to a particular method of the Web service interface and receives data when the method is called. For example, you bind the WebServiceReceive activity to the Authenticate method of the ILoginWorkflow interface. The input arguments of the method must be bound to public members of the workflow class:

   public string receivedUser = default(System.String);
public string receivedPswd = default(System.String);

 

The WebServiceResponse activity responds to a request made through a Web service interface. Note that you can only use a WebServiceResponse activity in a workflow that also has a WebServiceReceive activity. An essential property of the activity is ReceiveActivityId, which indicates the ID of the related WebServiceReceive activity. You can only use WebServiceReceive activities located in the same workflow. The return value of the method must also be bound to a public member in the workflow class:

public bool returnedAuthenticate = default(System.Boolean);

 

Basically, the interaction between the client the Web service, and the workflow can summarized as follows. The client references the Web service and places a call to one of its methods. The Web service starts the workflow and invokes the WebServiceReceive activity, passing input parameters. The receive activity caches the input data. Possible intermediate activities (Code, Parallel, If, and so on) run as usual. At the end, the WebServiceResponse activity is invoked to generate the return value. The WebServiceResponse activity is expected to cache the return value into the bound public member, the Boolean returnedAuthenticate member in the previous line of code.

A workflow exposed through a Web service can be thought of as a regular workflow that is wrapped by a pair of WebServiceReceive and WebServiceResponse activities.

To generate handlers that manage input and output data, you right-click on the receive/response activities and click Generate Handlers. At the minimum, do that for WebServiceResponse so that some return value can be sent to the caller. When you're done with the code, you right-click on the project and select Publish as Web service (see Figure 8). The workflow designer creates a small Web service project with a Web.config file, an .asmx endpoint, and the assembly that contains the workflow. Then you should deploy these files on IIS 6.0 and call the workflow.

Figure 8 Publishing a Workflow
Figure 8 Publishing a Workflow

It is interesting to take a look at the Web.config file that is generated for you. In addition to the <WorkflowRuntime> section that I discussed earlier in the column, the configuration requires an HTTP module:

<httpModules>
<add type="System.Workflow.Runtime.Hosting.WorkflowWebHostingModule,
System.Workflow.Runtime"
name="WorkflowHost"/>
</httpModules>
The HTTP module is responsible for instantiating and managing the workflow runtime and routing Web service requests to the appropriate workflow instance. The HTTP module maps incoming requests to the right workflow instance using the ASP.NET session state information.

 

In production code, you should significantly increase the timeout of your session state (the default is 20 minutes) and opt for database storage of session state to ensure that workflow instance IDs are not lost in the event of AppDomain shutdown and process recycling. Note that the HTTP module is designed to work well in Web farm scenarios.

The HTTP module leverages the ASP.NET session state locking mechanism to ensure that requests directed at the same workflow instance are serialized. The ASP.NET session state locking mechanism guarantees that requests in the same session are queued. Due to this locking mechanism, you cannot have interleaved receive/response pairs in a workflow. Most of the time, to expose a workflow through a Web service, you simply take an existing workflow and wrap it with a pair of receive/response activities. The input for the workflow is gathered by the WebServiceReceive activity and the WebServiceResponse component collects the return values.

Back to top

Conclusion

ASP.NET applications are yet another possible host for Windows Workflow Foundation workflows. Due to the special nature of ASP.NET applications, hosting a workflow requires a bit of attention. In particular, you need to enrich the run-time environment with a couple of run-time services that come out of the box—namely the threading and SQL Server persistence services.

The threading service, in particular, ensures that the page is rendered only when the workflow completes or becomes idle. What if a workflow takes too long to complete? In this case, you can consider exposing the workflow as a Web service and binding it to an ASP.NET asynchronous page. Having workflow exposed as Web services also increases the reach of the workflow and allows you to call it from other platforms.

Back to top

Send your questions and comments for Dino to  cutting@microsoft.com.
Download Image NEW: Explore the sample code online! - or - Code download available at: CuttingEdge0604.exe (153KB)

Dino Esposito is a mentor at Solid Quality Learning and the author of Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Based in Italy, Dino is a frequent speaker at industry events worldwide. Get in touch with Dino by e-mailing him at cutting@microsoft.com or join the blog at weblogs.asp.net/despos.






--------------------------------------------------------------------
以下内容转载自http://www.devx.com/dotnet/Article/29992






--------------------------------------------------------------------
以下内容转载自http://tech.ccidnet.com/art/1110/20030624/862153_1.html
 

简介   通过使用WWF,你可以创建基于处理器流的工作流并且把它们部署在任何类型的.NET应用程序中。此外,本文还讨论了ASP.NET开发者面对的一些特有的问题-这些问题可能通过使用工作流得到解决,如维持状态和页面导航等。   在2005年9月,微软在它的一年两次的专业开发者会议上公开了Windows Workflow Foundation(WWF,Windows工作流基础)。作为WinFX API的支柱之一,WWF提供给开发者一个普通框架-在其上开发过程驱动的和以工作流为中心的应用程序。   当前,有些组织力图把整个商业过程自动化;他们的标准答案就是集合一队开发者来开发相应的代码。尽管这种方式对于这些组织带来良好的作用,然而也有一些固有的问题。为了深入理解这一问题,你需要理解一个工作流的基本特征。   一个工作流本质是一种方法-用来归档包含在完成一个单元的工作中的活动。典型地,在处理过程中,工作流流过一项或更多活动。这些活动可以通过机器或人工来实现,并且有可能象在一个互联网应用程序定义页面顺序一样得简单,也有可能象管理必须为任何数目的人都要看到、更改并同意的文件或产品一样得复杂。   因为如此多的工作流必须考虑到人工参预,所以可能需要花费很长工期才能完成,时间可能为几小时到数月或更长。例如,参预在该过程中的人可能无法找到,不在本地或忙于另外的任务;因此,工作流必须在所有非活动期间能够把自身持续性存储。而且,通过编码独立实现的过程可能对非技术人员难于理解而对开发者却难于更改。这一点和其它一些因素正是例如WindowsWF等通用工作流框架的目标-其目的就在于使创建、改变和管理工作流更容易-这是通过向它们提供一个可视化接口或通过定义一组普通API来实现的。   你可以把WWF工作流放置在任何类型的.NET应用程序中-包括Windows表单程序,控制台应用程序,Windows服务和ASP.NET Web应用程序。每种类型都需要专门的考虑。尽管一些现有示例已经足够说明如何把工作流宿主到Windows表单程序和控制台应用程序中,但是本文将集中于讨论ASP.NET开发者的问题-他们希望把工作流集成到自己的应用程序中。    作者注:本文所提供的代码是以Windows WF Beta 1和Visual Studio 2005 Beta 2 为工具创建的。你可以在www.windowsworkflow.net找到有关安装Windows WF的信息。尽管本文讨论了Windows WF的一些基础问题,但是还有其它一些这方面的可用资源。我假定读者至少了解一点Windows WF。本文的目的是深度分析Windows WF和ASP.NET,而不是从一个高层次上讨论Windows WF。    一、 Windows WF和MVC模式   在开发一个ASP.NET应用程序时,你可能使用WWF的一个普通的方法是实现一种模型-视图-控制器(MVC)方法。实质上,MVC的目标是把描述层、应用程序逻辑和应用程序流逻辑分离开来。   搞清楚这个将十分有益于一个ASP.NET应用程序的开发,请考虑一个帮助桌面票工作流的场所。假定有一个商业用户通过填写一个ASP.NET Web表单并点击一个提交按钮来启动该工作流。接下来,服务器就会通知一个使用Windows表单应用程序和帮助桌面的雇员--有新票可用了。该帮助桌面雇员然后将在这一问题上工作,并在最后关闭该票。如果使用Windows WF来开发这个工作流情形,那么所有的处理逻辑和流程可以被包含在工作流本身,而该ASP.NET应用程序将完全不需要了解这一逻辑。   这种场所提供了一些稳固的证据-把描述与逻辑相分离是一件好事情。因为这个处理帮助桌面请求的过程是非常普通的,如果使用C#或VB.NET代码在若干不同的.NET应用程序中实现这一逻辑,那么你将会冒着重复编码的危险甚至更坏的情形--用完全不同的代码导致同样的商业处理过程的不同实现。但是如果你使用WWF来实现这一过程,那么需要这一过程的应用程序开发者将仅需在一处修改这些步骤-工作流本身-而不必担心这样会改变应用程序逻辑。代码复制和在哪里实现该过程可以通过Windows WF的使用来加以缓和。   当使用Windows WF在ASP.NET中实现MVC架构时,开发者应该尝试构建独立于应用程序的工作流-而该工作流仍然宿主于该应用程序中。这将有助于保持逻辑独立于描述并且保持在该Web应用程序中的工作步骤顺序和页面流之间的高度独立性。   一个WWF开发新手可能试图用一固定数目的活动以某种顺序去开发一个工作流,然后开发一组ASP.NET Web表单--这些表单以与之相同的顺序从一个表单流向另一个表单。很遗憾,尽管这看上去挺符合逻辑,但是实际上这是非常不具有生产效率的,因为你将会再次实现这个工作流逻辑。Web页面X不需要知道是否它需要转到页面Y或页面Z来正确地实现该工作流步骤。代之的是,该工作流(模型)应该告诉ASP.NET(控制器)下一步该干什么;然后ASP.NET应该决定要显示哪个页面。这样,每个页面几乎不需要了解整个过程;它仅需要知道怎样完成一个不同的活动并且让该工作流来关心页面是如何从一处流向另一处的。这种分离在开发者处理页面流时带来了一种极大的灵活性。例如,如果你决定改变该页面显示顺序,那么你可以从工作流中容易地实现这一点,而不需要改变该ASP.NET应用程序中的一行代码。    二、 一个简单的工作流MVC实例   为了说明这一思想,我将向你展示一个简单ASP.NET应用程序和工作流。这个过度简化的工作流描述了一个进度-收集一些来自于一外部应用程序的私人信息,然后显示它。步骤如下:   1. 调用一个方法--这意味着请求一个人的名字;该工作流使用了InvokeMethod活动(见图1)。   2. 等待直到一个事件被激发--这意味着收到一个名字;在这一步中,该工作流使用了EventSink活动。   3. 使用一类似调用,从宿主获得一个电子邮件地址。   4. 等待一个事件意味着收到一个地址。   5. 在收到名字和电子邮件以后,该工作流启动一个InvokeMethod活动来发送个人资料到调用者应用程序。在一种真实世界情形,这最后一步并不很重要。更可能的是,你将调用一个Web服务来发送数据到另外的系统,或把它放进一数据库。

图1.示例工作流:这个工作流描述了隐含在示例ASP.NET应用程序中的过程

  为了在ASP.NET中实现这个工作流,你需要一个页面来收集人名,一个页面来收集电子邮件地址和一个页面来显示个人资料。记住,数据登录表单应该丝毫不知道之前或之后所发生的一切。对于显示页面也是如此。然而,该ASP.NET应用程序必须了解要把哪个页面显示给用户;这正是引入控制器的目的之所在。这个示例使用一个Http处理器来实现该解决方案。这个称为WorkflowController的定制的处理器负责下列任务:   ·获得到工作流运行时刻的一个参考。   ·获得一个到已有的或启动一个新的工作流实例的参考(这依赖于是否已启动一个工作流实例)。   ·建立控制器和工作流之间的通讯。   ·处理来自该工作流的事件。   ·告诉ASP.NET需要显示哪个页面,这依赖于现在正执行该工作流中的哪一层。   你已看到,这个定制的处理器实质上负责处理所有的与WWF和页面控制相关的工作--让单个的ASP.NET页面对在后台正在进行的动作保持缄默。Web表单需要担心的唯一的事情是执行手头特定的任务并且把必要的数据传递到控制器。   默认地,WWF以一个异步的模型工作。这意味着,当一个应用程序宿主启动一个工作流实例时,控制立即返回到该宿主,而该工作流继续在另一个线程上执行。这在一个Windows表单应用程序中可能是很有用的-其中十分期盼用户接口的连续响应性。通过使用这个异步的模型,工作流可以在后台执行而该用户可以继续操作该应用程序。然而,在一个Web应用程序中,可能不期望这种类型的行为,因为在服务器完成一个单元的工作后控制通常将只返回到用户。这正是Windows WF的可扩展性的体现。在Windows WF中,开发者可以利用或创建运行时刻服务来监控甚至修改该工作流运行时刻。该示例包括:   ·持续性服务-存储执行和空闲时间之间的工作流状态   ·追踪服务-输出有关工作流执行的信息到某种媒体   ·事务服务-帮助维持工作流执行过程中的数据完整性   另外,线程服务让开发者控制工作流实例的执行方式。如前面所讨论的,工作流运行时刻默认地将在一个独立于宿主的线程上异步地运行实例。但是由于这很可能不是ASP.NET所期望的,所以你需要交换默认工作流线程服务。幸运的是,微软已经为此提供了一种解决方案--ASPNetThreadingService。为了实现这一变化,你或者可以手工编码方式把ASPNetThreadingService添加到工作流运行时刻服务,或在web.config文件中完成这一任务。本文中的示例应用程序使用了配置方式。在web.config(见列表1)的工作流运行时刻/服务节中,添加类似下列的这一行: <add type= System.Workflow.Runtime.Hosting.ASPNetThreadingService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35/>

三、 实现控制器逻辑   接下来,你需要以一个Http处理器来实现控制器逻辑。为了构建该控制器,所有你需要做的是创建一个称为WorkflowController处理器的类-它实现IHttp处理器接口。到目前为止,你还没有看到有关Windows WF的任何特别的东西-这是特别针对于ASP.NET的功能(请继续往下读)。   在这个WorkflowController处理器类中,名为ProcessRequest的IHttp处理器接口方法处理一个来自于该ASP.NET应用程序的Web请求。这里,你需要获得到一个静态的工作流运行时刻实例的一个参考,为该工作流建立事件处理器,并且启动工作流的执行。在启动一个工作流实例之前,你需要检查是否该请求的查询串值包含一个代表一个工作流实例ID的GUID。如果存在这个ID,你就知道已经有一个实例正在运行,这样你可以得到一个到该实例的参考并继续执行。如果不存在这个ID,你需要通过调用工作流运行时刻Start Workflow方法来创建一个新的实例并且开始执行过程。   在启动一个实例后,事件处理器将管理进出工作流的通讯。因为本文的目的不是讨论本地通讯服务,所以在此我不会详细讨论这个主题,而是分析其高级的实现技术,并再次讨论这在一个ASP.NET应用程序中是如果工作的。为了便利通讯处理,你将需要若干.NET接口--用于描述出/入该工作流和宿主的信息。你会在本文所附WorkflowClassLibrary工程源码中找到这一些。你还会找到一些实现这些接口的类以及实现工作流机制所必须的相应功能。   让我们再简单地看一下web.config文件。注意,在早些时候讨论的ASPNetThreadingService元素附近,我们使用了三个元素来描述通讯服务类: <add type=Workflow.RuntimeServices.GetNameService, Workflow.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c4620ae819b5257e/> <add type=Workflow.RuntimeServices.GetEmailService, Workflow.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c4620ae819b5257e/> <add type=Workflow.RuntimeServices.SendDataService, Workflow.Library, Version=1.0.0.0, Culture=neutral, PublicKeyToken=c4620ae819b5257e/>   这里还有一个元素,它指导工作流运行时刻库来使用SqlStatePersistenceService。这种另外的服务把一个工作流的状态持续性存储到一个在页面请求之间的SQL服务器数据库之中。你必须提前手工地创建这个数据库,但是微软提供了SQL脚本来做这件事情。你将会在C:\WINDOWS\Microsoft.NET\Framework\v2.0.50215\Windows Workflow Foundation\SQL文件夹下找到它们。就象模型服务一样,你可以编程地添加这些服务,但是你也可以在配置中实现它,这将会大大降低代码的编写量并且提供灵活性-甚至在代码生产之后。而且在web.config中有一行,它添加一个HttpModule-它支持在ASP.NET中的Windows WF运行时刻库;还有一行用于设置更早些时候讨论的Http处理器控制器。如你所见,在这个配置中存在许多的东西。   作为结论,Windows WF为开发者在其上开发基于工作流的应用程序提供了一个极其易用和可扩展的框架。实现商业过程已经并将继续成为一种重要的应用程序技术。除非你是一个技术服务公司或ISV,否则软件是一定要提供商业及其运行过程的。通过使用如Windows WF这样的工具,开发者可以使得开发过程容易且灵活。






--------------------------------------------------------------------
以下内容转载自http://dev.yesky.com/msdn/190/2241190.shtml

利用WWF进行ASP.NET程序开发

2005-12-20 10:13 作者: 朱先中编译 出处: 天极开发 责任编辑:方舟
  简介

  通过使用WWF,你可以创建基于处理器流的工作流并且把它们部署在任何类型的.NET应用程序中。此外,本文还讨论了ASP.NET开发者面对的一些特有的问题-这些问题可能通过使用工作流得到解决,如维持状态和页面导航等。

  在2005年9月,微软在它的一年两次的专业开发者会议上公开了Windows Workflow Foundation(WWF,Windows工作流基础)。作为WinFX API的支柱之一,WWF提供给开发者一个普通框架-在其上开发过程驱动的和以工作流为中心的应用程序。

  当前,有些组织力图把整个商业过程自动化;他们的标准答案就是集合一队开发者来开发相应的代码。尽管这种方式对于这些组织带来良好的作用,然而也有一些固有的问题。为了深入理解这一问题,你需要理解一个工作流的基本特征。

  一个工作流本质是一种方法-用来归档包含在完成一个单元的工作中的活动。典型地,在处理过程中,工作"流"流过一项或更多活动。这些活动可以通过机器或人工来实现,并且有可能象在一个互联网应用程序定义页面顺序一样得简单,也有可能象管理必须为任何数目的人都要看到、更改并同意的文件或产品一样得复杂。

  因为如此多的工作流必须考虑到人工参预,所以可能需要花费很长工期才能完成,时间可能为几小时到数月或更长。例如,参预在该过程中的人可能无法找到,不在本地或忙于另外的任务;因此,工作流必须在所有非活动期间能够把自身持续性存储。而且,通过编码独立实现的过程可能对非技术人员难于理解而对开发者却难于更改。这一点和其它一些因素正是例如WindowsWF等通用工作流框架的目标-其目的就在于使创建、改变和管理工作流更容易-这是通过向它们提供一个可视化接口或通过定义一组普通API来实现的。

  你可以把WWF工作流放置在任何类型的.NET应用程序中-包括Windows表单程序,控制台应用程序,Windows服务和ASP.NET Web应用程序。每种类型都需要专门的考虑。尽管一些现有示例已经足够说明如何把工作流宿主到Windows表单程序和控制台应用程序中,但是本文将集中于讨论ASP.NET开发者的问题-他们希望把工作流集成到自己的应用程序中。

  作者注:本文所提供的代码是以Windows WF Beta 1和Visual Studio 2005 Beta 2 为工具创建的。你可以在www.windowsworkflow.net找到有关安装Windows WF的信息。尽管本文讨论了Windows WF的一些基础问题,但是还有其它一些这方面的可用资源。我假定读者至少了解一点Windows WF。本文的目的是深度分析Windows WF和ASP.NET,而不是从一个高层次上讨论Windows WF。

  一、 Windows WF和MVC模式

  在开发一个ASP.NET应用程序时,你可能使用WWF的一个普通的方法是实现一种模型-视图-控制器(MVC)方法。实质上,MVC的目标是把描述层、应用程序逻辑和应用程序流逻辑分离开来。

  搞清楚这个将十分有益于一个ASP.NET应用程序的开发,请考虑一个帮助桌面票工作流的场所。假定有一个商业用户通过填写一个ASP.NET Web表单并点击一个提交按钮来启动该工作流。接下来,服务器就会通知一个使用Windows表单应用程序和帮助桌面的雇员--"有新票可用了"。该帮助桌面雇员然后将在这一问题上工作,并在最后关闭该票。如果使用Windows WF来开发这个工作流情形,那么所有的处理逻辑和流程可以被包含在工作流本身,而该ASP.NET应用程序将完全不需要了解这一逻辑。

  这种场所提供了一些稳固的证据-把描述与逻辑相分离是一件好事情。因为这个处理帮助桌面请求的过程是非常普通的,如果使用C#或VB.NET代码在若干不同的.NET应用程序中实现这一逻辑,那么你将会冒着重复编码的危险甚至更坏的情形--用完全不同的代码导致同样的商业处理过程的不同实现。但是如果你使用WWF来实现这一过程,那么需要这一过程的应用程序开发者将仅需在一处修改这些步骤-工作流本身-而不必担心这样会改变应用程序逻辑。代码复制和在哪里实现该过程可以通过Windows WF的使用来加以缓和。

  当使用Windows WF在ASP.NET中实现MVC架构时,开发者应该尝试构建独立于应用程序的工作流-而该工作流仍然宿主于该应用程序中。这将有助于保持逻辑独立于描述并且保持在该Web应用程序中的工作步骤顺序和页面流之间的高度独立性。

  一个WWF开发新手可能试图用一固定数目的活动以某种顺序去开发一个工作流,然后开发一组ASP.NET Web表单--这些表单以与之相同的顺序从一个表单流向另一个表单。很遗憾,尽管这看上去挺符合逻辑,但是实际上这是非常不具有生产效率的,因为你将会再次实现这个工作流逻辑。Web页面X不需要知道是否它需要转到页面Y或页面Z来正确地实现该工作流步骤。代之的是,该工作流(模型)应该告诉ASP.NET(控制器)下一步该干什么;然后ASP.NET应该决定要显示哪个页面。这样,每个页面几乎不需要了解整个过程;它仅需要知道怎样完成一个不同的活动并且让该工作流来关心页面是如何从一处流向另一处的。这种分离在开发者处理页面流时带来了一种极大的灵活性。例如,如果你决定改变该页面显示顺序,那么你可以从工作流中容易地实现这一点,而不需要改变该ASP.NET应用程序中的一行代码。

  二、 一个简单的工作流MVC实例

  为了说明这一思想,我将向你展示一个简单ASP.NET应用程序和工作流。这个过度简化的工作流描述了一个进度-收集一些来自于一外部应用程序的私人信息,然后显示它。步骤如下:

  1. 调用一个方法--这意味着请求一个人的名字;该工作流使用了InvokeMethod活动(见图1)。

  2. 等待直到一个事件被激发--这意味着收到一个名字;在这一步中,该工作流使用了EventSink活动。

  3. 使用一类似调用,从宿主获得一个电子邮件地址。

  4. 等待一个事件意味着收到一个地址。

  5. 在收到名字和电子邮件以后,该工作流启动一个InvokeMethod活动来发送个人资料到调用者应用程序。在一种真实世界情形,这最后一步并不很重要。更可能的是,你将调用一个Web服务来发送数据到另外的系统,或把它放进一数据库。


图1.示例工作流:这个工作流描述了隐含在示例ASP.NET应用程序中的过程

  为了在ASP.NET中实现这个工作流,你需要一个页面来收集人名,一个页面来收集电子邮件地址和一个页面来显示个人资料。记住,数据登录表单应该丝毫不知道之前或之后所发生的一切。对于显示页面也是如此。然而,该ASP.NET应用程序必须了解要把哪个页面显示给用户;这正是引入控制器的目的之所在。这个示例使用一个Http处理器来实现该解决方案。这个称为WorkflowController的定制的处理器负责下列任务:

  ·获得到工作流运行时刻的一个参考。

  ·获得一个到已有的或启动一个新的工作流实例的参考(这依赖于是否已启动一个工作流实例)。

  ·建立控制器和工作流之间的通讯。

  ·处理来自该工作流的事件。

  ·告诉ASP.NET需要显示哪个页面,这依赖于现在正执行该工作流中的哪一层。

  你已看到,这个定制的处理器实质上负责处理所有的与WWF和页面控制相关的工作--让单个的ASP.NET页面对在后台正在进行的动作保持"缄默"。Web表单需要担心的唯一的事情是执行手头特定的任务并且把必要的数据传递到控制器。

  默认地,WWF以一个异步的模型工作。这意味着,当一个应用程序宿主启动一个工作流实例时,控制立即返回到该宿主,而该工作流继续在另一个线程上执行。这在一个Windows表单应用程序中可能是很有用的-其中十分期盼用户接口的连续响应性。通过使用这个异步的模型,工作流可以在后台执行而该用户可以继续操作该应用程序。然而,在一个Web应用程序中,可能不期望这种类型的行为,因为在服务器完成一个单元的工作后控制通常将只返回到用户。这正是Windows WF的可扩展性的体现。在Windows WF中,开发者可以利用或创建"运行时刻服务"来监控甚至修改该工作流运行时刻。该示例包括:

  ·持续性服务-存储执行和空闲时间之间的工作流状态

  ·追踪服务-输出有关工作流执行的信息到某种媒体

  ·事务服务-帮助维持工作流执行过程中的数据完整性

  另外,线程服务让开发者控制工作流实例的执行方式。如前面所讨论的,工作流运行时刻默认地将在一个独立于宿主的线程上异步地运行实例。但是由于这很可能不是ASP.NET所期望的,所以你需要交换默认工作流线程服务。幸运的是,微软已经为此提供了一种解决方案--ASPNetThreadingService。为了实现这一变化,你或者可以手工编码方式把ASPNetThreadingService添加到工作流运行时刻服务,或在web.config文件中完成这一任务。本文中的示例应用程序使用了配置方式。在web.config(见列表1)的工作流运行时刻/服务节中,添加类似下列的这一行:

<add type=
"System.Workflow.Runtime.Hosting.ASPNetThreadingService,
System.Workflow.Runtime, Version=3.0.00000.0,
Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

三、 实现控制器逻辑

  接下来,你需要以一个Http处理器来实现控制器逻辑。为了构建该控制器,所有你需要做的是创建一个称为WorkflowController处理器的类-它实现IHttp处理器接口。到目前为止,你还没有看到有关Windows WF的任何特别的东西-这是特别针对于ASP.NET的功能(请继续往下读)。

  在这个WorkflowController处理器类中,名为ProcessRequest的IHttp处理器接口方法处理一个来自于该ASP.NET应用程序的Web请求。这里,你需要获得到一个静态的工作流运行时刻实例的一个参考,为该工作流建立事件处理器,并且启动工作流的执行。在启动一个工作流实例之前,你需要检查是否该请求的查询串值包含一个代表一个工作流实例ID的GUID。如果存在这个ID,你就知道已经有一个实例正在运行,这样你可以得到一个到该实例的参考并继续执行。如果不存在这个ID,你需要通过调用工作流运行时刻Start Workflow方法来创建一个新的实例并且开始执行过程。

  在启动一个实例后,事件处理器将管理进出工作流的通讯。因为本文的目的不是讨论本地通讯服务,所以在此我不会详细讨论这个主题,而是分析其高级的实现技术,并再次讨论这在一个ASP.NET应用程序中是如果工作的。为了便利通讯处理,你将需要若干.NET接口--用于描述出/入该工作流和宿主的信息。你会在本文所附WorkflowClassLibrary工程源码中找到这一些。你还会找到一些实现这些接口的类以及实现工作流机制所必须的相应功能。

  让我们再简单地看一下web.config文件。注意,在早些时候讨论的ASPNetThreadingService元素附近,我们使用了三个元素来描述通讯服务类:

<add type="Workflow.RuntimeServices.GetNameService,
Workflow.Library, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=c4620ae819b5257e"/>
<add type="Workflow.RuntimeServices.GetEmailService,
Workflow.Library, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=c4620ae819b5257e"/>
<add type="Workflow.RuntimeServices.SendDataService,
Workflow.Library, Version=1.0.0.0, Culture=neutral,
PublicKeyToken=c4620ae819b5257e"/>

  这里还有一个元素,它指导工作流运行时刻库来使用SqlStatePersistenceService。这种另外的服务把一个工作流的状态持续性存储到一个在页面请求之间的SQL服务器数据库之中。你必须提前手工地创建这个数据库,但是微软提供了SQL脚本来做这件事情。你将会在C:\WINDOWS\Microsoft.NET\Framework\v2.0.50215\Windows Workflow Foundation\SQL文件夹下找到它们。就象模型服务一样,你可以编程地添加这些服务,但是你也可以在配置中实现它,这将会大大降低代码的编写量并且提供灵活性-甚至在代码生产之后。而且在web.config中有一行,它添加一个HttpModule-它支持在ASP.NET中的Windows WF运行时刻库;还有一行用于设置更早些时候讨论的Http处理器控制器。如你所见,在这个配置中存在许多的东西。

  作为结论,Windows WF为开发者在其上开发基于工作流的应用程序提供了一个极其易用和可扩展的框架。实现商业过程已经并将继续成为一种重要的应用程序技术。除非你是一个技术服务公司或ISV,否则软件是一定要提供商业及其运行过程的。通过使用如Windows WF这样的工具,开发者可以使得开发过程容易且灵活。






--------------------------------------------------------------------
以下内容转载自http://weblogs.asp.net/scottgu/archive/2006/08/31/Windows-Workflow-Foundation.aspx

Windows Workflow Foundation

Workflow is one of the new core capabilities (along with WPF aka Avalon and WCF aka Indigo) being added in the .NET Framework 3.0 release later this year.  It provides an in-process workflow engine to process rules, a designer for VS 2005 to enable both developers and non-developers to define custom workflow processes graphically, and a new Workflow namespace to integrate these within code.  The official site to learn more about Windows Workflow Foundation can be found here.

Over the last two weeks I've also seen a number of great new posts and web-casts published that cover it in more detail.  Below is a list of some of them you might want to explore to learn more:

Windows Workflow Foundation Basics:

.NET Rocks! Audio-Cast: Carl Franklin talks with Michael Stiefel about Windows Workflow Foundation and discusses what it is and why you might want to use.  You can listen to this show here.

Bart De Smet has a number of great posts that cover some of the core concepts with Windows Workflow Foundation (WF):

K. Scott Allen has also posted several good blog posts on Workflow:

ASP.NET Page Flow:

ASP.NET developers should checkout the ASP.NET: An Overview of ASP.NET and Windows Workflow Foundation Integration web-cast that Kashif Alam from the ASP.NET team did earlier this month.  It explores designing and developing UI workflow applications with ASP.NET and how developers will be able to use the new "Page Flow" capabilities that are being added to ASP.NET to enable developers to create representative UI for business processes defined with Windows Workflow Foundation (and avoid hardcoding in workflow logic in code).

For other great ASP.NET web-casts (both upcoming as well as past ones that have been recorded and published online), please check out this Web Casts listing page on the www.asp.net site.

SharePoint 2007 Custom Workflows:

SharePoint 2007 (which is coming out later this year and is built on ASP.NET 2.0) allows users to define and author workflows for common process activities.  Sahil has a great blog post here that describes how to use this to define a custom process for a SharePoint site.

Hope this helps,

Scott

 --------------------------------------------------
内容转载自http://www.odetocode.com/Articles/465.aspx

ASP.NET and Windows Workflows Foundation

Posted by on Sunday, February 11, 2007

This article and sample application takes a look at the architecture and patterns used to build a realistic WF sample in ASP.NET.
Programming Windows Workflow Foundation by Scott Allen Programming Windows Workflow Foundation If you enjoyed this article, you'll enjoy the book even more! Order now from Packt Publishing and save 10%!

Combining ASP.NET 2.0 and Windows Workflow Foundation (WF) will provide us with all the essential tools for building workflow-enabled web applications. Both ASP.NET and WF, however, are significant pieces of technology. Joining the two into a long-term relationship requires some careful thought and planning. In this article, we will look at an order processing application built for the web, and walk through the design. The application includes practical abstractions that shield the web application from the details of the workflow runtime. The application also addresses some real world scenarios. For instance, we'll keep our application state consistent with our workflow state by enlisting our database work inside the same transactions used by the WF tracking and persistence services.

Windows Workflow and ASP.NET In Harmony

Full source code to this sample is available for download. After extracting the solution files, see the readme.htm file for installation tips. This article doesn't go into great detail on how to use Windows Workflow Foundation, or how to use ASP.NET, but does highlight important decisions to make when combining the two, and tries to point to other resources for additional information.

wfOrders

The requirements have arrived from the product manager and we're building an order tracking application. What sort of orders? Well, these could be book orders, or bicycle orders – we don't care about the details. What we do care about is the requirement to track the order from creation to completion, with a number of steps in-between. We also need to record the processing history of each order, and provide our non-technical business experts some insight into the order-processing pipeline. Finally, our product manager wants us to provide an intelligent user interface that can, for example, prevent a user from accidently marking an order as shipped when the warehouse has yet to process the order.

These requirements play to the strengths of Windows Workflow Foundation. Microsoft designed WF to manage long-running processes, and provide transparency into both the design and the execution of these processes. An out of the box tracking service can record the processing history for each order, and the state machine workflow type provided by WF can prevent a user from accidently short-circuiting the process.

Enough of the marketing spiel though, let’s builds something.

The State Machine

For this application we are going to use a state machine workflow, and this workflow will be the first piece of the sample we will build (see OrderStateMachine.xoml in the project wfOrderWorkflows). Before authoring a new workflow, one has to choose between a sequential workflow and a state machine workflow. Both models have their relative strengths and weaknesses. My article "State Machines In Windows Workflow" highlights the differences and offers an in-depth look at the design and execution of state machines. We won't cover those basics in this article.

Let's say we've discussed the life of an order with the business people and we have a clear picture of the business process. From the discussion we've determined an order is always in one of 3 states: open, processed, or completed. An order is "open" when a customer first creates the order, and moves to a "processed" state when it is ready to ship. If the customer changes the order in this state, the order will return to the "open" state. When we ship a processed order, (or the customer cancels the order), the order moves to a completed state and the workflow is finished. We can start to model this process by dragging the state activities we need from the workflow toolbox window into a new state machine workflow.

A first cut at the state machine

At this point we only have empty states -we haven't created events and transitions between states. Notice we actually have 4 states in the workflow. We've decided to add an "initial" state that represents the step just before an order is actually created. We want the creation step to be a part of the workflow, and this "Initial" state represents the stage of the process just before an order is created. We'll see advantages to this approach later.

The Contract

A state machine workflow needs the outside world to raise events and drive the workflow to completion. These events represent real actions that can happen to an order. In talking with the business people, we’ve decided there are 5 distinct actions that can occur to an order during its lifetime. An order can be created, updated, processed, canceled, and shipped.

To deliver these events to the workflow, we'll need to define a communications contract. In .NET, an interface type defines a software contract. The following interface is decorated with the ExternalDataExchange attribute. This attribute is allows WF to recognize this interface as a contract to deliver workflow related events.

[ExternalDataExchange]
public interface IOrderService
{
    
event EventHandler<OrderEventArgs> OrderCreated;
    
event EventHandler<OrderEventArgs> OrderShipped;
    
event EventHandler<OrderEventArgs> OrderUpdated;
    
event EventHandler<OrderEventArgs> OrderProcessed;
    
event EventHandler<OrderEventArgs> OrderCanceled;
}

The OrderEventArgs class we are using in the interface represents the data we are delivering to WF. In some scenarios, simply raising an event to a workflow provides enough information for a state machine to act. However, we want to pass in a "business object" or "domain object" that represents the order being acted on by the user. We'll find out later that WF will use this object to update an order record in the database, however, by passing in the order object we could perform validations and run business rules inside the workflow and before allowing the order to progress to a new state. Passing the order gives us greater flexibility and power inside the WF code. We will define our event arguments class with following code. 

[Serializable]
public class OrderEventArgs : ExternalDataEventArgs
{
    
public OrderEventArgs(Guid instanceId, Order order)
        :
base(instanceId)
    {
        
Check.ArgumentIsNotNull(instanceId, "instanceId");
        
Check.ArgumentIsNotNull(order, "order");

        _order = order;
    }

    
private Order _order;
    
public Order Order
    {
        
get { return _order; }
        
set { _order = value; }
    }   
}

The Order class itself is defined in the wfOrderCommon project. You can assume it is a typical "business" object with properties that ultimately map to columns in one or more database tables.

Note: it is important for the event arguments to be 100% serializable. Since the event arguments contain an instance of the Order class, the Order class, will also need to be serializable.In this sample it is enough to decorate both with the Serializable attribute.

At some point, we will need to provide a concrete implementation of this interface, but for now we can finish our state machine.

The Activities

Again, if you are not familiar with the design and execution of state machine workflow, I encourage you to go back and read upon the basics, as this section is going to move fast.

With an external data exchange interface in place, we can start to add EventDriven activities to our workflow. These activities will listen for events to arrive from the outside world. The outside world could be an ASP.NET application, or could be a Windows Forms smart client – the workflow doesn't (and shouldn't) care. It is at this point that we will decide which events are legal events in each state, and where those events will lead. When we are done adding all the activities, our workflow will look like the following.

A fleshed out state machine

We can see that when the order is in the "Open" state, it will accept the OrderUpdated and the OrderProcessed events, but not the OrderShipped event (we set the Name property on each EventDriven activity to match the name of the event it will listen for - a highly recommend practice). If we click on an EventDriven activity, we can add additional activities that will execute when the associated event arrives. Below shows a completed EventDriven activity for the ProcessedOrder event.

 EventDriven activity details

Inside we've placed three activities. The first activity is a HandleExternalEvent (HEC) activity. The HEC is configured to wait for an OrderProcessed event to arrive from a service implementing the IOrderService interface we defined earlier. If that event arrives, control will continue to the next activity, a SetState activity. The SetState activity tells the state machine to transition to the Processed state (this activity has focus, and you can see the configuration in the Properties window of the screen shot).

You might be wondering why the SetState activity is not the last activity in the sequence, as it would seem logical that the last thing we want to do is transition to the next state. It turns out that WF will transition to the state targeted by the last SetState activity that executes, even if that SetState activity is not the last activity to execute. In this case, our last activity (a custom activity we will discuss soon), actually binds to some meta properties of the SetState activity, and it works better to have our custom activity appear after SetState (to avoid compiler warnings about binding to an activity that executes later in the sequence).

Our last activity is special. Our last activity is a custom activity that encapsulates the updating and saving of an order to the database. In addition to saving all the order details, like the order description, this custom activity will also update a State property on the business object representing an order. This State property will ultimately be saved into a column in our database table with other orders. By keeping the current status of an order in the same table as the order itself, we've made it easy for the user interface and other components to quickly inspect the current status of all orders.

Let me expand on the above paragraph a bit. Our UI will ultimately want to present a grid view of orders in the system, like the one seen in the beginning of the article. We'll want to display the current state for each order, and this state should reflect the current state of the workflow associated with the order. There are actually many techniques we could use to retrieve (or record) the status of any given order. We could use a WF tracking service, as Matt Milner shows in his blog post: "Tracking the current state in a state machine workflow". Another approach is to use a StateMachineWorkflowInstance object, but this doesn't scale well if we need to display multiple orders. We will use the StateMachineWorkflowInstance class for another purpose later in this article.

Our approach is to save the state of an Order into the Order table of the database. Anyone issuing a "SELECT * FROM Orders" query will see the current status and all other information for all orders. How will our custom activity know what state the Order will be in? We will bind the State property on our custom activity to the TargetStateName property of the previous SetState activity. See "Using Dependency Properties" for more information on activity binding.

custom activity property binding

Before we show some more details of our custom activity, let's talk about how we will get this information into the database in a reliable manner.

The Transaction

We'll be making use of the WF SQL Persistence Service. Why? Well, it may take days or weeks for an order to move from the "Open" state to the "Completed" state. We can't rely on the web server staying up 100% of the time for weeks on end, or having enough memory to keep millions of order workflows in RAM. We need a place to save our workflows, and this is the job of a workflow persistence service. If you want more details on the SQL persistence service, see my article on "Hosting Windows Workflow".

In short, when our workflow reaches a point where it has nothing to do but wait for an event to arrive, the workflow runtime will see the workflow is idle and ask the persistence service (if one is configured) to take action. The SQL persistence service will serialize the workflow and shove the resulting bits into a SQL Server table. The service can then unload the workflow instance from memory. Weeks later, when a new event arrives (like the order finally shipped), the WF runtime will ask the persistence service to rehydrate the workflow. The runtime can than deliver the new event to the workflow and the workflow can resume execution.

You might wonder what will happen if we save our updated Order object to the database, but the persistence service fails to connect to SQL Server and throws an exception. We'd have inconsistent data in our database, which is always bad for business!. It would be best if our custom activity could work inside the same database transaction that the persistence service will use (and the tracking service, too). We can ensure that our Order table and the workflow will both agree on the state of all orders, regardless of catastrophic database or network errors. Working with transactions in WF is possible using a variety of techniques. See "Workflows and Transactions" and Windows Workflow Runtime Service: The Transaction Service" for more details from WF team members.

Our approach will be to interact with a transaction service. This means our workflow doesn't explicitly model a transaction in the workflow with a TransactionScope activity. Instead, our custom activity will shield a workflow developer from the intricacies of transactions by implicitly working with a transaction service. For details on building custom activities, see Matt Milner's article "Build Custom Activities To Extend The Reach Of Your Workflows". Let's look at the Execute method of our custom activity.

protected override ActivityExecutionStatus Execute(
                
ActivityExecutionContext executionContext)
{
    
if (Order != null)
    {
        Order.State = State;
        Order.WorkflowId = WorkflowInstanceId;
        
        
IOrderTransactionService txnService =
            executionContext.GetService<
IOrderTransactionService>();
        txnService.UpdateOrder(Order);                
    }

    
return ActivityExecutionStatus.Closed;
}

Our activity doesn't work with the database directly, but asks for a service implementing the IOrderTransactionService interface. The custom activity asks this transaction service to do the work. A transaction service in WF has to implement the IPendingWork interface. First, we define an interface for our transaction service.

public interface IOrderTransactionService : IPendingWork
{
    
void UpdateOrder(Order order);
}

We implement this interface with the following concrete class.

class OrderTransactionService : IOrderTransactionService
{
    #region IOrderTransactionService Members

    
public void UpdateOrder(Order order)
    {
        
Check.ArgumentIsNotNull(order, "order");
        
        
WorkflowEnvironment.WorkBatch.Add(this, order);
    }

    #endregion

    #region
IPendingWork Members

    
public void Commit(Transaction transaction, ICollection items)
    {
        
Check.ArgumentIsNotNull(transaction, "transaction");
        
Check.ArgumentIsNotNull(items, "items");

        
OrderGateway gateway = new OrderGateway();

        
foreach (object o in items)
        {
            
Order order = o as Order;
            
Check.IsNotNull(order,
                
"Unexpected object in items work batch collection");

            
if (order.Id == 0)
            {
                gateway.InsertOrder(order);
            }
            
else
            {
                gateway.UpdateOrder(order);
            }
        }
    }

    
public void Complete(bool succeeded, ICollection items)
    {
        
// nothing to cleanup
    }

    
public bool MustCommit(System.Collections.ICollection items)
    {
        
return items != null && items.Count > 0;
    }

    #endregion
}

The first method, the UpdateOrder method that our custom activity invokes, doesn't deal with the database either. Instead, it adds the order to the workflow's current work batch. Window s Workflow will collect batch items from our service, the persistence service, and the transaction service, and when the time is right will ask them all to commit after it has started an ambient transaction (a System.Transactions.Transaction). WF will call our service's Commit method, and our data access code will automatically enlist in this transaction (assuming we are using SQL Server). We've hidden the actual data access code behind an OrderGateway class, but you could easily use ADO.NET classes or other persistence approaches inside of Commit.

At this point we are fairly complete with everything our workflow needs. Now it's time to turn our attention to higher layers in the application and begin to think about how ASP.NET will interact with WF. We also need to build our service that will implement IOrderService, and raise events to the workflow on behalf of ASP.NET. 

The Mediator

Chances are that we don't want to put code that interacts directly with Windows Workflow inside our web forms. As we will see, it's not trivial to run a workflow and immediately fetch the results. We are going to build a mediator class that will take care of the workflow goo and make life easy for the UI developer. Before we look at our mediator class, I want to talk about one special consideration when using WF in ASP.NET.

Manual Scheduling

A scheduling service in Windows Workflow is responsible for acquiring a thread for the WF runtime to execute a workflow. For details on scheduling services, see my article on "Hosting Windows Workflow".

If you know about the scheduling services, then you know the default scheduling service will acquire threads from the CLR background thread pool. This default approach works well for smart client applications where threads are plentiful. For a server environment, however, using more background threads can starve the CLR thread pool. Besides, we don't really need to tie up two threads for every HTTP request when all we want to do is block the first thread until a workflow completes. Thus, we we want to skip the default scheduler and configure the WF runtime to use the ManualWorkflowSchedulerService for our ASP.NET apps.

The manual scheduling service does not actively acquire a thread. Instead, the manual service waits for the host to donate a thread, and the WF runtime will use the donated thread to execute a workflow. Using the manual scheduler we can use one thread to process the HTTP request and run the workflow synchronously. 

One catch that might not be obvious is that we need to explicitly run a workflow by donating a thread whenever we deliver an event to a workflow. If you are familiar with Windows programming, this is similar to "pumping messages" with a thread to keep a window responsive. Keep this in mind as we progress.

Running Workflows

Let's now take a look at a few of the WorkflowMediator methods. Remember, this is a class we are building to hide workflow complexities. In most applications the mediator will be a singleton that is globally available. The mediator is responsible for creating a WorkflowRuntime instance, but doesn't need to expose the WF runtime to the outside world. Applying your favorite dependency injection / inversion of control pattern with the mediator means you can unit test other components that use workflows without involving the workflow runtime.

public WorkflowResults RunWorkflow(Type workflowType)
{
    
Check.ArgumentIsNotNull(workflowType, "workflowType");

    
WorkflowInstance instance =
        _workflowRuntime.CreateWorkflow(workflowType);
    instance.Start();

    
bool result = WorkflowScheduler.RunWorkflow(instance.InstanceId);
    
Check.IsTrue(result, "Could not run workflow "
                        + instance.InstanceId);

    
return CurrentResultsInContext;
}

public WorkflowResults RunWorkflow(Guid instanceId)
{
    
Check.IsNotTrue(instanceId == Guid.Empty, "Invalid Workflow Instance ID");

    
WorkflowInstance instance = _workflowRuntime.GetWorkflow(instanceId);
    
Check.IsNotNull(instance, "Could not retrieve workflow");

    
bool result = WorkflowScheduler.RunWorkflow(instance.InstanceId);
    
Check.IsTrue(result, "Could not run workflow "
                        + instance.InstanceId);

    
return CurrentResultsInContext;
}

public ManualWorkflowSchedulerService WorkflowScheduler
{
    
get
    {
        
return _workflowRuntime.GetService
                    <
ManualWorkflowSchedulerService>();
    }
}

The WorkflowMediator class exposes two methods to run a workflow. The first method takes a Type parameter; the second version takes a Guid parameter. The first method assumes the workflow hasn't been created and will ask the WF runtime to create a new workflow instance. After calling Start on the workflow instance (which puts the workflow in a runnable state),the method uses the RunWorkflow method of the manual scheduler to execute the workflow. The second method uses the incoming GUID parameter and RunWorkflow to re-start an existing workflow that is probably idle and waiting to process an event.

When we call RunWorkflow, we hand our thread over to the WF runtime. The WF runtime will use this thread to execute the workflow until the workflow completes, terminates, aborts, or goes idle. Once one of these actions occurs the method call returns and we have control of our thread again. But how do we know what exactly happened to our workflow? Did it complete successfully? Did it terminate with an exception? The RunWorkflow method only returns a boolean value. A value of true means the workflow started running. This value doesn't tell us why the workflow ended. We need to look at the workflow lifetime events published by the workflow runtime.

Workflow Results

To understand what happens during execution of a workflow we need to subscribe to events published by the WorkflowRuntime. These events include WorkflowCompleted,WorkflowTerminated, and WorkflowIdled, among many others. We have to be careful when subscribing to these events. Most server-side applications will use a single WorkflowRuntime instance to run multiple workflows, we can create memory leaks and introduce race conditions if we don't handle the events properly. See my posts on managing workflow events in ASP.NET Part I and Part II for more details.

Since the goal of our WorkflowMediator is to hide the complexities of workflow from the rest of the application, the mediator takes care of subscribing to these events and translating the events into return values from its own RunWorkflow method. Let's take a look at a couple of the event handlers.

void _workflowRuntime_WorkflowTerminated(
    
object sender, WorkflowTerminatedEventArgs e)
{
    CurrentResultsInContext =
        
WorkflowResults.CreateTerminatedWorkflowResults(e)
}

void _workflowRuntime_WorkflowCompleted(
    
object sender, WorkflowCompletedEventArgs e)
{
    CurrentResultsInContext =
        
WorkflowResults.CreateCompletedWorkflowResults(e);
}

These event handlers take event arguments from the workflow runtime and create a WorkflowResults object. WorkflowResults is another class we've written in the wfOrderServices project. It has properties that can hold information for any kind of workflow event. Static factory methods call private constructors that look like the following.

public static WorkflowResults CreateCompletedWorkflowResults
                                (
WorkflowCompletedEventArgs args)
{
    
WorkflowResults results = new WorkflowResults(args);
    results._status =
WorkflowStatus.Completed;
    
return results;
}

public static WorkflowResults CreateTerminatedWorkflowResults
                      ``          (
WorkflowTerminatedEventArgs args)
{
    
WorkflowResults results = new WorkflowResults(args);
    results._status =
WorkflowStatus.Terminated;
    
return results;
}

private WorkflowResults(WorkflowCompletedEventArgs args)
{
    Check.ArgumentIsNotNull(args,
"args");
    _outputs = args.OutputParameters;
    _instanceId = args.WorkflowInstance.InstanceId;
    _definition = args.WorkflowDefinition;
}

private WorkflowResults(WorkflowTerminatedEventArgs args)
{
    Check.ArgumentIsNotNull(args,
"args");
    _instanceId = args.WorkflowInstance.InstanceId;
    _exception = args.Exception;
}

We also have a Status property on our WorkflowResults class. We've defined a WorkflowStatus enum that can be one of the following values: Completed, Terminated, Aborted, or Running. We can infer the current status of a workflow based on the type of event we receive. A WorkflowCompleted event means the workflow is completed- likewise for the Terminated and Aborted events. If we receive a WorkflowIdled event, we can assume the workflow is still running, but it has just gone idle with no work to do until a new event arrives.

The next challange is to deliver these results to someone who cares. To do this we'll need a bit of context.

The Context Service

When working with the manual workflow scheduler, it is safe to assume that the whoever called RunWorkflow donated the thread that executed the workflow and raised an event. If we place our WorkflowResults into a "context" for the current thread, then the caller can pull these results out of the context when it regains control of the thread.

In an ASP.NET application, we can use the current HttpContext Items collection as the context for our current thread. (One caveat here: if you use Delay activities with and configure active timers for the manual scheduling service, these events will happen on a background thread that is not associated with an HTTP request). We are not going to use HttpContext directly, however, we will hide it behind a service abstraction. After the WorkflowMediator recieves a workflow event, it places a new WorkflowResults object into a property called CurrentResultsInContext. This property is implemented with the following code.

public WorkflowResults CurrentResultsInContext
{
    
get { return _context.Items[_contextKey] as WorkflowResults; }
    
set
    {
        
if(_context.Items.Contains(_contextKey))
        {
            _context.Items[_contextKey] =
value;
        }
        
else
        {
            _context.Items.Add(_contextKey,
value);
        }
    }
}

The property is using a context service that is stored in a field named _context. This service implements the following interface.

public interface IContextService
{
    IDictionary Items
    {
        
get;
    }
}

If we are running under ASP.NET, we can provide a concrete implementation of the context service that looks like the following.

public class HttpContextWrapper : IContextService
{
    
public IDictionary Items
    {
        
get
        {
            
return HttpContext.Current.Items;
        }
    }
}

Back in the WorkflowMediator, it's now easy to pull back the results of running a workflow and return these results to the caller.

public WorkflowResults RunWorkflow(Type workflowType)
{
    
//...
    return CurrentResultsInContext;
}

We now have one additional piece of infrastructure to work with. Although the WorkflowMediator may look like a great deal of work, it contains relatively few lines of code. The code provides a good abstraction layer over top the underlying workflow engine, and decouples other services from relying on knowledge of the WF runtime. We are now ready to move to the next layer.

The Order Service

Early in this article we defined an IOrderService interface. This interface provided the communication contract between our workflow and the host. We are now ready to provide a concrete implementation of this service. There were 5 events that the interface will force us to implement, but we'll add some additional methods that will make this service easy to use from ASP.NET. The OrderService, defined in wfOrderServices, is the only "workflow" API we will be using from our ASP.NET web form.

We'll design our OrderService with methods that correspond to actions that can happen to an Order. We'll provide CreateOrder, ShipOrder, UpdateOrder, and CancelOrder methods. Below is our implementation for ShipOrder.

public bool ShipOrder(Order order)
{
    
Check.ArgumentIsNotNull(order, "order");

    
bool eventResult = RaiseEvent(OrderShipped, order, order.WorkflowId);
    
if (eventResult == true)
    {
        
WorkflowResults workflowResults =
            
WorkflowMediator.Instance.RunWorkflow(order.WorkflowId);
        
Check.IsNotNull(workflowResults, "Could not harvest workflow results");
        
        VerifyResults(workflowResults,
WorkflowStatus.Completed);

    }
    
return eventResult;
}

Most of the methods in our service follow a basic pattern. First, the method will raise an event that corresponds to the order action. In this case, we'll raise the OrderShipped event using a helper method we've defined called RaiseEvent – more on that method in a minute. This event should eventually reach our workflow. The methods then use the WorkflowMediator to run the workflow, and the ensuing WorkflowResults object is then inspected to ensure the workflow is doing what we expect. For instance, after the OrderShipped event is processed by our workflow, we expect the workflow to stop execution (remember the workflow results contain the status of the workflow itself, not the state of our order, although the state of our order is also complete).

Notice the OrderService doesn't have to deal directly with the WorkflowRuntime or its associated events, but it does have some knowledge of how the workflow should proceed for an order. This is a comfortable abstraction as we've separated concerns a bit. The WorkflowMediator concentrates on managing the WF runtime and its events, while the OrderService concentrates on managing the "big picture" workflow for an order. After each call into the WorkflowMediator, the OrderService will verify the state of the workflow using a VerifyResults method. This ensures the workflow didn't terminate with an exception. For all of our other methods, we ensure the workflow is still running. We pass VerifyResults the actual results received, and the expected status of the workflow (Running or Complete).   

private void VerifyResults(WorkflowResults workflowResults, WorkflowStatus status)
{
    
if (workflowResults.Status != status)
    {
        
if (workflowResults.Exception != null)
        {
            
throw workflowResults.Exception;
        }
        
else
        {
            
string expected = status.ToString();
            
string actual = workflowResults.Status.ToString();
            
throw new InvalidOperationException(
                
String.Format("Workflow {0} expected status {1} actual status {2}",
                                workflowResults.InstanceId, expected, actual));
        }
    }
}

If an exception occurred during the workflow processing, we'll throw the exception. If the workflow isn't in the state we expected, we'll also throw an exception.

Now, let's return to the RaiseEvent helper method. This method is shown below.

private bool RaiseEvent(EventHandler<OrderEventArgs> ev,
                        
Order order, Guid instanceId)
{
    
bool result = true;
    
try
    {

        
if (ev != null)
        {
            
OrderEventArgs e = new OrderEventArgs(instanceId, order);
            ev(
null, e);
        }
    }
    
catch (EventDeliveryFailedException)
    {
        result =
false;
    }
    
    
return result;
}

What jumps out immediately is that we are eating the EventDeliveryFailedException. This is a judgment call. Let me give you a common scenario for the EventDeliveryFailedException and you can decide for your own application the best approach to take.

Let's say I'm looking at Order #5 and know that the warehouse justprocessed order #5 and the order is ready to ship. I press the "Process Order" button in the UI to process the order. Unfortunately, my co-worker who is sitting in another room with the application open also knows that Order #5 is ready to ship. She clicked the button 5 seconds before I did and processed the order. When my event arrives, the state machine will throw an exception. As far as the state machine is concerned, it's not legal to "process" an order in the "processed" state. The exception is an EventDeliveryFailedException exception. In a web application with concurrent activity this isn't an entirely exceptional circumstance. In this sample I decided to return false, and the UI layer can ultimately deal with the failure to process (perhaps just refresh the screen to show a more recent order status). You'll have to decide on the best approach for your application in this scenario.

 

With all our services in place, we are finally ready to move to the presentation layer.

our little helpers

Finally, ASP.NET

For an article with "ASP.NET" in the title, we've sure done a great deal of work without seeing a single button click event handler. This is good news, actually, because we now have all the abstractions needed for an ASP.NET developer to use Windows Workflow with little grief. In fact, the ASP.NET developer doesn't know that Windows Workflow is inside the application and silently persisting and tracking order workflows as they execute. The ASP.NET application is in the wfOrderWeb project in the accompanying download.

The following piece of code is the event handler for the "Create Order" button.

protected void createOrderButton_Click(object sender, EventArgs e)
{
    
Order order = Order.CreateOrder(); // just creates an object instance - no DB activity
    order.Title = titleTextBox.Text;
    titleTextBox.Text =
"";

    
OrderService.Instance.CreateOrder(order);

    DataBindGridView();
}

The ASP.NET page tells the OrderService to create a new order, and then refreshes the GridView that appears on the screen. We've simplified life in the code-behind file tremendously, and the ASP.NET developer can concentrate on providing a good user experience.

Windows Workflow and ASP.NET In Harmony

One feature of our ASP.NET application is that the code will enable and disable buttons on the form. The available buttons are based on the available transitions in our workflow model. For instance, when an order is shipped, the bottom row of buttons will all be disabled. When an order is in the open state, the "Ship Order" button will be disabled. We can do this by calling into the OrderService and asking what transitions are available for any given order.

public OrderTransitions GetAvailableOrderTransitions(int orderId)
{
    
Order order = GetOrderById(orderId);
    
Check.IsNotNull(order, "Could not fetch order " + orderId);
  
    
OrderTransitions result = OrderTransitions.None;
    
ReadOnlyCollection<string> transitions =
        
WorkflowMediator.Instance.GetStateMachineTransitions(
            order.WorkflowId);

    
if (transitions.Count > 0)
    {
        result |=
OrderTransitions.CanCancel;
    }

    
foreach (string stateName in transitions)
    {
        
if (stateName == "Open")
            result |=
OrderTransitions.CanOpen;
        
if (stateName == "Processed")
            result |=
OrderTransitions.CanProcess;
        
if (stateName == "Completed")
            result |=
OrderTransitions.CanComplete;
    }

    
return result;
}

The OrderService will ask the WorkflowMediator to find out the available transitions for an order, and we set the flags in an OrderTransitions enumeration to match these transitions. The WorkflowMediator is querying the state machine using the StateMachingWorkflowInstance class, which you can read more about in my "State Machines In Windows Workflow" article.

Conclusions

Joining Windows Workflow and ASP.NET into a testable, flexible, maintainable application requires a bit of work, but the same could be said for almost any technology. What have we gained with WF? We've gained transparency in the sense that any developer, or even business person, can look at our workflow model in the workflow designer see how an order moves from Open to Completed. We have a UI that is driven by this model, and that will prevent users from accidently shipping a processed order. Although we haven't talked about the WF tracking service, we can instantly record a history of execution for each order by simply configuring this service into the runtime. We have automatic support for our long-running order processing by virtue of using the WF SQL persistence service. There are also a host of WF features we haven't taken advantage of, like using a WF Policy activity inside our state machine to execute a set of declarative business rules to validate an order.

There are an infinite number of ways to use Windows Workflow Foundation in ASP.NET, and this sample looks at just one approach. Hopefully this article and accompanying download will give you some ideas and code to build upon. If you have questions or comments, you can reach me through my blog.

 

by K. Scott Allen


posted on 2008-01-10 23:39 Ray Zhang 阅读(2983) 评论(0)  编辑 收藏

导航

公告



真实姓名:张大磊(Ray Zhang)

言论仅为个人当前时间之观点
转载敬请联系作者并注明出处
昵称:Ray Zhang
园龄:4年11个月
粉丝:25
关注:3

统计

搜索

 
 

常用链接

最新随笔

随笔分类(31)

随笔档案(65)

文章档案(7)

相册

Healthcare

MAC

Private

现实中的朋友

最新评论