经过遵循最佳实践,能够编写更好的控制器。所谓的“瘦”控制器(指代码更少、职责更少的控制器)更容易阅读和维护。并且,一旦你的控制器很瘦,可能就不须要对它们进行太多测试了。相反,你能够专一于测试业务逻辑和数据访问代码。瘦控制器的另外一个优势是,它更容易维护控制器的多个版本。html
这篇文章讨论了使控制器变胖的坏习惯,而后探索了使控制器变瘦和易于管理的方法。我列出编写控制器的最佳实践可能并不全面,但我已经讨论了最重要的一些,并在适当的状况下提供了相关的源代码。在接下来的几节中,咱们将研究什么是胖控制器,为何它是一种代码坏味道,瘦控制器是什么,为何它是有益的,以及如何使控制器瘦、简单、可测试和可管理。数据库
从控制器中删除数据访问代码
在编写控制器时,你应该坚持单一责任原则,这意味着控制器应该有“一个责任”或“有且只有一个缘由能够更改”。换句话说,你但愿将更改控制器代码的缘由减至最少。下面的代码显示了具备数据访问逻辑的典型控制器。app
在.NET生态系统中使用特定的技术堆栈会产生一些困惑,由于有不少选择,好比应该使用哪一种类型的运行时?在这篇文章中,咱们将试图把这些要点都说清楚。asp.net
public class AuthorController : Controller { private AuthorContext dataContext = new AuthorContext(); public ActionResult Index(int authorId) { var authors = dataContext.Authors .OrderByDescending(x=>x.JoiningDate) .Where(x=>x.AuthorId == authorId) .ToList(); return View(authors); } }
在action内部使用数据上下文实例读取数据,违反了单一职责原则,并使你的控制器充斥着不该该出如今那里的代码。在本例中,咱们使用一个DataContext(假设咱们使用Entity Framework Core)来链接、处理数据库中的数据。函数
明天若是你决定更改数据访问技术(为了更好的性能或其余缘由),你也必须更改你的控制器。例如,若是我想使用Dapper链接到底层数据库该怎么办?更好的方法是使用repository类来封装数据访问逻辑(尽管我不太喜欢repository模式)。让咱们用如下代码更新AuthorController。工具
public class AuthorController : Controller { private AuthorRepository authorRepository = new AuthorRepository(); public ActionResult Index(int authorId) { var authors = authorRepository.GetAuthor(authorId); return View(authors); } }
控制器如今看起来更瘦了。那么这是编写这个控制器的最佳方法吗?不是。若是你的控制器正在访问数据访问组件,那么它将作太多的事情,所以违反了单一职责原则。控制器不该该有直接访问数据访问组件的数据访问逻辑或代码。下面是AuthorController类的改进版本。性能
public class AuthorController : Controller { private AuthorService authorService = new AuthorService(); public ActionResult Index(int authorId) { var authors = authorService.GetAuthor(authorId); return View(authors); } }
AuthorService类利用AuthorRepository类执行CRUD操做。测试
public class AuthorService { private AuthorRepository authorRepository = new AuthorRepository(); public Author GetAuthor (int authorId) { return authorRepository.GetAuthor(authorId); } }
避免编写样板代码来映射对象
你常常须要映射数据传输对象(DTO)和域对象,反之亦然。请参考下面给出的代码片断,它显示了控制器方法内部的映射逻辑。this
public IActionResult GetAuthor(int authorId) { var author = authorService.GetAuthor(authorId); var authorDTO = new AuthorDTO(); authorDTO.AuthorId = author.AuthorId; authorDTO.FirstName = author.FirstName; authorDTO.LastName = author.LastName; authorDTO.JoiningDate = author.JoiningDate; }
你不该该在控制器中编写这样的映射逻辑,由于它会使控制器膨胀并增长额外的责任。若是你要编写映射逻辑,能够利用像AutoMapper这样的对象映射器工具来避免编写大量样板代码。.net
最后,你应该将映射逻辑移到前面建立的服务类中。注意AutoMapper是如何被用来映射两个不兼容的类型Author和AuthorDTO的。
public class AuthorService { private AuthorRepository authorRepository = new AuthorRepository(); public AuthorDTO GetAuthor (int authorId) { var author = authorRepository.GetAuthor(authorId); return Mapper.Map<AuthorDTO>(author); } }
避免在控制器中编写业务逻辑代码
不该该在控制器中编写业务逻辑或验证逻辑。控制器应该只接受一个请求,而后跳转下一个action,除此以外没有其余的。全部的业务逻辑代码都应该转移到其余类中(好比咱们前面建立的AuthorService类)。有几种方法能够在请求管道中设置验证器,而不要在控制器中编写验证逻辑。这会使你的控制器变得没必要要的臃肿,并让它负责它不该该作的任务。
更喜欢依赖注入而不是组合
你应该更喜欢在控制器中使用依赖项注入来管理依赖项。依赖注入是控制反转(IoC)原则的一个子集。它用于经过容许从外部注入的依赖项删除内部依赖项。
经过利用依赖注入,你没必要关心对象的实例化、初始化等。你能够有一个返回所需类型实例的工厂,而后可使用构造函数注入来使用该实例。下面的代码片断说明了如何使用构造函数将IAuthorService类型的实例注入到AuthorController。(假设IAuthorService是AuthorService类扩展的接口。)
public class AuthorController : Controller { private IAuthorService authorService = new AuthorService(); public AuthorController(IAuthorService authorService) { this.authorService = authorService; } }
使用action过滤器来消除重复的代码
能够在asp.net core中使用action过滤器在请求管道中的特定点执行定制代码。例如,你可使用action过滤器在操做action方法执行以前和以后执行自定义代码。你能够从控制器的action方法中删除验证逻辑,并将其写入action过滤器中,而不是在控制器中编写验证逻辑。下面的代码片断显示了如何实现这一点。
[ValidateModelState] [HttpPost] public ActionResult Create(AuthorRequest request) { AuthorService authorService = new AuthorService(); authorService.Save(request); return RedirectToAction("Home"); }
你将多个职责分配给了一个控制器,那么也会有多个缘由致使控制器更改。所以,这违反了单一责任原则,该原则规定类应该有且只有一个变动的理由。
原文连接:https://www.infoworld.com/art...
欢迎关注个人公众号,若是你有喜欢的外文技术文章,能够经过公众号留言推荐给我。