如何在 ASP.NET Core 中写出更干净的 Controller
你可以遵循一些最佳實(shí)踐來(lái)寫出更干凈的 Controller,一般我們稱這種方法寫出來(lái)的 Controller 為瘦Controller,瘦 Controller 的好處在于擁有更少的代碼,更加單一的職責(zé),也便于閱讀和維護(hù),而且隨著時(shí)間的推移也容易做 Controller 的多版本。
這篇文章我們一起討論那些讓 Controler 變胖變臃腫的一些壞味道,并且一起探索讓 Controller 變瘦的手段,雖然我的一些在 Controller 上的最佳實(shí)踐可能不是專業(yè)的,但我每一步都提供相關(guān)源代碼來(lái)進(jìn)行優(yōu)化,接下來(lái)的章節(jié)中,我們會(huì)討論什么是 胖Controller,什么是 壞味道,什么是 瘦Controller,它能帶給我們什么福利?并且如何讓 Controller 變瘦,變簡(jiǎn)單,利測(cè)試,易維護(hù)。
從 Controller 中移除數(shù)據(jù)層代碼
當(dāng)在寫 Controller 的時(shí)候,你應(yīng)該遵守 單一職責(zé),也就意味著你的 Controller 只需做一件事情,換句話說(shuō),只有一個(gè)因素或者唯一一個(gè)因素能讓你修改 Controller 中的代碼,如果有點(diǎn)懵的話,考慮下面的代碼片段,它將 數(shù)據(jù)訪問(wèn)代碼 糅進(jìn)了 Controller 。
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);}//Other?action?methods }請(qǐng)注意上面的代碼在 Action 中使用了 dataContext 從數(shù)據(jù)庫(kù)讀取數(shù)據(jù),這就違反了單一職責(zé)原則,并直接導(dǎo)致了 Controller 的臃腫。
假如后續(xù)你需要修改 數(shù)據(jù)訪問(wèn)層 代碼,可能基于更好的性能或者你能想到的原因,這時(shí)候只能被迫在 Controller 中修改,舉個(gè)例子吧:假如你想把上面的 EF 改成 Dapper 去訪問(wèn)底層的 Database,更好的做法應(yīng)該是單獨(dú)拎出來(lái)一個(gè) repository 類來(lái)操控 數(shù)據(jù)訪問(wèn) 相關(guān)的代碼,下面是更新后的 AuthorController。
public?class?AuthorController?:?Controller {private?AuthorRepository?authorRepository?=?new?AuthorRepository();public?ActionResult?Index(int?authorId){var?authors?=?authorRepository.GetAuthor(authorId);return?View(authors);}//Other?action?methods }現(xiàn)在 AuthorController 看起來(lái)是不是精簡(jiǎn)多了,上面的代碼是不是就是最佳實(shí)踐呢?不完全是,為什么這么說(shuō)呢?上面這種寫法導(dǎo)致 Controller 變成了 數(shù)據(jù)訪問(wèn)組件,取出數(shù)據(jù)后必然少不了一些業(yè)務(wù)邏輯處理,這就讓 Controller 違反了 單一職責(zé),對(duì)吧,更通用的做法應(yīng)該是將 數(shù)據(jù)訪問(wèn)邏輯 封裝在一個(gè) service 層,下面是優(yōu)化之后的 AuthorController 類。
public?class?AuthorController?:?Controller {private?AuthorService?authorService?=?new?AuthorService();public?ActionResult?Index(int?authorId){var?authors?=?authorService.GetAuthor(authorId);return?View(authors);}//Other?action?methods }再看一下 AuthorService 類,可以看到它利用了 AuthorRepository ?去做 CURD 操作。
public?class?AuthorService {private?AuthorRepository?authorRepository?=?new?AuthorRepository();public?Author?GetAuthor?(int?authorId){return?authorRepository.GetAuthor(authorId);}//Other?methods }避免寫大量代碼做對(duì)象之間映射
在 DDD 開(kāi)發(fā)中,經(jīng)常會(huì)存在 DTO 和 Domain 對(duì)象,在數(shù)據(jù) Input 和 Output 的過(guò)程中會(huì)存在這兩個(gè)對(duì)象之間的 mapping,按照普通的寫法大概就是這樣的。
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;//Other?code...... }可以看到,這種一一映射的寫法讓 Controller 即時(shí)膨脹,同時(shí)也讓 Controller 增加了額外的功能,那如何把這種 模板式 代碼規(guī)避掉呢?可以使用專業(yè)的 對(duì)象映射框架 AutoMapper 去解決,下面的代碼展示了如何做 ?AutoMapper 的配置。
public?class?AutoMapping {public?static?void?Initialize(){Mapper.Initialize(cfg?=>{cfg.CreateMap<Author,?AuthorDTO>();//Other?code????????????});} }接下來(lái)可以在 Global.asax 中調(diào)用 Initialize() 初始化,如下代碼所示:
protected?void?Application_Start() {AutoMapping.Initialize();????????? }最后,可以將 mapping 邏輯放在 service 層中,請(qǐng)注意下面的代碼是如何使用 AutoMapper 實(shí)現(xiàn)兩個(gè)不兼容對(duì)象之間的映射。
public?class?AuthorService {private?AuthorRepository?authorRepository?=?new?AuthorRepository();public?AuthorDTO?GetAuthor?(int?authorId){var?author?=?authorRepository.GetAuthor(authorId);return?Mapper.Map<AuthorDTO>(author);}//Other?methods }避免在 Controller 中寫業(yè)務(wù)邏輯
盡量避免在 Controller 中寫 業(yè)務(wù)邏輯 或者 驗(yàn)證邏輯, Controller 中應(yīng)該僅僅是接收一個(gè)請(qǐng)求,然后被下一個(gè) action 執(zhí)行,別無(wú)其它,回到剛才的問(wèn)題,這兩種邏輯該怎么處理呢?
業(yè)務(wù)邏輯
這些邏輯可以封裝 XXXService 類中,比如之前創(chuàng)建的 AuthorService。
驗(yàn)證邏輯
這些邏輯可以用 AOP 的操作手法,比如將其塞入到 Request Pipeline 中處理。
使用依賴注入而不是硬組合
推薦在 Controller 中使用依賴注入的方式來(lái)實(shí)現(xiàn)對(duì)象之間的管理,依賴注入是 控制反轉(zhuǎn) 的一個(gè)子集,它通過(guò)外部注入對(duì)象之間的依賴從而解決內(nèi)部對(duì)象之間的依賴,很拗口是吧!
一旦你用了依賴注入方式,就不需要關(guān)心對(duì)象是怎么實(shí)例化的,怎么初始化的,下面的代碼展示了如何在 AuthorController 下的構(gòu)造函數(shù)中實(shí)現(xiàn) IAuthorService 對(duì)象的注入。
public?class?AuthorController?:?Controller {private?IAuthorService?authorService?=?new?AuthorService();public?AuthorController(IAuthorService?authorService){this.authorService?=?authorService;}//?Action?methods }使用 action filer 消除 Controller 中的重復(fù)代碼
可以利用 action filter 在 Request pipeline 這個(gè)管道的某些點(diǎn)上安插一些你的自定義代碼,舉個(gè)例子,可以使用 ActionFilter 在 Action 的執(zhí)行前后安插一些自定義代碼,而不是將這些業(yè)務(wù)邏輯放到 Controller 中,讓 Controller 不必要的膨脹,下面的代碼展示了如何去實(shí)現(xiàn)。
[ValidateModelState] [HttpPost] public?ActionResult?Create(AuthorRequest?request) {AuthorService?authorService?=?new?AuthorService();authorService.Save(request);return?RedirectToAction("Home"); }總的來(lái)說(shuō),如果一個(gè) Controller 被賦予了幾個(gè)職責(zé),那么只要是其中任何一個(gè)職責(zé)的原因,你都必須對(duì) Controller 進(jìn)行修改,總的來(lái)說(shuō),一定要堅(jiān)守 單一原則。
譯文鏈接:https://www.infoworld.com/article/3404472/how-to-write-efficient-controllers-in-aspnet-core.html
總結(jié)
以上是生活随笔為你收集整理的如何在 ASP.NET Core 中写出更干净的 Controller的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 如何成功搞垮一个团队?
- 下一篇: 探索 .NET团队对API的设计流程