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