请假 2.0 - Windows Workflow Foundation (WF) 和 DotNetNuke™






3.57/5 (5投票s)
一个 DotNetNuke 模块,与托管在开源项目 IWebWF 中的 WF 工作流进行通信。
- 下载 VacationRequest - 66.63 KB (Workflow 源代码)
- 下载 VacationRequestWorkflow - 10.02 KB (Windows Workflow,需要安装在 IWebWF 中)
- 下载 Vacation_Request_02 - 25.49 KB (DNN 模块。请先在 DNN4 中运行 LinqPrep)
引言
Windows WorkFlow (WF) 是一个强大的框架,用于创建企业级工作流。强大的功能也带来了巨大的复杂性。WF 的很大一部分是“自己动手”,它提供了核心组件,但很多必需的元素需要您自己提供。工作流的托管方式有很多种,本文将介绍使用 .asmx Web 服务托管工作流。
这个示例项目展示了一个完整 WF 解决方案,由一个 DotNetNuke 模块和一个托管在开源项目 IWebWF (IWebWF.com) 中的 WF 工作流组成。该项目名为“请假”,允许用户提交休假申请。申请将使用以下业务规则由工作流处理:
- 如果员工有足够的可用天数并且申请少于 4 天,则自动批准申请。
- 如果可用天数不足,或申请超过 3 天,则需要批准。
本文将使用以下大纲来探讨该项目:
- 请假 2.0 概述
- 设置
- 提交申请
- 如何动态调用 Web 服务
- 批准申请
- 创建 CheckDigit 并调用请假工作流 Web 服务
- 调用 DNN Web 服务并更新记录
- 日志记录
- DNN 模块
- 历史记录页面
- IWebWF
- 使用 IWebCore 进行日志记录和发送电子邮件
- 状态页面显示当前持久化的工作流
- 详细信息页面标识工作流
- DNN 模块
- 安全
- 工作流是“断开连接”的。它只通过 Web 服务进行输入和输出。
- CheckDigit 用于打开和关闭“门”。
请假 2.0 概述
下图显示了“请假”应用程序的基本结构:
应用程序的“前端” resides 在一个运行在 DotNetNuke 网站中的模块里。“后端”处理 resides 在运行在 IWebWF 网站中的工作流里。DotNetNuke 模块和工作流通过 Web 服务进行通信。
DotNetNuke 模块公开以下 Web 服务:
- UpdateVacationRequest - 允许工作流更新请假记录。
- GetUserDetails - 提供请假详情,包括用户可用的天数。
工作流公开以下 Web 服务:
- StartWorkflow - 允许 DNN 模块启动请假工作流。
- ApproveRequest - 允许 DNN 模块批准请假。
WF 工作流可以通过多种方式托管。工作流甚至可以托管在 DotNetNuke 网站中。选择此设计是因为它允许通过添加多个 IWebWF 应用程序实例来扩展解决方案。此外,工作流组件和持久化服务可能非常消耗资源。最好将这些从 DotNetNuke 网站中卸载。
Windows Communication Foundation (WCF) 可用于替代本解决方案中描述的 .asmx Web 服务。虽然这可行,但 WCF 在构建提供不必要挑战的 WCF 服务时会带来复杂性,例如需要复杂的配置文件来绑定协议和传输。
设置
要设置应用程序,您需要安装 IWebWF 和 DotNetNuke。您可以从: http://www.codeplex.com/IWebWF 下载 IWebWF,从: http://DotNetNuke.com 下载 DotNetNuke。
IWebWF
安装 IWebWF 后,以管理员身份登录,配置并测试电子邮件设置。
解压 VacationRequestWorkflow.zip 文件中的文件,将 VacationRequest.dll 文件放在“Bin”目录中,将 VacationRequest.asmx 文件放在“Webservice”目录中。
DotNetNuke
使用 标准的 DotNetNuke 模块安装流程 安装 Vacation Request_02.00.00_Install.zip 模块(如果使用 DNN4,请先运行 LinqPrep)。
以管理员身份登录,然后点击 [设置 Web 服务 URL] 链接,将 Web 服务链接指向 IWebWF 网站中的 VacationRequest.asmx 页面。

