后端freestyle
后端核心其实就展示层(控制器层)和应用服务层(业务逻辑层),展示层通过应用服务层定义一些业务接口来交互,他们之间的数据传输通过 dto 对象。
对于 post 请求的数据,有一些同学为了图方便,直接用实体来接收前端数据,不建议大家这么做。我们是规定必须建一个 model 类来接收,也就是 dto。下面是添加、更新和删除的示例:
[HttpPost] public ActionResult Add(AddWikiMenuItemInput input) { IWikiMenuItemAppService service = this.CreateService<IWikiMenuItemAppService>(); WikiMenuItem entity = service.Add(input); return this.AddSuccessData(entity); } [HttpPost] public ActionResult Update(UpdateWikiMenuItemInput input) { IWikiMenuItemAppService service = this.CreateService<IWikiMenuItemAppService>(); service.Update(input); return this.UpdateSuccessMsg(); } [HttpPost] public ActionResult Delete(string id) { IWikiMenuItemAppService service = this.CreateService<IWikiMenuItemAppService>(); service.Delete(id); return this.DeleteSuccessMsg(); } |
AddWikiMenuItemInput 类:
[MapToType(typeof(WikiMenuItem))] public class AddWikiMenuItemInput : ValidationModel { public string ParentId { get; set; } [RequiredAttribute(ErrorMessage = "名称不能为空")] public string Name { get; set; } public string DocumentId { get; set; } public bool IsEnabled { get; set; } public int? SortCode { get; set; } } |
数据校验我们使用 .NET 自带的 Validator,所以我们可以在 dto 的成员上打一些验证标记,同时要继承我们自定义的一个类,ValidationModel,这个类有一个 Validate 方法,我们验证数据是否合法的时候只需要调用下这个方法就好了:dto.Validate()。按照常规做法,数据校验应该在控制器的 Action 里,但目前我是将这个校验操作放在了应用服务层里。
对于 dto,最终是要与实体建立映射关系的,所以,我们还要给 dto 打个 [MapToType(typeof(WikiMenuItem))] 标记,表示这个 dto 类映射到 WikiMenuItem 实体类。
应用服务层添加、更新和删除数据实现:
public class WikiMenuItemAppService : AdminAppService, IWikiMenuItemAppService { public WikiMenuItem Add(AddWikiMenuItemInput input) { input.Validate(); WikiMenuItem entity = this.DbContext.InsertFromDto<WikiMenuItem, AddWikiMenuItemInput>(input); return entity; } public void Update(UpdateWikiMenuItemInput input) { input.Validate(); this.DbContext.UpdateFromDto<WikiMenuItem, UpdateWikiMenuItemInput>(input); } public void Delete(string id) { id.NotNullOrEmpty(); bool existsChildren = this.DbContext.Query<WikiMenuItem>(a => a.ParentId == id).Any(); if (existsChildren) throw new InvalidDataException("删除失败!操作的对象包含了下级数据"); this.DbContext.DeleteByKey<WikiMenuItem>(id); } } |
DbContext.InsertFromDto 和 DbContext.UpdateFromDto 是 ORM 扩展的方法,通用的,定义好 dto,并给 dto 标记好映射实体,调用这两个方法时传入 dto 对象就可以插入和更新。从 dto 到将数据插进数据库,有数据校验,也不用拼 sql!这都是基于 ORM 和 AutoMapper 的配合。
日常开发中,频繁的写 try catch 代码是件很蛋疼的事,因此,我们可以定义一个全局异常处理的过滤器去记录错误信息,配合 NLog 组件,MVC中任何错误都会被记录进文件。所以,如果下载了源码你会发现,项目中几乎没有 try catch 类的代码。
public class HttpGlobalExceptionFilter : IExceptionFilter { private readonly IHostingEnvironment _env; public HttpGlobalExceptionFilter(IHostingEnvironment env) { this._env = env; } public ContentResult FailedMsg(string msg = null) { Result retResult = new Result(ResultStatus.Failed, msg); string json = JsonHelper.Serialize(retResult); return new ContentResult() { Content = json }; } public void OnException(ExceptionContext filterContext) { if (filterContext.ExceptionHandled) return; //执行过程出现未处理异常 Exception ex = filterContext.Exception; #if DEBUG if (filterContext.HttpContext.Request.IsAjaxRequest()) { string msg = null; if (ex is Ace.Exceptions.InvalidDataException) { msg = ex.Message; filterContext.Result = this.FailedMsg(msg); filterContext.ExceptionHandled = true; return; } } this.LogException(filterContext); return; #endif if (filterContext.HttpContext.Request.IsAjaxRequest()) { string msg = null; if (ex is Ace.Exceptions.InvalidDataException) { msg = ex.Message; } else { this.LogException(filterContext); msg = "服务器错误"; } filterContext.Result = this.FailedMsg(msg); filterContext.ExceptionHandled = true; return; } else { //对于非 ajax 请求 this.LogException(filterContext); return; } } /// <summary> /// 将错误记录进日志 /// </summary> /// <param name="filterContext"></param> void LogException(ExceptionContext filterContext) { ILoggerFactory loggerFactory = filterContext.HttpContext.RequestServices.GetService(typeof(ILoggerFactory)) as ILoggerFactory; ILogger logger = loggerFactory.CreateLogger(filterContext.ActionDescriptor.DisplayName); logger.LogError("Error: {0}, {1}", ReplaceParticular(filterContext.Exception.Message), ReplaceParticular(filterContext.Exception.StackTrace)); } static string ReplaceParticular(string s) { if (string.IsNullOrEmpty(s)) return s; return s.Replace("\r", "#R#").Replace("\n", "#N#").Replace("|", "#VERTICAL#"); } } |
结语
咱做开发的,避免不了千篇一律的增删查改,所以,我们要想尽办法 write less,do more!这个项目只是一个入门学习的demo,并没什么特别的技术,但里面也凝聚了不少LZ这几年开发经验的结晶,希望能对一些猿友有用。大家有什么问题或建议可以留言讨论,也欢迎各位入群畅谈.NET复兴大计(群号见左上角)。最后,感谢大家阅读至此!
该项目使用的是vs2017开发,数据库默认使用 SQLite,配置好 SQLite 的db文件即可运行。亦支持 SqlServer 和 MySql,在项目找到相应的数据库脚本,运行脚本创建相关的表后修改配置文件(configs/appsettings.json)内数据库连接配置即可。
源码地址:https://github.com/shuxinqin/Ace