![](./imgs/znzmsy.jpg) > 需求 - **共性提炼**:因为从首页项目,到楼层、区域、电箱展示内容格式相同,视图模型需要提炼出基类。 - **层级扩展**:设计图中给出的是"项目、楼层、区域、电箱",如果有更多的层级如何处理?这里不能针对每个层级硬编码,这样会写很多接口,应该保持尽量少的接口。 - **分页**:如果列表项很多,需要分页。 - **中介模式**:希望控制器方法内代码尽量简洁,把请求交给一套机制,该机制返回响应。这里采用中介者模式。 > 视图模型设计 从前端发来的请求,服务端需要通过类来封装。所有的请求都涉及到分页、集团相关、项目相关、连接字符串相关,提炼出一个基类。 BaseQuery.cs ``` public SieveModel PagingModel{get;set;} public string GroupId{get;set;} public string ProjectId{get;set;} public string ConnKey{get;set;} ``` 以上,`SieveModel`来自一个第三方的有关分页的库,叫做`Sieve`。 考虑到首页和内页不同之处,在基类基础上再设计: GeneralSumQuery.cs ``` //IRequest来自一个中介者模式的库MediatR //意思是说,只要给我GeneralSumQuery,我就返回GeneralSumViewModel给外界 //GeneralSumViewModel这个模型是给手机端,手机端拿着这个模型去填充页面 public class GeneralSumQuery : BaseQuery, IRequest { //首页的Project和内页的Location都需要用到 //当Level=0,就代表首页Project,当Level=1比如代表楼层,以此类推 public int Level{get;set;} //这个属性是给Location用的,就是根据不同的LocationId去加载其下的子级Location //子级Location有一个属性叫做ParentId,这个属性就指向父级的LocationId //所有,根据父级的主键,也就是这里的LocationId能获得其下的所有子级Location public string LocationId{get;set;} } ``` `GeneralSumViewModel`是返回给手机端用的,它代表如下红框中的部分: ![](./imgs/vm1.png) 对应代码: ``` public class GeneralSumViewModel { //一旦有集合属性,一定要在构造函数内初始化,否则容易报null的错 public GeneralSumViewModel() { Items = new List(); } public List Items{get;set;} } ``` `GeneralSumItemViewmodel`代表这列表项,即如下红框中的部分: ![](./imgs/vm2.png) 代码如下: ``` public class GeneralSumItemViewModel : BaseViewModel { /// /// 名称:项目名称、楼层名称、区域名称、电箱名称 /// public string Name { get; set; } /// /// 报警颜色:WarningColorEnum /// public short Color { get; set; } /// /// 是否可以进入到下一级 /// public bool IsContinue { get; set; } /// /// 下一个位置层级 /// public int NextLevel { get; set; } /// /// 本层Location主键,项目的LocationId=-1 /// public string LocationId { get; set; } /// /// 报警 /// public GeneralWarningItemViewModel WarningItem { get; set; } /// /// 负荷 /// public GeneralFuHeItemViewModel FuHeItem { get; set; } /// /// 开关比例 /// public GeneralKGBLItemViewModel KGBLItem { get; set; } ``` 视图模型的基类: ``` public class BaseViewModel { public string ProjectId { get; set; } public string ConnKey { get; set; } } ``` 有关报警的视图模型: ``` public class GeneralWarningItemViewModel : BaseViewModel { /// /// 报警描述 /// public string Description { get; set; } /// /// 报警颜色:WarningColorEnum /// public short Color { get; set; } /// /// 报警数 /// public string WarningNum { get; set; } } ``` 有关负荷的视图模型: ``` public class GeneralFuHeItemViewModel : BaseViewModel { /// /// 负荷值 /// public string FuHeValue { get; set; } } ``` 有关开关比例的视图模型: ``` public class GeneralKGBLItemViewModel : BaseViewModel { public string OpenNum { get; set; } public string CloseNum { get; set; } } ``` 以上,包括接口所接受的模型和接口响应给前端的视图模型。 输入和输出定下来了,现在需要通过中介者模式让处理请求,输出响应。如下: ![](./imgs/mediator.png) > 处理请求和响应,中介者模式 MediatR是一个有关中介者模式的第三方库,它的作者就是大名鼎鼎的AutopMapper的作者,而AutoMapper是用来建立DTO和Entity Framework模型之间映射的第三方库。 HomeController.cs ``` [Route("api/home")] public class HomeController : BaseController { private readonly IHostingEnvironment _env; public HomeController(IHostingEnvironment env) { _env = env; } /// /// 获取首页项目列表 /// /// /// 返回首页项目列表 [Authorize] [Route("projects")] [HttpPost] [ProducesResponseType(200)] public async Task GetProjects(BaseFlatQuery query) { var generalQuery = new GeneralSumQuery { Level = 0, LocationId = string.Empty, GroupId = User.FindFirst(GlobalSettings.Auth_GroupId).Value, ConnKey = string.Empty, ProjectId = string.Empty, PagingModel = new SieveModel { Page=query.Page, PageSize = query.PageSize } }; return Ok(await Mediator.Send(generalQuery)); } /// /// 获取楼层、区域等列表 /// /// /// 返回楼层、区域等列表 [Authorize] [Route("locations")] [HttpPost] [ProducesResponseType(200)] public async Task GetLocations(GeneralLocationQuery query) { var generalQuery = new GeneralSumQuery { Level = query.Level, LocationId = query.LocationId, GroupId=string.Empty, ConnKey=query.ConnKey, ProjectId = query.ProjectId, PagingModel = new SieveModel { Page = query.Page, PageSize = query.PageSize } }; return Ok(await Mediator.Send(generalQuery)); } /// /// 首页测试数据 /// /// /// 返回首页测试数据 [Authorize] [HttpPost] [Route("test")] [ProducesResponseType(200)] public IActionResult Test() { //return Ok(new { Id = 1, Name="这里是测试数据"}); return Ok(User.FindFirst(GlobalSettings.Auth_GroupId).Value); } } ``` 为什么需要`BaseFlatQuery`这个类?为什么不能用`BaseQuery`这个类?因为`BaseQuery`有些属性不是前端输入的,比如GroupId, ConnKey,这些属性是根据用户的相关信息赋值的,前端的请求不需要这个。而且在`BaseQuery`中有关分页的属性SieveModel中的Page和PageSize是可空的,在空的情况Sieve这个分页库会区找全局设置中的相关设置,即到appsettings.json中去找。总之,`BaseFlatQuery`是用来方便前端输入的,尽量让前端输入最简,并且没有嵌套类。同时,使用`BaseFlatQuery`这个类更安全,因为外界无从知晓服务端类的属性结构。 MediatR的工作原理: - 定义请求: ``` public class MyQuery : IRequest {} ``` - 定义处理流程 ``` public class MyQueryHandler : IRequestHandler { public async Task Handler(MyQuery request, CancellationToken cancellationToken) { } } ``` MediatR的实现细节略去。