项目架构开发:展现层(上)

上次我们创建了项目的服务层,服务层在业务逻辑简单,或项目运行初期不是很容易体现出他的价值;传送门:项目架构开发:服务层(上)

服务层专门处理非业务逻辑的一些功能,比如缓存、异常处理、组织多个应用逻辑等;这次我们搭建最上层的展现层,用到的知识面包括以下:

asp.net mvc5 + bootstrap + autofac + AutoMapper

这次我们没有用服务层,而是直接调用应用逻辑层接口方法,其实对小项目来说,这样已经足够了;服务层我们下次再讲吧

现在开始吧!

 

1、创建MVC + UnitTest

 

先搭建个框架,网上找的后台模板

 

 

 2、ViewModel

 UI的数据载体最好新建一个viewmodel,这样就不用依赖DTO或PO,因为页面上显示的数据实体一般比较大,会封装比DTO多的多的属性

LoginUserViewModel.cs

 1 using Infrastructure.Common;
 2 using System;
 3 using System.Collections.Generic;
 4 
 5 namespace Presentation.MVC.Models
 6 {
 7     public class LoginUserViewModel
 8     {
 9         public int RowNumber { get; set; }
10 
11         public Guid Id { get; set; }
12         public string LoginName { get; set; }
13         public short? IsEnabled { get; set; }
14         public DateTime? CreateTime { get; set; }
15     }
16 
17     public class LoginUserListViewModel
18     {
19         public List<LoginUserViewModel> Items { get; set; }
20     }
21 
22     public class LoginUserPageViewModel : PageViewModelBase
23     {
24         public List<LoginUserViewModel> Items { get; set; }
25     }
26 }

PageViewModelBase.cs (这个是分页时候用的,如上边标红实体)

 1 using Infrastructure.Common;
 2 
 3 namespace Presentation.MVC.Models
 4 {
 5     public class PageViewModelBase
 6     {
 7         public bool IsFirst { get; set; }
 8         public bool IsLast { get; set; }
 9 
10         public Page Page { get; set; }
11         public int Total { get; set; }
12         public int TotalPage
13         {
14             get
15             {
16                 return (Total % Page.PageSize) == 0 ? Total / Page.PageSize : (Total / Page.PageSize) + 1;
17             }
18         }
19 
20         public int PrePage
21         {
22             get
23             {
24                 if (Page.PageIndex == 1)
25                 {
26                     IsFirst = true;
27                     return 1;
28                 }
29                 else
30                 {
31                     IsFirst = false;
32                     return Page.PageIndex - 1;
33                 }
34             }
35         }
36         public int NextPage
37         {
38             get
39             {
40                 if (Page.PageIndex == TotalPage)
41                 {
42                     IsLast = true;
43                     return TotalPage;
44                 }
45                 else
46                 {
47                     IsLast = false;
48                     return Page.PageIndex + 1;
49                 }
50             }
51         }
52     }
53 }

 

 3、映射

 主要是DTO映射成ViewModel,这里我们用的是AutoMapper

AutoMapperConfiguration.cs,AutoMapper用法很简单,引用他,然后像下边代码那样写,然后再应用启动的时候加载

 1 using AutoMapper;
 2 using Business.ReverseDDD.Model;
 3 using Presentation.MVC.Models;
 4 
 5 namespace Presentation.MVC.Mappings
 6 {
 7     public class AutoMapperConfiguration
 8     {
 9         public static void Configure()
10         {
11             Mapper.CreateMap<LoginUser, LoginUserViewModel>();
12
13         }
14     }
15 }

Global.asax.cs

1 protected void Application_Start()
2         {
3             AreaRegistration.RegisterAllAreas();
4             FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
5             RouteConfig.RegisterRoutes(RouteTable.Routes);
6             BundleConfig.RegisterBundles(BundleTable.Bundles);
7 
8             AutoMapperConfiguration.Configure();
9         }

 

4、视图

