上次我們創建了項目的服務層,服務層在業務邏輯簡單,或項目運行初期不是很容易體現出他的價值;傳送門:項目架構開發:服務層(上)
服務層專門處理非業務邏輯的一些功能,比如緩存、異常處理、組織多個應用邏輯等;這次我們搭建最上層的展現層,用到的知識面包括以下:
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">«</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">»</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、完整項目架構如下