很多初学者,首先最想解决的问题是:如何将WF与MVC程序相结合。由于Web程序属于长时间运行的流程,因此持续化功能的运用就非常重要了。
本文将结合书签、WorkflowApplication、生命周期事件、MVC、持续化、传参、状态机实现一个简单的审核流程的示例。
本文模拟一个用户注册流程,此流程非常简单,简单到什么地步?
两个用户,版主与管理员,版主负责帮助录入新用户信息,但需要管理员审核通过后才插入数据库,否则审核不通过回退给版主修改。
首先设计一张表如下:
真实环境中不应该这样设计,而是外键关联用户数据(Id、Name、PassWord、Age),本处作为Demo,一切从简。
首先设计一个流程如下:
流程并不复杂,此处需要3个"活动类","一个书签类"。如"提交"活动类代码:
public sealed class Upload : CodeActivity { public InOutArgument<WorkflowModel> workflowModel { get; set; } protected override void Execute(CodeActivityContext context) { WorkflowModel model = context.GetValue<WorkflowModel>(workflowModel); model.WorkflowId = context.WorkflowInstanceId; model.State = "等待审核"; try { model.Add(); //插入数据库 } catch(Exception e) { Console.WriteLine(e.Message); } } }
其放在"提交"State里。
公用书签代码如下:
public sealed class CustomBookmark : NativeActivity { public InArgument<string> BMName { get; set; } public InOutArgument<WorkflowModel> workflowModel { get; set; } protected override bool CanInduceIdle { get { return true; } } protected override void Execute(NativeActivityContext context) { string BookMarkName = context.GetValue<string>(BMName); context.CreateBookmark(BookMarkName, new BookmarkCallback(borkmarkCallback)); } void borkmarkCallback(NativeActivityContext context, Bookmark bookmark, object obj) { Dictionary<string, object> Dic = obj as Dictionary<string, object>; context.SetValue(workflowModel, Dic["Model"] as WorkflowModel); } }
其放在“状态切换线”上,如回退书签:
整个工作流仅仅一个参数,上面那张表的一个实体:
此参数,也作为全局变量使用了。
如果在MVC的Controller里面的Action里面操作工作流的话,代码会非常的乱。所以另外起了一个工作流帮助类。
核心类
其代码如下:
public class WorkflowHelper { #region 工作流属性 WorkflowApplication instance = null; SqlWorkflowInstanceStore instanceStore = null; InstanceView view; AutoResetEvent idleEvent = new AutoResetEvent(false); #endregion //初始化工作流 public void InitialWorkflowApplication() { string connectionString = "server=192.168.0.69;database=waterageII;uid=water;pwd=water"; instance.Idle = delegate(WorkflowApplicationIdleEventArgs e) { idleEvent.Set(); }; instance.Completed = delegate(WorkflowApplicationCompletedEventArgs e) { idleEvent.Set(); }; instanceStore = new SqlWorkflowInstanceStore(connectionString); view = instanceStore.Execute(instanceStore.CreateInstanceHandle(), new CreateWorkflowOwnerCommand(), TimeSpan.FromSeconds(30)); instanceStore.DefaultInstanceOwner = view.InstanceOwner; instance.InstanceStore = instanceStore; } public string Next(WorkflowModel model) { if (model.Id <= 0) { Dictionary<string, object> Dic = new Dictionary<string, object>(); Dic.Add("Model", model); instance = new WorkflowApplication(new Register(), Dic); InitialWorkflowApplication(); instance.Run(); } else { instance = new WorkflowApplication(new Register()); InitialWorkflowApplication(); instance.Load(model.WorkflowId); if (instance.GetBookmarks().Count() > 0) { Dictionary<string, object> Dic = new Dictionary<string, object>(); Dic.Add("Model", model); BookmarkResumptionResult BRR = instance.ResumeBookmark("Bookmark", Dic); } } //等待工作流线程空闲,才往下走 idleEvent.WaitOne(); instance.Unload(); return "运行成功"; } public WorkflowModel GetModelById(int Id) { WorkflowModel w = new WorkflowModel(); DataTable dt = w.Get(Id); WorkflowModel model = new WorkflowModel(); if (dt != null) { DataRow dr = dt.Rows[0]; model.Id = Convert.ToInt32(dr["Id"]); model.Name = Convert.ToString(dr["Name"]); model.PassWord = Convert.ToString(dr["PassWord"]); model.Age = Convert.ToInt32(dr["Age"]); model.State = Convert.ToString(dr["State"]); model.BelongUser = Convert.ToString(dr["BelongUser"]); model.OperateUser = Convert.ToString(dr["OperateUser"]); model.OperateTime = Convert.ToDateTime(dr["OperateTime"]); model.OperateType = Convert.ToString(dr["OperateType"]); model.WorkflowId = new Guid(Convert.ToString(dr["WorkflowId"])); return model; } return null; } }
这样一封装了之后,MVC的Controller非常简洁:
public class HomeController : Controller { User Manager = new User() { Name = "管理员" }; User NewUser = new User() { Name = "版主" }; [HttpGet] public ActionResult Login() { return View(); } [HttpPost] public ActionResult Login(string UserName) { Session["UserName"] = UserName; return Redirect("/Home/List"); } public ActionResult List() { //管理员列表,能看到"等待审核"的数据,而版主能看到"审核回退"的数据 string State = Session["UserName"].ToString() == "管理员" ? "等待审核" : "审核回退"; WorkflowModel model = new WorkflowModel(); DataTable dt = model.GetNewestByState(State); return View(dt); } public ActionResult Register() { return View(); } public ActionResult Upload(WorkflowModel model) { model.OperateUser = Session["UserName"].ToString(); model.OperateTime = DateTime.Now; model.OperateType = "提交"; WorkflowHelper WFController = new WorkflowHelper(); WFController.Next(model); return Content("提交成功,请等待管理员审核!"); } [HttpGet] public ActionResult Check(int Id) { WorkflowHelper WFController = new WorkflowHelper(); WorkflowModel model = WFController.GetModelById(Id); return View(model); } [HttpPost] public ActionResult Check(WorkflowModel model) { model.OperateUser = Session["UserName"].ToString(); model.OperateTime = DateTime.Now; model.OperateType = "审核"; //审核(调用控制器的审核方法) WorkflowHelper WFController = new WorkflowHelper(); string response = WFController.Next(model); return Content(response); } public ActionResult Return(int Id) { WorkflowHelper WFController = new WorkflowHelper(); WorkflowModel model = WFController.GetModelById(Id); return View(model); } }
Controller就只做一件事,拿数据,调用流程控制器的方法(无论当前是在哪一步,都只管调用Helper类的Next()方法,我用了多个页面,相对来说,一个页面一个比较好,因为会涉及到权限以及下一步的角色与用户),所有流程的初始化,流程走到哪一步了,是否已经持续化,能不能加载出来,在Controller里面都不用管。
实体层(在里面包含了数据库访问)如下:
public class WorkflowModel { static string ConstringSql = "server=CZZ;database=xxoo;uid=sa;pwd=123;"; public int Id { get; set; } public string Name { get; set; } public string PassWord { get; set; } public int Age { get; set; } public string Option { get; set; } public string State { get; set; } public string BelongUser { get; set; } public string OperateUser { get; set; } public DateTime OperateTime { get; set; } public string OperateType { get; set; } public Guid WorkflowId { get; set; } public void Add() { SqlConnection conn = new SqlConnection(ConstringSql); SqlCommand cmd = conn.CreateCommand(); cmd.CommandText = "INSERT INTO WorkflowRegister VALUES(@Name,@PassWord,@Age,@Option,@State,@BelongUser,@OperateUser,@OperateTime,@OperateType,@WorkflowId)"; //设置参数类型 cmd.Parameters.Add("@Name", SqlDbType.NVarChar); cmd.Parameters.Add("@PassWord", SqlDbType.VarChar); cmd.Parameters.Add("@Age", SqlDbType.Int); cmd.Parameters.Add("@Option", SqlDbType.NVarChar); cmd.Parameters.Add("@State", SqlDbType.NVarChar); cmd.Parameters.Add("@BelongUser", SqlDbType.NVarChar); cmd.Parameters.Add("@OperateUser", SqlDbType.NVarChar); cmd.Parameters.Add("@OperateTime", SqlDbType.DateTime); cmd.Parameters.Add("@OperateType", SqlDbType.NVarChar); cmd.Parameters.Add("@WorkflowId", SqlDbType.UniqueIdentifier); //设置参数值 cmd.Parameters["@Name"].Value = this.Name == null ? "" : this.Name; cmd.Parameters["@PassWord"].Value = this.PassWord == null ? "" : this.PassWord; cmd.Parameters["@Age"].Value = this.Age; cmd.Parameters["@Option"].Value = this.Option == null ? "" : this.Option; cmd.Parameters["@State"].Value = this.State == null ? "" : this.State; cmd.Parameters["@BelongUser"].Value = this.BelongUser == null ? "" : this.BelongUser; cmd.Parameters["@OperateUser"].Value = this.OperateUser == null ? "" : this.OperateUser; cmd.Parameters["@OperateTime"].Value = this.OperateTime; cmd.Parameters["@OperateType"].Value = this.OperateType == null ? "" : this.OperateType; cmd.Parameters["@WorkflowId"].Value = this.WorkflowId; conn.Open(); cmd.ExecuteNonQuery(); conn.Close(); } public DataTable Get(int Id) { SqlConnection conn = new SqlConnection(ConstringSql); string strSql = string.Format("SELECT * FROM WorkflowRegister WHERE Id = {0}", Id); DataTable dt = new DataTable(); SqlDataAdapter da = new SqlDataAdapter(strSql, conn); da.Fill(dt); if (dt.Rows.Count > 0) { return dt; } return null; } public DataTable GetNewestByState(string State) { SqlConnection conn = new SqlConnection(ConstringSql); string strSql = string.Format("SELECT W1.* FROM WorkflowRegister AS W1 LEFT JOIN WorkflowRegister AS W2 ON W1.WorkflowId = W2.WorkflowId AND W1.OperateTime < W2.OperateTime WHERE W2.Id IS NULL AND W1.State = '{0}'", State); DataTable dt = new DataTable(); SqlDataAdapter da = new SqlDataAdapter(strSql, conn); da.Fill(dt); if (dt.Rows.Count > 0) { return dt; } return null; } }
代码贴得差不多了,来看看运行效果:
版主能够看到状态为"审核回退"案件列表:
管理员能够看到状态为"等待审核"的列表:
提交页面如下:
当一条流程完整走下来,数据库信息大致如下:
WF如果运用得当,应该能够帮助自己更好地理清流程走向。对于工作流之外的开发者来说,就只管Next(),Next()就可以了。
下一篇文章应该是考虑,如果将下一步的角色、选择用户的功能等内容也封装进入流程。