为了方便,CURD都在一个页面实现了

  1 @model Presentation.MVC.Models.LoginUserPageViewModel
  2 @{
  3     ViewBag.Title = "Index";
  4     Layout = "~/Views/Shared/_Bootstrap.cshtml";
  5 }
  6 
  7 <div class="container">
  8     <h2></h2>
  9     <div class="panel panel-default">
 10         <div class="panel-heading">
 11             <h4 class="panel-title">
 12                 <a data-toggle="collapse" data-parent="#accordion"
 13                    href="#collapseOne" id="actionType">
 14                     新增用户
 15                 </a>
 16             </h4>
 17         </div>
 18         <div id="collapseOne" class="panel-collapse collapse">
 19             <div class="panel-body">
 20 
 21                 @using (Html.BeginForm("Add", "LoginUser", null, FormMethod.Post, new { @id = "formLoginUser", @class = "form-horizontal", role = "form" }))
 22                 {
 23                     @Html.AntiForgeryToken()
 24 
 25                     <div class="form-group">
 26                         <label for="firstname" class="col-sm-2 control-label">登录名</label>
 27                         <div class="col-sm-10">
 28                             <input type="text" class="form-control" name="LoginName" id="LoginName"
 29                                    placeholder="请输入登录账户名" />
 30                         </div>
 31                     </div>
 32                     <div class="form-group">
 33                         <label for="lastname" class="col-sm-2 control-label">登录密码</label>
 34                         <div class="col-sm-10">
 35                             <input type="text" class="form-control" name="Password" id="Password"
 36                                    placeholder="请输入登录密码" />
 37                         </div>
 38                     </div>
 39                     <div class="form-group">
 40                         <div class="col-sm-offset-2 col-sm-10">
 41                             <div class="checkbox">
 42                                 <label>
 43                                     <input type="checkbox" name="IsEnabled" id="IsEnabled" value="1" /> 是否有效
 44                                 </label>
 45                             </div>
 46                         </div>
 47                     </div>
 48 
 49                     <div class="form-group">
 50                         <div class="col-sm-offset-2 col-sm-10">
 51                             <input type="hidden" name="Id" id="Id" />
 52                             <button type="submit" class="btn btn-default">提交</button>
 53                         </div>
 54                     </div>
 55                 }
 56 
 57             </div>
 58         </div>
 59     </div>
 60 
 61 
 62     <h4>用户列表</h4>
 63     <table id="list" class="table table-striped table-bordered table-hover table-condensed">
 64         <thead>
 65             <tr>
 66                 <th>序号</th>
 67                 <th>登录名</th>
 68                 <th>是否有效</th>
 69                 <th>创建时间</th>
 70                 <th>操作</th>
 71             </tr>
 72         </thead>
 73         <tbody>
 74             @foreach (var item in Model.Items)
 75             {
 76                 <tr>
 77                     <td>@item.RowNumber</td>
 78                     <td>@item.LoginName</td>
 79                     <td>@item.IsEnabled</td>
 80                     <td>@item.CreateTime</td>
 81                     <td>
 82                         <button type="button" class="btn btn-link btn-xs" key="@item.Id" action="edit">修改</button>
 83                         <button type="button" class="btn btn-link btn-xs" key="@item.Id" action="delete">删除</button>
 84                     </td>
 85                 </tr>
 86             }
 87         </tbody>
 88     </table>
 89 
 90     <ul class="pagination">
 91         <li><a href="/LoginUsers/@Model.PrePage">&laquo;</a></li>
 92         @for (int index = 1; index <= Model.TotalPage; index++)
 93         {
 94             if (Model.Page.PageIndex == index)
 95             {
 96                 <li class="active"><a href="/LoginUsers/@index">@index</a></li>
 97             }
 98             else
 99             {
100                 <li><a href="/LoginUsers/@index">@index</a></li>
101             }
102         }
103         <li><a href="/LoginUsers/@Model.NextPage">&raquo;</a></li>
104     </ul>
105 
106     <script type="text/javascript">
107         $(function () {
108             $(".btn-link").click(function () {
109                 var obj = $(this);
110                 var key = obj.attr("key");
111                 if (key == undefined || key == null || key == "") return;
112 
113                 var action = obj.attr("action");
114 
115                 if (action == "delete") {
116                     CreateDeleteWindow(function () {
117                         location.href = "/LoginUser/Delete/" + key;
118                     });
119                 }
120 
121                 if (action == "edit") {
122                     $.ajax({
123                         url: "/LoginUser/Edit/" + key,
124                         type: "GET",
125                         dataType: 'json',
126                         success: function (result) {
127                             $("#Id").val(result.Id);
128                             $("#LoginName").val(result.LoginName);
129                             if (result.IsEnabled == 1) $("#IsEnabled").attr("checked", "true");
130                             else $("#IsEnabled").removeAttr("checked");
131 
132                             $('#collapseOne').collapse('show');
133                             $("#formLoginUser").attr("action", "/LoginUser/Edit");
134                             $("#actionType").html("修改用户");
135                         },
136                         error: function (e) {
137                             alert(e);
138                         }
139                     });
140                 }
141             });
142 
143         });
144     </script>
145 
146 </div>

