当前位置: 首页 > 科技观察

如何在ASP.NETCore中写出更干净的Controller

时间:2023-03-15 09:41:05 科技观察

本文转载请联系码农阅读公众号。您可以遵循一些最佳实践来编写更简洁的控制器。一般我们把这种方法写出来的Controller称为瘦Controller。瘦Controller的优点是代码少,职责单一,易读易维护,随着时间的推移很容易做多个版本的Controller。在这篇文章中,我们将讨论一些让Controller变胖、臃肿的坏习惯,并探索让Controller变瘦的方法。虽然我在Controller上的一些最佳实践可能并不专业,但我提供了每个步骤,在后面的章节中,我们将讨论什么是胖控制器,什么是难闻的气味,什么是瘦控制器,以及它能给我们带来什么好处?以及如何让控制器更薄、更简单、更易于测试和维护。去掉Controller中的数据层代码写Controller的时候应该遵循单一职责,也就是说你的Controller只需要做一件事,换句话说,只有一个因素或者只有一个因素是你可以修改的。控制器中的代码,如果有点混乱,请考虑以下代码片段,它将数据访问代码混合到控制器中。publicclassAuthorController:Controller{privateAuthorContextdataContext=newAuthorContext();publicActionResultIndex(intauthorId){varauthors=dataContext.Authors.OrderByDescending(x=>x.JoiningDate).Where(x=>x.AuthorId==authorId).ToList();returnView(authors);}//Otheractionmethods}请注意,上述代码在Action中使用dataContext从数据库中读取数据,违反了单一职责原则,直接导致了Controller的臃肿。如果以后需要修改数据访问层代码,可能是基于更好的性能或者你能想到的原因。这时候只能在Controller中强制修改了。例如:如果要把上面的EF改成Dapper来访问底层的Database,更好的做法应该是独立出一个repository类来控制数据访问相关的代码。以下是更新后的AuthorController。publicclassAuthorController:Controller{privateAuthorRepositoryauthorRepository=newAuthorRepository();publicActionResultIndex(intauthorId){varauthors=authorRepository.GetAuthor(authorId);returnView(authors);}//Otheractionmethods}现在AuthorController看起来更精简了,对吧?最佳实践如何?不完全是,为什么这么说?上面的写法使得Controller变成了一个数据访问组件。取完数据后,免不了要进行一些业务逻辑处理,这使得Controller违背了单一职责,对吧,更一般的做法应该是将数据访问逻辑封装在一个服务层,下面是优化后的AuthorController类。publicclassAuthorController:Controller{privateAuthorServiceauthorService=newAuthorService();publicActionResultIndex(intauthorId){varauthors=authorService.GetAuthor(authorId);returnView(authors);}//Otheractionmethods}再看AuthorService类,可以看到是用AuthorRepository做的凝乳操作。publicclassAuthorService{privateAuthorRepositoryauthorRepository=newAuthorRepository();publicAuthorGetAuthor(intauthorId){returnauthorRepository.GetAuthor(authorId);}//Othermethods}避免为对象之间的映射写很多代码在DDD开发中,数据中经常会有DTO和Domain对象Input这两个对象之间会有映射在输出输出的过程中,按照普通的写法大概是这样的。publicIActionResultGetAuthor(intauthorId){varauthor=authorService.GetAuthor(authorId);varauthorDTO=newAuthorDTO();authorDTO.AuthorId=author.AuthorId;authorDTO.FirstName=author.FirstName;authorDTO.LastName=author.LastName;authorDTO.authoring.Date=JoiningDate;//Othercode......}可见,这种一对一的映射方式,让Controller瞬间扩展,也为Controller增加了额外的功能,那么如何避免这段模板代码呢?可以使用专业的对象映射框架AutoMapper来解决。以下代码显示了如何配置AutoMapper。publicclassAutoMapping{publicstaticvoidInitialize(){Mapper.Initialize(cfg=>{cfg.CreateMap();//Othercode});}}接下来可以调用Global.asax中的Initialize()进行初始化,如图在下面的代码中显示:protectedvoidApplication_Start(){AutoMapping.Initialize();}最后,映射逻辑可以放在服务层,请注意下面的代码是如何使用AutoMapper实现两个不兼容对象之间的映射的。publicclassAuthorService{privateAuthorRepositoryauthorRepository=newAuthorRepository();publicAuthorDTOGetAuthor(intauthorId){varauthor=authorRepository.GetAuthor(authorId);returnMapper.Map(author);}//Othermethods}尽量避免在Controller中写业务逻辑业务逻辑或者验证逻辑,Controller应该只接收一个请求,然后被下一个action执行,没有别的,回到刚才的问题,这两个逻辑怎么处理?这些业务逻辑可以封装在XXXService类中,比如前面创建的AuthorService。Validationlogic这些逻辑可以使用AOP操作,比如插入到RequestPipeline中进行处理。使用依赖注入而不是硬组合。推荐在Controller中使用依赖注入来实现对象之间的管理。依赖注入是控制反转的一个子集。它通过在外部注入对象之间的依赖关系来解决内部对象之间的关系。依赖是一口,对吧?一旦你使用了依赖注入方法,你就不需要关心对象是如何被实例化或初始化的。下面的代码展示了如何在AuthorController下的构造函数中实现IAuthorService对象的注入。publicclassAuthorController:Controller{privateIAuthorServiceauthorService=newAuthorService();publicAuthorController(IAuthorServiceauthorService){this.authorService=authorService;}//Actionmethods}使用actionfilter消除Controller中的重复代码。你可以使用actionfilter在requestpipeline的某个点插入一些你自定义的代码,比如你可以使用ActionFilter在Action执行前后插入一些自定义代码,而不是把这些业务逻辑放在Controller中,使得Controller不必要的臃肿,下面的代码展示了如何完成。[ValidateModelState][HttpPost]publicActionResultCreate(AuthorRequestrequest){AuthorServiceauthorService=newAuthorService();authorService.Save(request);returnRedirectToAction("Home");}一般来说,如果给一个Controller几个职责,只要是对于任何这些责任的原因,你必须修改控制器。一般来说,你必须坚持一个原则。翻译链接:https://www.infoworld.com/article/3404472/how-to-write-efficient-controllers-in-aspnet-core.html