[编辑用户请假天数](从模块主页面)允许管理员设置用户的可用天数。
另外,请确保 DNN 管理员帐户有一个电子邮件地址,并且 SMTP 设置已在 DNN 网站中配置。
提交申请
请假申请在 DNN 模块中开始。它会向工作流 Web 服务发出 Web 服务调用。DNN 模块创建一个随机数并将其作为 CheckDigit 保存到数据库中。RequestID、CheckDigit、门户管理员和用户的电子邮件地址将传递给工作流 Web 服务。
在 DNN 中创建代理以调用外部 Web 服务
该项目被编译,然后创建的 VacationWebService.dll 文件被放置在 DNN 网站的“Bin”目录中。这允许您使用类似以下的代码来实例化该类:
VacationRequestWorkflow_WebService wsVacationRequest = new VacationRequestWorkflow_WebService();并使用类似以下的代码动态更改 Web 服务地址:
// Set the address to the web service wsVacationRequest.Url = GetWebServiceURL();以下代码展示了创建随机 CheckDigit 代码并调用工作流 Web 服务的完整代码:
// Create a record VacationRequestDAL VacationRequestDAL = new VacationRequestDAL(); VacationRequest VacationRequest = new VacationRequest(); VacationRequest.Approved = false; VacationRequest.CheckDigit = GetRandomNumber(); VacationRequest.Complete = false; VacationRequest.CreatedDate = DateTime.Now; VacationRequest.DateRequested = Convert.ToDateTime(txtRequestedDate.Text); VacationRequest.DaysRequested = Convert.ToInt32(txtRequestedDays.Text); VacationRequest.LastActiveDate = DateTime.Now; VacationRequest.NeedsApproval = false; VacationRequest.UserID = UserId; VacationRequest.WorkflowInstanceId = ""; VacationRequestDAL.VacationRequests.InsertOnSubmit(VacationRequest); VacationRequestDAL.SubmitChanges(); // Reference to the web service VacationRequestWorkflow_WebService wsVacationRequest = new VacationRequestWorkflow_WebService(); // Enable cookies wsVacationRequest.CookieContainer = new System.Net.CookieContainer(); // Set the address to the web service wsVacationRequest.Url = GetWebServiceURL(); // Call the method to start the workflow Guid WorkflowInstanceID = wsVacationRequest.StartWorkflow(VacationRequest.RequestID, VacationRequest.CheckDigit, GetLocalWebserviceURL(), PortalSettings.Email, UserInfo.Email); // Update the WorkflowInstanceID UpdateWorkflowInstanceID(VacationRequest.RequestID, WorkflowInstanceID); pnlVacationRequest.Visible = false; lblError.Text = "Request submitted. You will receive an email with a response";
请假工作流
VacationRequest 工作流有一个接口,定义了它公开的 Web 服务(StartWorkflow 和 ApproveRequest)。
public interface IVacationRequest { Guid StartWorkflow(int parmRecordID, int parmCheckDigit, string parmWebService, string parmAdministratorEmailstring, string parmEmployeeEmail); bool ApproveRequest(int parmRecordID, int parmCheckDigit, bool parmApproval); }
VacationRequestWorkflow.cs 文件包含工作流的逻辑。
StartVacationRequest 活动绑定到 StartWorkflow Web 方法,该方法启动工作流。工作流启动后,wsGetUserDetails 活动调用 DNN 网站中的 GetUserDetails Web 服务。
工作流能够通过将一个方法连接到 Invoking 事件并设置指向 DNN 模块中 Web 服务的 .asmx Web 代理的 URL 来动态设置 Web 地址。
// Set the URL to the web service to the URL that was passed whe the Workflow was started wsVacationRequest.WebService objWebService = (wsVacationRequest.WebService)e.WebServiceProxy; objWebService.Url = WebService;
工作流调用 DNN 网站中的此 Web 服务方法,该方法返回关于请假的信息。
public UserDetails GetUserDetails(int RecordID, int CheckDigit) { UserDetails UserDetails = new UserDetails(); VacationRequestDAL VacationRequestDAL = new VacationRequestDAL(); // Search for a matching record var VacationRequestsResult = (from VacationRequests in VacationRequestDAL.VacationRequests where VacationRequests.RequestID == RecordID & VacationRequests.CheckDigit == CheckDigit & VacationRequests.CheckDigit != 0 select VacationRequests).FirstOrDefault(); // If the record is found return the result if (VacationRequestsResult != null) { var VacationDaysResult = (from Days in VacationRequestDAL.VacationDays where Days.UserID == VacationRequestsResult.UserID select Days).FirstOrDefault(); UserDetails.RequestedDate = VacationRequestsResult.DateRequested; UserDetails.DaysRequested = VacationRequestsResult.DaysRequested; UserDetails.VacationDays = VacationDaysResult.VacationDays; } else { // Set the values so that the Workflow service will know the request is bad // The workflow will receive these values and terminate the workflow UserDetails.RequestedDate = new DateTime(1900,1,1); UserDetails.DaysRequested = 0; UserDetails.VacationDays = 0; } return UserDetails; }
在工作流收到关于请假的信息后,流程到达 ifElseActivity_ProcessRequest 决策点。根据从 DNN Web 服务接收到的值,可能会处理三个可能的活动组。
当您右键单击 TerminateWorkflowBranch...
并选择“属性”时,您将能够看到确定该分支执行的规则条件(申请的天数,请假天数为 0)。
对于 NeedsApprovalBranch,可以执行相同的过程来查看规则条件(申请的天数超过 3 天,或申请的天数超过请假天数,或请假天数减去申请的天数小于 0)。
(有关使用规则引擎的更多信息,请参阅本文)
ApprovedBranch 没有规则条件,并且将在 TerminateWorkflowBranch 和 NeedsApprovalBranch 的规则条件不满足时默认执行(ifElseActivity 分支从左到右进行评估)。
批准申请
如果申请天数和请假天数均为 0,工作流将终止。如果申请少于 4 天且有足够的可用天数,则申请将自动批准。将向用户发送电子邮件,工作流终止。
对于所有其他情况,将执行 NeedsApprovalBranch 分支。此分支包含调用 DNN 网站中 Web 服务的活动以更新记录。它将记录设置为需要批准,并向管理员发送电子邮件,指示有一个记录需要批准。
然后工作流继续到 WaitForApproval 活动。此活动包含 ApprovalWebService 组。
WaitForApproval 活动是一个 WhileActivity,它将循环直到满足 Code Condition。只有当 CheckIsTimeToStop 方法返回 true 时,它才会退出循环。
wsApprovalRequest_Input 活动是 ApprovalWebService 组中的第一个活动。wsApprovalRequest_Input 活动绑定到工作流中的 ApproveRequest Web 方法。它将等待来自 DNN 网站的 Web 服务调用来批准申请。
DNN 模块
当以管理员身份登录 DNN 网站时,[批准申请] 链接将带您进入批准页面。
以下代码创建一个新的随机 CheckDigit 并调用工作流 Web 服务:
bool boolApproval = false; string strCommandArgument = (string)e.CommandArgument; int intRecordID = Convert.ToInt32(e.CommandName); int CheckDigit = GetRandomNumber(); Guid WorkflowInstanceID = UpdateCheckDigitAndGetWorkflowInstanceID(intRecordID, CheckDigit); if (strCommandArgument == "Approve") { // Approve boolApproval = true; } else { // Decline boolApproval = false; } // Reference to the web service VacationRequestWorkflow_WebService wsVacationRequest = new VacationRequestWorkflow_WebService(); // Enable cookies wsVacationRequest.CookieContainer = new System.Net.CookieContainer(); // Set the address to the web service wsVacationRequest.Url = GetWebServiceURL(); // Create a URI Uri VacationRequestUri = new Uri(wsVacationRequest.Url); // Enable cookies wsVacationRequest.CookieContainer = new System.Net.CookieContainer(); // Use the URI to obtain a collection of the cookies CookieCollection mycollection = wsVacationRequest.CookieContainer.GetCookies(VacationRequestUri); // Add the current WorkflowInstanceId to the cookie collection // that will be passed to the web service wsVacationRequest.CookieContainer.SetCookies ( VacationRequestUri, String.Format("{0}={1}", "WF_WorkflowInstanceId", WorkflowInstanceID.ToString()) ); // Call the method on the workflow wsVacationRequest.ApproveRequest(intRecordID, CheckDigit, boolApproval);
请假工作流
工作流接收来自 DNN 网站的批准调用,并处理 ApprovalProcess 活动组中的活动。wsUpdateVacationRequestApproval 活动调用 DNN 网站中的以下 UpdateVacationRequest Web 服务方法来更新记录:
public bool UpdateVacationRequest(int RecordID, int CheckDigit, int VacationDays, bool NeedsApproval, bool Approved, bool Complete) { VacationRequestDAL VacationRequestDAL = new VacationRequestDAL(); // Search for a matching record var VacationRequestsResult = (from VacationRequests in VacationRequestDAL.VacationRequests where VacationRequests.RequestID == RecordID & VacationRequests.CheckDigit == CheckDigit & VacationRequests.CheckDigit != 0 select VacationRequests).FirstOrDefault(); // Only update if record is found if (VacationRequestsResult != null) { VacationRequestsResult.Approved = Approved; VacationRequestsResult.Complete = Complete; VacationRequestsResult.NeedsApproval = NeedsApproval; VacationRequestsResult.LastActiveDate = DateTime.Now; // Set Check Digit to 0 so that the record can not be updated by web services VacationRequestsResult.CheckDigit = 0; // Update Vacation Days var VacationDaysResult = (from Days in VacationRequestDAL.VacationDays where Days.UserID == VacationRequestsResult.UserID select Days).FirstOrDefault(); VacationDaysResult.VacationDays = VacationDays; // Commit changes VacationRequestDAL.SubmitChanges(); } return true; }
在工作流中,将 isTimeToStop 值设置为 true,这将导致 CheckIsTimeToStop 方法返回 true。
#region CheckIsTimeToStop // This method will return the value of isTimeToStop // The value of isTimeToStop will be set by other // methods in the workflow private void CheckIsTimeToStop(object sender, ConditionalEventArgs e) { e.Result = !(isTimeToStop); } #endregion这将导致 WaitForApproval 活动停止,工作流终止。流程完成。
日志记录
当工作流运行时,如果没有日志记录,很难了解发生了什么。
DNN 模块通过点击 [历史记录] 链接(以管理员身份登录时)提供了一些日志记录。
VacationRequest 工作流项目包含对 IWebCore 项目的引用,允许它使用类似以下代码轻松记录操作:
#region WriteToIWebWFLog private void WriteToIWebWFLog(string LogEvent) { ApplicationLog.AddToLog(string.Format("VacationRequest Event - WorkflowInstanceID: {0} Event: {1}", this.WorkflowInstanceId.ToString(), LogEvent)); } private void WriteErrorToIWebWFLog(string Error) { ApplicationLog.AddToLog(string.Format("VacationRequest Error - WorkflowInstanceID: {0} Error: {1}", this.WorkflowInstanceId.ToString(), Error)); } #endregion
IWebCore 项目还允许使用类似以下代码发送电子邮件:
#region SendEmailtoEmployee private void SendEmailtoEmployee() { try { Email Email = new Email(); string strEmailMessage = String.Format("Your Vacation Request for {0} {1}.", objUserDetails.RequestedDate.ToShortDateString(), (Approval) ? "has been approved" : "has been declined"); Email.SendMail(EmployeeEmail, "", "", "", "Email From VacationRequest", strEmailMessage, ""); WriteToIWebWFLog(strEmailMessage); } catch (Exception ex) { WriteErrorToIWebWFLog(String.Format("{0} - {1} ", ex.Message, ex.StackTrace)); } } #endregion
日志记录的事件显示在 IWebWF 网站的管理页面中。
此外,IWebWF 网站中的工作流状态页面显示了当前任何持久化的工作流。这些是等待某种操作才能终止的工作流。持久化服务会自动保存已空闲工作流的状态,并在需要时自动重新激活它们。这使得长期运行的工作流在服务器重启期间也能继续运行。
点击状态页面上的工作流 GUID 会显示工作流名称及其活动。
安全
Windows Workflow 允许您以任何您喜欢的方式设计安全性。本示例使用以下安全设计:
- 工作流是“断开连接”的。它只使用 Web 服务进行通信。
- 工作流不“信任”任何传递给它的信息。员工的可用天数不能直接传递给它。当工作流启动时,它使用请假 RequestID 和 CheckDigit 来检索其计算所需的信息。这可以防止黑客传递虚假信息。
- DNN 模块中的 Web 服务方法只允许在很短的时间内检索和更新记录。在大多数情况下只有几秒钟。DNN 模块通过创建记录并将 RequestID 和 CheckDigit 发送给工作流来“打开大门”。工作流更新记录,然后将 CheckDigit 设置为 0。这会“关闭”记录的“大门”。
版本控制
如果您部署了一个工作流,并在仍有工作流使用旧版本运行时对其进行了更改,那么当这些工作流尝试重新加载时,您将收到一个错误。为避免此问题,您需要使用工作流版本控制。
有关使用 IWebWF 进行工作流版本控制的信息,请参阅本文。
摘要
问题依然存在,为什么要做这么多麻烦呢?这个例子几乎不适合作为 WF 的正当用途。使用工作流而不是简单的过程代码的原因是:
- 如果规则变得更复杂,工作流很容易调整。
- 如果需要复杂的计算来确定何时批准申请,工作流可以执行得更好、更快。
此外,使用 IWebWF 可以省去很多不必要的工作。