65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.57/5 (5投票s)

2008年10月10日

GPL3

9分钟阅读

viewsIcon

45964

downloadIcon

1128

一个 DotNetNuke 模块,与托管在开源项目 IWebWF 中的 WF 工作流进行通信。

引言

Windows WorkFlow (WF) 是一个强大的框架,用于创建企业级工作流。强大的功能也带来了巨大的复杂性。WF 的很大一部分是“自己动手”,它提供了核心组件,但很多必需的元素需要您自己提供。工作流的托管方式有很多种,本文将介绍使用 .asmx Web 服务托管工作流。

这个示例项目展示了一个完整 WF 解决方案,由一个 DotNetNuke 模块和一个托管在开源项目 IWebWF (IWebWF.com) 中的 WF 工作流组成。该项目名为“请假”,允许用户提交休假申请。申请将使用以下业务规则由工作流处理:

  • 如果员工有足够的可用天数并且申请少于 4 天,则自动批准申请。
  • 如果可用天数不足,或申请超过 3 天,则需要批准。

本文将使用以下大纲来探讨该项目:

  • 请假 2.0 概述
  • 设置
  • 提交申请
    • 如何动态调用 Web 服务
  • 批准申请
    • 创建 CheckDigit 并调用请假工作流 Web 服务
    • 调用 DNN Web 服务并更新记录
  • 日志记录
    • DNN 模块
      • 历史记录页面
    • IWebWF
      • 使用 IWebCore 进行日志记录和发送电子邮件
      • 状态页面显示当前持久化的工作流
      • 详细信息页面标识工作流
  • 安全
    • 工作流是“断开连接”的。它只通过 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 保存到数据库中。RequestIDCheckDigit、门户管理员和用户的电子邮件地址将传递给工作流 Web 服务。

在 DNN 中创建代理以调用外部 Web 服务

为了让 DNN 模块能够调用外部 Web 服务并动态更改地址,创建了一个 VacationWebService 项目,其中包含一个普通的 .asmx Web 代理,该代理连接到 IWebWF 网站中的工作流 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 服务(StartWorkflowApproveRequest)。

        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 没有规则条件,并且将在 TerminateWorkflowBranchNeedsApprovalBranch 的规则条件不满足时默认执行(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 服务进行通信。
  • 工作流不“信任”任何传递给它的信息。员工的可用天数不能直接传递给它。当工作流启动时,它使用请假 RequestIDCheckDigit 来检索其计算所需的信息。这可以防止黑客传递虚假信息。
  • DNN 模块中的 Web 服务方法只允许在很短的时间内检索和更新记录。在大多数情况下只有几秒钟。DNN 模块通过创建记录并将 RequestIDCheckDigit 发送给工作流来“打开大门”。工作流更新记录,然后将 CheckDigit 设置为 0。这会“关闭”记录的“大门”。

版本控制

如果您部署了一个工作流,并在仍有工作流使用旧版本运行时对其进行了更改,那么当这些工作流尝试重新加载时,您将收到一个错误。为避免此问题,您需要使用工作流版本控制。

有关使用 IWebWF 进行工作流版本控制的信息,请参阅本文。

摘要

问题依然存在,为什么要做这么多麻烦呢?这个例子几乎不适合作为 WF 的正当用途。使用工作流而不是简单的过程代码的原因是:

  • 如果规则变得更复杂,工作流很容易调整。
  • 如果需要复杂的计算来确定何时批准申请,工作流可以执行得更好、更快。

此外,使用 IWebWF 可以省去很多不必要的工作。

© . All rights reserved.