DDD领域驱动设计初探(7):Web层的搭建

tsums 8年前
   <p>来自: <a href="/misc/goto?guid=4959670426185356491" rel="nofollow">http://blog.jobbole.com/99390/</a></p>    <p>前言:好久没更新博客了,每天被该死的业务缠身,今天正好一个模块完成了,继续来完善我们的代码。之前的六篇完成了领域层、应用层、以及基础结构层的部分代码,这篇打算搭建下UI层的代码。</p>    <h2>一、UI层介绍</h2>    <p>在DDD里面,UI层的设计也分为BS和CS,本篇还是以Web为例来说明。我们的Web采用的是MVC+bootstrap的架构。Table组件使用的是bootstrap table,之所以用它是因为它的API比较全,并且博主觉得它的风格适用于各种类型的设备,无论是PC端还是手机端都都能很好的兼容各种浏览器。</p>    <p>这里还是贴出bootstrap API的相关地址。</p>    <p>Bootstrap中文网: <a href="/misc/goto?guid=4958532246454818584" rel="nofollow,noindex">http://www.bootcss.com/</a></p>    <p>Bootstrap Table Demo: <a href="/misc/goto?guid=4959670426312175653" rel="nofollow,noindex">http://issues.wenzhixin.net.cn/bootstrap-table/index.html</a></p>    <p>Bootstrap Table API: <a href="/misc/goto?guid=4959670426404246823" rel="nofollow,noindex">http://bootstrap-table.wenzhixin.net.cn/zh-cn/documentation/</a></p>    <p>Bootstrap Table源码: <a href="/misc/goto?guid=4958865022484382996" rel="nofollow,noindex">https://github.com/wenzhixin/bootstrap-table</a></p>    <p>Bootstrap DataPicker: <a href="/misc/goto?guid=4959670426524700158" rel="nofollow,noindex">http://www.bootcss.com/p/bootstrap-datetimepicker/</a></p>    <p><a href="/misc/goto?guid=4959670426615601981" rel="nofollow,noindex">Bootstrap离线API</a></p>    <h2>二、代码示例</h2>    <p>上篇完成了WCF的设计代码,但是具体的业务逻辑的代码还没有,我们先来实现具体业务的CURD代码。</p>    <h3>1、WCF代码</h3>    <p>1.1 WCF服务业务接口代码</p>    <pre>  ///       /// 权限管理模块接口契约      ///       [ServiceContract]      [ServiceInterface]      public interface IPowerManageWCFService      {            #region 用户管理          [OperationContract]          List GetUsers(ExpressionNode expressionNode);            [OperationContract]          DTO_TB_USERS AddUser(DTO_TB_USERS oUser);            [OperationContract]          bool DeleteUser(DTO_TB_USERS oUser);            [OperationContract]          bool DeleteUserByLamada(ExpressionNode expressionNode);            [OperationContract]          bool UpdateUser(DTO_TB_USERS oUser);          #endregion            #region 部门管理          [OperationContract]          List GetDepartments(ExpressionNode expressionNode);            [OperationContract]          DTO_TB_DEPARTMENT AddDepartment(DTO_TB_DEPARTMENT oDept);            [OperationContract]          bool DeleteDepartment(DTO_TB_DEPARTMENT oDept);            [OperationContract]          bool DeleteDeptByLamada(ExpressionNode expressionNode);            [OperationContract]          bool UpdateDepartment(DTO_TB_DEPARTMENT oDept);          #endregion            #region 角色管理          [OperationContract]          List GetRoles(ExpressionNode expressionNode);            [OperationContract]          DTO_TB_ROLE AddRole(DTO_TB_ROLE oRole);          #endregion            #region 菜单管理          [OperationContract]          List GetMenus(ExpressionNode expressionNode);            [OperationContract]          DTO_TB_MENU AddMenu(DTO_TB_MENU oMenu);          #endregion      }</pre>    <p>1.2 WCF接口实现代码:</p>    <pre>  [ServiceClass]      public class PowerManageWCFService :BaseService, IPowerManageWCFService      {          #region Fields          [Import]          private IUserRepository userRepository { get; set; }            [Import]          private IDepartmentRepository departmentRepository { get; set; }            [Import]          private IRoleRepository roleRepository { get; set; }            [Import]          private IMenuRepository menuRepository { get; set; }           #endregion            #region Constust          public PowerManageWCFService()          {            }          #endregion            #region WCF服务接口实现          #region 用户管理          //这里参数为什么不直接用Expression>这种类型,是因为Expression不支持序列化,无法用于WCF数据的传递          public List GetUsers(ExpressionNode expressionNode)          {              Expressionbool>> selector = null;              if (expressionNode != null)              {                  selector = expressionNode.ToExpressionbool>>();              }              var lstRes = base.GetDtoByLamada(userRepository, selector);              return lstRes;          }            public DTO_TB_USERS AddUser(DTO_TB_USERS oUser)          {              return base.AddDto(userRepository, oUser);          }            public bool DeleteUser(DTO_TB_USERS oUser)          {              var bRes = false;              try              {                  base.DeleteDto(userRepository, oUser);                  bRes = true;              }              catch              {                 }              return bRes;          }            public bool DeleteUserByLamada(ExpressionNode expressionNode)          {              Expressionbool>> selector = null;              if (expressionNode != null)              {                  selector = expressionNode.ToExpressionbool>>();              }              var bRes = false;              try              {                  base.DeleteDto(userRepository, selector);                  bRes = true;              }              catch              {                }              return bRes;          }            public bool UpdateUser(DTO_TB_USERS oUser)          {              var bRes = false;              try              {                  base.UpdateDto(userRepository, oUser);                  bRes = true;              }              catch              {                }              return bRes;          }          #endregion            #region 部门管理          public List GetDepartments(ExpressionNode expressionNode)          {              Expressionbool>> selector = null;              if (expressionNode != null)              {                  selector = expressionNode.ToExpressionbool>>();              }              return base.GetDtoByLamada(departmentRepository, selector);          }          public DTO_TB_DEPARTMENT AddDepartment(DTO_TB_DEPARTMENT oDept)          {              return base.AddDto(departmentRepository, oDept);          }            public bool DeleteDepartment(DTO_TB_DEPARTMENT oDept)          {              var bRes = false;              try              {                  base.DeleteDto(departmentRepository, oDept);                  bRes = true;              }              catch              {                 }              return bRes;          }            public bool DeleteDeptByLamada(ExpressionNode expressionNode)          {              Expressionbool>> selector = null;              if (expressionNode != null)              {                  selector = expressionNode.ToExpressionbool>>();              }              var bRes = false;              try              {                  base.DeleteDto(departmentRepository, selector);                  bRes = true;              }              catch              {                }              return bRes;          }            public bool UpdateDepartment(DTO_TB_DEPARTMENT oDept)          {              var bRes = false;              try              {                  base.UpdateDto(departmentRepository, oDept);                  bRes = true;              }              catch              {                 }              return bRes;          }          #endregion            #region 角色管理          public List GetRoles(ExpressionNode expressionNode)          {              Expressionbool>> selector = null;              if (expressionNode != null)              {                  selector = expressionNode.ToExpressionbool>>();              }              return base.GetDtoByLamada(roleRepository, selector);          }            public DTO_TB_ROLE AddRole(DTO_TB_ROLE oRole)          {              return base.AddDto(roleRepository, oRole);          }          #endregion            #region 菜单管理          public List GetMenus(ExpressionNode expressionNode)          {              Expressionbool>> selector = null;              if (expressionNode != null)              {                  selector = expressionNode.ToExpressionbool>>();              }              return base.GetDtoByLamada(menuRepository, selector);          }            public DTO_TB_MENU AddMenu(DTO_TB_MENU oMenu)          {              return base.AddDto(menuRepository, oMenu);          }          #endregion          #endregion      }</pre>    <p>这里要说明一点,在通过lamada表达式查询的方法里面为什么不直接用Expression>这种类型,而要使用ExpressionNode这种类型的变量呢?</p>    <p>这是因为Expression不支持序列化,无法用于WCF数据的传递。ExpressionNode这个对象的使用需要添加Serialize.Linq这个dll的引用,还好有我们神奇的NuGet,让我们再也不用去网上找一大堆的dll了。</p>    <p><img src="https://simg.open-open.com/show/11cb468da933181a94818a9b1f972182.png"></p>    <p>我们公用的增删改查封装到了BaseService这个父类里面。</p>    <p>1.3 BaseService代码</p>    <pre>  public class BaseService      {          #region Fields          private bool bInitAutoMapper = false;          #endregion            #region Construct           public BaseService()          {              //注册MEF              Regisgter.regisgter().ComposeParts(this);          }          #endregion            #region 查询          ///           /// 通用单表查询方法          ///           /// DTOmodel          /// 领域模型          /// 需要传过来的仓储接口对象          /// 前端传过来的lamada表达式          ///           public List GetDtoByLamada(IRepository oRepository, Expressionbool>> selector = null)              where DomainModel : AggregateRoot              where DtoModel : Dto_BaseModel          {              InitAutoMapper();              if (selector == null)              {                  var lstDomainModel = oRepository.Entities.ToList();                  return Mapper.Map, List>(lstDomainModel);              }              //得到从Web传过来和DTOModel相关的lamaba表达式的委托              Funcbool> match = selector.Compile();              //创建映射Expression的委托              Func mapper = AutoMapper.QueryableExtensions.Extensions.CreateMapExpression(Mapper.Engine).Compile();              //得到领域Model相关的lamada              Expressionbool>> lamada = ef_t => match(mapper(ef_t));              List list = oRepository.Find(lamada).ToList();              return Mapper.Map, List>(list);          }           #endregion            #region 新增          public DtoModel AddDto(IRepository oRepository, DtoModel oDtoModel)              where DomainModel : AggregateRoot              where DtoModel : Dto_BaseModel          {              InitAutoMapper();              var oDomain = Mapper.Map(oDtoModel);              oRepository.Insert(oDomain);              return Mapper.Map(oDomain);          }          #endregion            #region 删除          public int DeleteDto(IRepository oRepository, DtoModel oDtoModel)              where DomainModel : AggregateRoot              where DtoModel : Dto_BaseModel          {              InitAutoMapper();              var oDomain = Mapper.Map(oDtoModel);              return oRepository.Delete(oDomain);          }            public int DeleteDto(IRepository oRepository, Expressionbool>> selector = null)              where DomainModel : AggregateRoot              where DtoModel : Dto_BaseModel          {              InitAutoMapper();              if (selector == null)              {                  return 0;              }              //得到从Web传过来和DTOModel相关的lamaba表达式的委托              Funcbool> match = selector.Compile();              //创建映射Expression的委托              Func mapper = AutoMapper.QueryableExtensions.Extensions.CreateMapExpression(Mapper.Engine).Compile();              //得到领域Model相关的lamada              Expressionbool>> lamada = ef_t => match(mapper(ef_t));              return oRepository.Delete(lamada);          }           #endregion            #region 更新          public void UpdateDto(IRepository oRepository, DtoModel oDtoModel)              where DomainModel : AggregateRoot              where DtoModel : Dto_BaseModel          {              InitAutoMapper();              var oDomain = Mapper.Map(oDtoModel);              oRepository.Update(oDomain);          }          #endregion            #region Private          private void InitAutoMapper()          {              var oType = Mapper.FindTypeMapFor();              if (oType==null)              {                  Mapper.CreateMap();                  Mapper.CreateMap();              }          }          #endregion      }</pre>    <p>这个父类主要做了两件事:一是MEF的初始化;二是通用增删改查的实现。所有dto对象和领域model的映射都在这里统一管理。</p>    <h3>2、UI层代码</h3>    <p>UI层里面,为了更好分离代码,我们引入了接口编程的机制,引入了ESTM.Web.IBLL和ESTM.Web.BLL两个项目,如图:</p>    <p><img src="https://simg.open-open.com/show/c3ea0c06211c873064804d5d7589e8e6.png"></p>    <p>为什么要有这么一个接口层?之前 <a href="/misc/goto?guid=4959670426709613980" rel="nofollow,noindex"> MEF实现设计上的“松耦合”(终结篇:面向接口编程)</a> 这篇已经做过介绍,对面向接口编程不了解的朋友可以看看。</p>    <p>2.1 ESTM.Web.IBLL代码</p>    <p>这个dll主要定义接口规则。</p>    <pre>  public interface IPowerManager      {          List GetUsers(Expressionbool>> selector = null);            DTO_TB_USERS AddUser(DTO_TB_USERS oUser);            bool DeleteUser(DTO_TB_USERS oUser);            bool UpdateUser(DTO_TB_USERS oUser);            bool DeleteUser(Expressionbool>> selector = null);            List GetDepartments(Expressionbool>> selector = null);            DTO_TB_DEPARTMENT AddDepartment(DTO_TB_DEPARTMENT oDept);            bool DeleteDepartment(DTO_TB_DEPARTMENT oDept);            bool DeleteDepartment(Expressionbool>> selector = null);            bool UpdateDepartment(DTO_TB_DEPARTMENT oDept);            List GetRoles(Expressionbool>> selector = null);            List GetMenus(Expressionbool>> selector = null);        }</pre>    <p>2.2 ESTM.Web.BLL代码</p>    <p>这个dll用于实现ESTM.Web.IBLL里面的接口方法</p>    <pre>  [Export(typeof(IPowerManager))]      public class PowerManager : IPowerManager      {          #region Fields                   //创建WCF服务连接对象          private ServiceReference_PowerManager.PowerManageWCFServiceClient oService = CreatePowerManagerService.GetInstance();           #endregion            #region 接口实现          public List GetUsers(Expressionbool>> selector = null)          {              return oService.GetUsers(GetExpressionNode(selector));          }            public List GetDepartments(Expressionbool>> selector = null)          {              return oService.GetDepartments(GetExpressionNode(selector));          }            public List GetRoles(Expressionbool>> selector = null)          {              return oService.GetRoles(GetExpressionNode(selector));          }            public List GetMenus(Expressionbool>> selector = null)          {              return oService.GetMenus(GetExpressionNode(selector));          }           #endregion            #region Privates          //将lamada表达式转换为可用于WCF传递的ExpressionNode类型          private ExpressionNode GetExpressionNode(Expressionbool>> selector)          {              if (selector == null)              {                  return null;              }              ExpressionConverter expressionConverter = new ExpressionConverter();              ExpressionNode expressionNode = expressionConverter.Convert(selector);              return expressionNode;          }          #endregion            public DTO_TB_USERS AddUser(DTO_TB_USERS oUser)          {              return oService.AddUser(oUser);          }            public bool DeleteUser(DTO_TB_USERS oUser)          {              return oService.DeleteUser(oUser);          }            public bool DeleteUser(Expressionbool>> selector = null)          {              if (selector == null)              {                  return false;              }              ExpressionConverter expressionConverter = new ExpressionConverter();              ExpressionNode expressionNode = expressionConverter.Convert(selector);              return oService.DeleteUserByLamada(expressionNode);          }            public bool UpdateUser(DTO_TB_USERS oUser)          {              return oService.UpdateUser(oUser);          }            public DTO_TB_DEPARTMENT AddDepartment(DTO_TB_DEPARTMENT oDept)          {              return oService.AddDepartment(oDept);          }            public bool DeleteDepartment(DTO_TB_DEPARTMENT oDept)          {              return oService.DeleteDepartment(oDept);          }            public bool UpdateDepartment(DTO_TB_DEPARTMENT oDept)          {              return oService.UpdateDepartment(oDept);          }            public bool DeleteDepartment(Expressionbool>> selector = null)          {              if (selector == null)              {                  return false;              }              ExpressionConverter expressionConverter = new ExpressionConverter();              ExpressionNode expressionNode = expressionConverter.Convert(selector);              return oService.DeleteDeptByLamada(expressionNode);          }      }</pre>    <pre>  public class CreatePowerManagerService      {          private static ServiceReference_PowerManager.PowerManageWCFServiceClient oPowerManagerClient = null;          private static object obj = new object();            public static ServiceReference_PowerManager.PowerManageWCFServiceClient GetInstance()          {              lock (obj)              {                  if (oPowerManagerClient == null)                  {                      oPowerManagerClient = new ServiceReference_PowerManager.PowerManageWCFServiceClient();                  }              }              return oPowerManagerClient;          }      }</pre>    <p>由于是采用的添加服务引用的方式引用的WCF服务,所以在这一层需要添加WCF服务的引用。在实现这部分代码的时候博主遇到过一个问题,在此和朋友们分享一下。由于在WCF服务的设计里面用到了DTO对象,而在ESTM.Web.BLL这个项目里面也要用到DTO,但是添加WCF服务引用的时候默认的是WCF服务里面的DTO,而不是ESTM.Common.DtoModel这个项目的DTO对象,这样就有问题了,每次如果我们需要改动下dto的内容,那么我们就需要更新下服务引用。还好,微软给我们选择的机制,我们来看图</p>    <p><img src="https://simg.open-open.com/show/5097f583f1ff5c51117038666e89dbaf.png"></p>    <p><img src="https://simg.open-open.com/show/78add29e8c4a1768b37bd1d97363f0d6.png"></p>    <p>这样就能解决上面的问题了。</p>    <p>2.3 ESTM.Web代码</p>    <p>按照面向接口的机制,ESTM.Web项目是不需要添加ESTM.Web.BLL这个实现层项目引用的,通过MEF动态导入ESTM.Web.BLL里面的对象。我们来看代码:</p>    <pre>  public class PowerManagerController : BaseController      {          [Import]          private IPowerManager PowerManager { set; get; }            #region Views          // GET: PowerManager          public ActionResult User()          {              return View();          }            public ActionResult Role()          {              return View();          }            public ActionResult Menu()          {              return View();          }            public ActionResult Department()          {              return View();          }           #endregion            #region 部门管理          public JsonResult GetDepartments(int limit, int offset, string departmentname, string statu)          {              //得到lamada表达式              var oLamadaExtention = new LamadaExtention();              if (!string.IsNullOrEmpty(departmentname))              {                  oLamadaExtention.GetExpression("DEPARTMENT_NAME", departmentname, ExpressionType.Contains);              }              if (!string.IsNullOrEmpty(statu))              {                  oLamadaExtention.GetExpression("STATUS", statu, ExpressionType.Contains);              }              var lamada = oLamadaExtention.GetLambda();              var lstRes = PowerManager.GetDepartments(lamada);                return Json(new { rows = lstRes.Skip(offset).Take(limit).ToList(), total = lstRes.Count }, JsonRequestBehavior.AllowGet);          }            public object GetDepartmentEdit(string strPostData)          {              var oDepartment = Newtonsoft.Json.JsonConvert.DeserializeObject(strPostData);              if (string.IsNullOrEmpty(oDepartment.DEPARTMENT_ID))              {                  oDepartment.DEPARTMENT_ID = Guid.NewGuid().ToString();                  oDepartment = PowerManager.AddDepartment(oDepartment);              }              else              {                  PowerManager.UpdateDepartment(oDepartment);              }              return oDepartment;          }            public object DeleteDept(string strID)          {              PowerManager.DeleteDepartment(x=>x.DEPARTMENT_ID == strID);              return new object();          }          #endregion            #region 菜单管理          public JsonResult GetMenus(int limit, int offset, string menuname, string menuurl)          {              var oLamadaExtention = new LamadaExtention();              if (!string.IsNullOrEmpty(menuname))              {                  oLamadaExtention.GetExpression("MENU_NAME", menuname, ExpressionType.Contains);              }              if (!string.IsNullOrEmpty(menuurl))              {                  oLamadaExtention.GetExpression("MENU_URL", menuurl, ExpressionType.Contains);              }              var lamada = oLamadaExtention.GetLambda();              var lstRes = PowerManager.GetMenus(lamada).ToList();                return Json(new { rows = lstRes.Skip(offset).Take(limit).ToList(), total = lstRes.Count }, JsonRequestBehavior.AllowGet);          }            public object GetMenuEdit(string strPostData)          {              var oMenu = Newtonsoft.Json.JsonConvert.DeserializeObject(strPostData);              if (string.IsNullOrEmpty(oMenu.MENU_ID))              {                  //oMenu = MenuManager.Add(oMenu);              }              else              {                  //MenuManager.Update(oMenu);              }              return oMenu;          }            public object DeleteMenu(string strID)          {              //MenuManager.Delete(strID);              return new object();          }            public object GetParentMenu()          {              var lstMenu = PowerManager.GetMenus(x => x.MENU_LEVEL == "1");                //var lstRes = RoleManager.Find().ToList();              //var oRes = new PageRowData();              //oRes.rows = lstRes.Skip(offset).Take(limit).ToList();              //oRes.total = lstRes.Count;              return lstMenu; ;          }            public object GetChildrenMenu(string strParentID)          {              var lstMenu = PowerManager.GetMenus(x => x.MENU_LEVEL == "2" & x.PARENT_ID == strParentID).ToList();                //var lstRes = RoleManager.Find().ToList();              //var oRes = new PageRowData();              //oRes.rows = lstRes.Skip(offset).Take(limit).ToList();              //oRes.total = lstRes.Count;              return lstMenu; ;          }          #endregion            #region 权限管理            public JsonResult GetRole(int limit, int offset, string rolename, string desc)          {              var oLamadaExtention = new LamadaExtention();              if (!string.IsNullOrEmpty(rolename))              {                  oLamadaExtention.GetExpression("ROLE_NAME", rolename, ExpressionType.Contains);              }              if (!string.IsNullOrEmpty(desc))              {                  oLamadaExtention.GetExpression("DESCRIPTION", desc, ExpressionType.Contains);              }              var lamada = oLamadaExtention.GetLambda();              var lstRes = PowerManager.GetRoles(lamada).ToList();                return Json(new { rows = lstRes.Skip(offset).Take(limit).ToList(), total = lstRes.Count }, JsonRequestBehavior.AllowGet);          }            #endregion            #region 用户管理          public JsonResult GetUsers(int limit, int offset, string username, string fullname)          {              var oLamadaExtention = new LamadaExtention();              if (!string.IsNullOrEmpty(username))              {                  oLamadaExtention.GetExpression("USER_NAME", username, ExpressionType.Contains);              }              if (!string.IsNullOrEmpty(fullname))              {                  oLamadaExtention.GetExpression("FULLNAME", fullname, ExpressionType.Contains);              }              var lamada = oLamadaExtention.GetLambda();                var lstRes = PowerManager.GetUsers(lamada).ToList();                return Json(new { rows = lstRes.Skip(offset).Take(limit).ToList(), total = lstRes.Count }, JsonRequestBehavior.AllowGet);          }            public object GetUserEdit(string strPostData)          {              var oUser = Newtonsoft.Json.JsonConvert.DeserializeObject(strPostData);              if (string.IsNullOrEmpty(oUser.USER_ID))              {                  oUser.USER_ID = Guid.NewGuid().ToString();                  oUser = PowerManager.AddUser(oUser);              }              else              {                  PowerManager.UpdateUser(oUser);              }              return oUser;          }            public object DeleteUser(string strID)          {              PowerManager.DeleteUser(x => x.USER_ID == strID);              return new object();          }          #endregion      }</pre>    <p>View页面</p>    <pre>  <!DOCTYPE html>  <html>  <head>      <meta name="viewport" content="width=device-width" />      <title>@ViewBag.Title</title>      @Styles.Render("~/Content/css")      @Styles.Render("~/Content/table-css")      @Scripts.Render("~/bundles/jquery")      @Scripts.Render("~/bundles/bootstrap")      @Scripts.Render("~/bundles/bootstrap-table")      @RenderSection("Scripts", false)  </head>  <body>      @RenderBody()  </body>  </html></pre>    <pre>  @{      ViewBag.Title = "部门管理";      Layout = "~/Views/Shared/_Layout.cshtml";  }    @Scripts.Render("~/bundles/PowerManage/DepartmentManage")    <div class="panel-body" style="padding-bottom:0px;">      <div class="panel panel-default">          <div class="panel-heading">查询条件</div>          <div class="panel-body">              <div class="row">                  <div class="col-md-4">                      <label for="txt_search_departmentname" class="col-sm-4 control-label" style="margin-top:6px;">部门名称</label>                      <span class="col-sm-8">                          <input type="text" class="form-control" id="txt_search_departmentname">                      </span>                  </div>                  <div class="col-md-4">                      <label for="txt_search_statu" class="col-sm-3 control-label" style="margin-top:6px;">状态</label>                      <span class="col-sm-8">                          <input type="text" class="form-control" id="txt_search_statu">                      </span>                  </div>                    <div class="col-md-4">                      <button type="button" id="btn_query" class="btn btn-primary">查询</button>                  </div>              </div>                </div>      </div>  </div>    <div id="toolbar" class="btn-group">      <button id="btn_add" type="button" class="btn btn-default">          <span class="glyphicon glyphicon-plus" aria-hidden="true"></span>新增      </button>      <button id="btn_edit" type="button" class="btn btn-default">          <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>修改      </button>      <button id="btn_delete" type="button" class="btn btn-default">          <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>删除      </button>  </div>  <table id="tb_departments"></table>    <form>      <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">          <div class="modal-dialog" role="document">              <div class="modal-content">                  <div class="modal-header">                      <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>                      <h4 class="modal-title" id="myModalLabel">新增</h4>                  </div>                  <div class="modal-body">                        <div class="form-group">                          <label for="txt_departmentname">部门名称</label>                          <input type="text" name="txt_departmentname" class="form-control" id="txt_departmentname" placeholder="部门名称">                      </div>                      <div class="form-group">                          <label for="txt_parentdepartment">上级部门</label>                          <input type="text" name="txt_parentdepartment" class="form-control" id="txt_parentdepartment" placeholder="上级部门">                      </div>                      <div class="form-group">                          <label for="txt_departmentlevel">部门级别</label>                          <input type="text" name="txt_departmentlevel" class="form-control" id="txt_departmentlevel" placeholder="部门级别">                      </div>                      <div class="form-group">                          <label for="txt_statu">状态</label>                          <input type="text" name="txt_statu" class="form-control" id="txt_statu" placeholder="状态">                      </div>                  </div>                  <div class="modal-footer">                      <button type="button" class="btn btn-default" data-dismiss="modal"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span>关闭</button>                      <button type="button" id="btn_submit" class="btn btn-primary" data-dismiss="modal"><span class="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>保存</button>                  </div>              </div>          </div>      </div>  </form></pre>    <p>JS代码我们来看一个页面就好了,其他页面类似:</p>    <pre>  $(function () {      $('#tb_departments').bootstrapTable({          url: '/PowerManager/GetDepartments',          method: 'post',          toolbar: '#toolbar',          pagination: true,          queryParams: queryParams,          queryParamsType: "limit",          //ajaxOptions: { departmentname: "", statu: "" },          sidePagination: "server",          pageSize: 5,          pageList: [5, 25, 50, 100],          search: true,          strictSearch: true,          showColumns: true,          showRefresh: true,          minimumCountColumns: 2,          clickToSelect: true,          columns: [{              checkbox: true          }, {              field: 'DEPARTMENT_NAME',              title: '部门名称'          }, {              field: 'PARENT_ID',              title: '上级部门'          }, {              field: 'DEPARTMENT_LEVEL',              title: '部门级别'          }, {              field: 'STATUS',              title: '状态'          }, ],          onLoadSuccess: function (data) {              var odata = data;          }      });        var oButtonInit = new ButtonInit();      oButtonInit.Init();    });    function queryParams(params) {  //配置参数      var temp = {   //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的          limit: params.limit,   //页面大小          offset: params.offset,  //页码          departmentname: $("#txt_search_departmentname").val(),          statu: $("#txt_search_statu").val()      };      return temp;  }    var ButtonInit = function () {      var oInit = new Object();      var postdata = {};        oInit.Init = function () {          $("#btn_add").click(function () {              $("#myModalLabel").text("新增");              $("#myModal").find(".form-control").val("");              $('#myModal').modal()                postdata.DEPARTMENT_ID = "";          });            $("#btn_edit").click(function () {              var arrselections = $("#tb_departments").bootstrapTable('getSelections');              if (arrselections.length > 1) {                  //alert("只能选择一行进行编辑");                  $("#btn_alert").alert();                  return;              }              if (arrselections.length <= 0) {                  //alert("请先选择需要编辑的行");                  $("#btn_alert").alert()                  return;              }              $("#myModalLabel").text("编辑");              $("#txt_departmentname").val(arrselections[0].DEPARTMENT_NAME);              $("#txt_parentdepartment").val(arrselections[0].PARENT_ID);              $("#txt_departmentlevel").val(arrselections[0].DEPARTMENT_LEVEL);              $("#txt_statu").val(arrselections[0].STATUS);                postdata.DEPARTMENT_ID = arrselections[0].DEPARTMENT_ID;              $('#myModal').modal();          });            $("#btn_delete").click(function () {              var arrselections = $("#tb_departments").bootstrapTable('getSelections');              if (arrselections.length <= 0) {                  //alert("请先选择需要编辑的行");                  $("#btn_alert").alert()                  return;              }                if (!confirm("确定要删除选定的数据吗?")) {                  return;              }                $.ajax({                  type: "post",                  url: "/PowerManager/DeleteDept",                  data: { strID: arrselections[0].DEPARTMENT_ID },                  success: function (data, status) {                      if (status == "success") {                          alert("提交数据成功");                          $("#tb_departments").bootstrapTable('refresh');                      }                  },                  error: function () {                      alert("error");                  },                  complete: function () {                      //alert("complete");                  }                });          });            $("#btn_submit").click(function () {              postdata.DEPARTMENT_NAME = $("#txt_departmentname").val();              postdata.PARENT_ID = $("#txt_parentdepartment").val();              postdata.DEPARTMENT_LEVEL = $("#txt_departmentlevel").val();              postdata.STATUS = $("#txt_statu").val();              $.ajax({                  type: "post",                  url: "/PowerManager/GetDepartmentEdit",                  data: { strPostData: JSON.stringify(postdata) },                  success: function (data, status) {                      if (status == "success") {                          alert("提交数据成功");                          $("#tb_departments").bootstrapTable('refresh');                      }                  },                  error: function () {                      //alert("error");                  },                  complete: function () {                      //alert("complete");                  }                });          });            $("#btn_query").click(function () {              $("#tb_departments").bootstrapTable('refresh');          });      };        return oInit;  };</pre>    <p>效果图:</p>    <p><img src="https://simg.open-open.com/show/fd6066f200d15441428db6620aad97e3.png"></p>    <p><img src="https://simg.open-open.com/show/844b20855e057f3738b3c732836d2523.png"></p>    <p><img src="https://simg.open-open.com/show/97c70418757575b53cc6c3f128d1d03d.png"></p>    <p>在做页面数据更新的时候,博主又遇到一个问题:ObjectStateManager 中已存在具有同一键的对象。ObjectStateManager 无法跟踪具有相同键的多个对象。在此还是记录下解决方案:</p>    <p>在仓储的公共实现类中将</p>    <pre>  public virtual IQueryable Entities    {       get { return UnitOfWork.context.Set(); }    }</pre>    <p>改成</p>    <pre>  public virtual IQueryable Entities  {    get { return UnitOfWork.context.Set().AsNoTracking() as IQueryable; }  }</pre>    <p>就可以了。</p>    <p>至此,从领域模型到Web前端的代码基本完成,可能很多代码并未完善,比如异常处理、数据验证等。</p>    <p>DDD领域驱动设计初探系列文章:</p>