其实也没什么特别的就是用了Bootstrap美化页面样式,对Bootstrap不懂的请点击这里

 

5、控制器

LoginUserController.cs

 1 using AutoMapper;
 2 using Business.DTO.Request;
 3 using Business.ReverseDDD.IApplication;
 4 using Infrastructure.Common;
 5 using LingExtensions;
 6 using Presentation.MVC.Models;
 7 using System;
 8 using System.Collections.Generic;
 9 using System.Web.Mvc;
10 
11 namespace Presentation.MVC.Controllers
12 {
13     public class LoginUserController : Controller
14     {
15         private ILoginUserApplication loginUserApplication;
16 
17         public LoginUserController(ILoginUserApplication loginUserApplication)
18         {
19             this.loginUserApplication = loginUserApplication;
20         }
21 
22         [Route("LoginUsers/{PageIndex=1}")]
23         public ActionResult Index(string PageIndex)
24         {
25             Page page = new Page();
26             page.PageIndex = PageIndex.ToInt(1);
27 
28             var model = new LoginUserPageViewModel();
29             var soure = this.loginUserApplication.GetPage(page, w => w.OrderByDescending(t => t.CreateTime));
30 
31             model.Items = Mapper.Map<List<LoginUserViewModel>>(soure.Item2);
32             model.Total = soure.Item1;
33             model.Page = page;
34 
35             return View(model);
36         }
37 
38         [HttpPost]
39         [ValidateAntiForgeryToken] 
40         public ActionResult Add(LoginUserCURequest entity)
41         {
42             this.loginUserApplication.Add(new LoginUserCURequest()
43             {
44                 Id = Guid.NewGuid(),
45                 LoginName = entity.LoginName,
46                 Password = entity.Password,
47                 IsEnabled = entity.IsEnabled
48             });
49 
50             return RedirectToAction("Index");
51         }
52 
53         [HttpGet]
54         public ActionResult Delete(string id)
55         {
56             this.loginUserApplication.Delete(Guid.Parse(id));
57 
58             return RedirectToAction("Index");
59         }
60 
61         [HttpGet]
62         public ActionResult Edit(Guid id)
63         {
64             var soure = this.loginUserApplication.Get(id);
65 
66             return Json(soure, JsonRequestBehavior.AllowGet);
67         }
68 
69         [HttpPost]
70         [ValidateAntiForgeryToken]
71         public ActionResult Edit(LoginUserCURequest entity)
72         {
73             this.loginUserApplication.Update(new LoginUserCURequest()
74             {
75                 Id = entity.Id,
76                 LoginName = entity.LoginName,
77                 Password = entity.Password,
78                 IsEnabled = entity.IsEnabled
79             });
80 
81             return RedirectToAction("Index");
82         }
83     }
84 }

 

都是演示CURD的功能,大家不要在意这些细节。。

看标红的地方,意思是将soure.Item2(是Tuple<int, IEnumerable<LoginUser>>类型)转换成List<LoginUserViewModel>

这就是AutoMapper的用法

 

还有是这里没有依赖具体应用逻辑组件的,只依赖了业务逻辑接口using Business.ReverseDDD.IApplication;

这个是为了解耦,而且对分层并行开发很有用,项目前端后端开发都不用依赖谁开发完才能往下继续;

 

控制器我们用的是依赖注入Autofac组件:

 

 6、UnitTest

LoginUserControllerTest.cs, 记得也要Mapping哦

 

测试通过了

 

7、UI,我们来看看界面功能

 7.1 新增用户

 

用户新增成功,列表正常显示数据

 

7.2 修改数据

修改成功

7.3 分页正常

 

至此,展现层完成了

 

8、完整项目架构如下

 

posted @ 2017-03-13 00:40 蓝建荣 阅读(...) 评论(...) 编辑 收藏