如何在 ASP.NET Core 中寫出更干凈的 Controller

你可以遵循一些最佳實踐來寫出更干凈的 Controller,一般我們稱這種方法寫出來的 Controller 為瘦Controller,瘦 Controller 的好處在于擁有更少的代碼,更加單一的職責,也便于閱讀和維護,而且隨著時間的推移也容易做 Controller 的多版本。
這篇文章我們一起討論那些讓 Controler 變胖變臃腫的一些壞味道,并且一起探索讓 Controller 變瘦的手段,雖然我的一些在 Controller 上的最佳實踐可能不是專業(yè)的,但我每一步都提供相關(guān)源代碼來進行優(yōu)化,接下來的章節(jié)中,我們會討論什么是?胖Controller,什么是?壞味道,什么是?瘦Controller,它能帶給我們什么福利?并且如何讓 Controller 變瘦,變簡單,利測試,易維護。
從 Controller 中移除數(shù)據(jù)層代碼
當在寫 Controller 的時候,你應該遵守?單一職責,也就意味著你的 Controller 只需做一件事情,換句話說,只有一個因素或者唯一一個因素能讓你修改 Controller 中的代碼,如果有點懵的話,考慮下面的代碼片段,它將 數(shù)據(jù)訪問代碼 糅進了 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 從數(shù)據(jù)庫讀取數(shù)據(jù),這就違反了單一職責原則,并直接導致了 Controller 的臃腫。
假如后續(xù)你需要修改?數(shù)據(jù)訪問層?代碼,可能基于更好的性能或者你能想到的原因,這時候只能被迫在 Controller 中修改,舉個例子吧:假如你想把上面的 EF 改成 Dapper 去訪問底層的 Database,更好的做法應該是單獨拎出來一個?repository?類來操控?數(shù)據(jù)訪問?相關(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 看起來是不是精簡多了,上面的代碼是不是就是最佳實踐呢?不完全是,為什么這么說呢?上面這種寫法導致 Controller 變成了?數(shù)據(jù)訪問組件,取出數(shù)據(jù)后必然少不了一些業(yè)務邏輯處理,這就讓 Controller 違反了?單一職責,對吧,更通用的做法應該是將?數(shù)據(jù)訪問邏輯?封裝在一個 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
}
避免寫大量代碼做對象之間映射
在 DDD 開發(fā)中,經(jīng)常會存在 DTO 和 Domain 對象,在數(shù)據(jù) 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 增加了額外的功能,那如何把這種?模板式?代碼規(guī)避掉呢?可以使用專業(yè)的?對象映射框架 AutoMapper?去解決,下面的代碼展示了如何做 ?AutoMapper 的配置。
public?class?AutoMapping
{
????public?static?void?Initialize()
????{
????????Mapper.Initialize(cfg?=>
????????{
????????????cfg.CreateMap();
????????????//Other?code????????????
????????});
????}
}
接下來可以在?Global.asax?中調(diào)用?Initialize()?初始化,如下代碼所示:
protected?void?Application_Start()
{
????AutoMapping.Initialize();?????????
}
最后,可以將 mapping 邏輯放在 service 層中,請注意下面的代碼是如何使用 AutoMapper 實現(xiàn)兩個不兼容對象之間的映射。
public?class?AuthorService
{
????private?AuthorRepository?authorRepository?=?new?AuthorRepository();
????public?AuthorDTO?GetAuthor?(int?authorId)
????{
????????var?author?=?authorRepository.GetAuthor(authorId);
????????return?Mapper.Map(author);
????}
????//Other?methods
}
避免在 Controller 中寫業(yè)務邏輯
盡量避免在 Controller 中寫?業(yè)務邏輯?或者?驗證邏輯, Controller 中應該僅僅是接收一個請求,然后被下一個 action 執(zhí)行,別無其它,回到剛才的問題,這兩種邏輯該怎么處理呢?
業(yè)務邏輯
這些邏輯可以封裝 XXXService 類中,比如之前創(chuàng)建的 AuthorService。
驗證邏輯
這些邏輯可以用 AOP 的操作手法,比如將其塞入到 Request Pipeline 中處理。
使用依賴注入而不是硬組合
推薦在 Controller 中使用依賴注入的方式來實現(xiàn)對象之間的管理,依賴注入是 控制反轉(zhuǎn) 的一個子集,它通過外部注入對象之間的依賴從而解決內(nèi)部對象之間的依賴,很拗口是吧!
一旦你用了依賴注入方式,就不需要關(guān)心對象是怎么實例化的,怎么初始化的,下面的代碼展示了如何在 AuthorController 下的構(gòu)造函數(shù)中實現(xiàn) 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 的執(zhí)行前后安插一些自定義代碼,而不是將這些業(yè)務邏輯放到 Controller 中,讓 Controller 不必要的膨脹,下面的代碼展示了如何去實現(xiàn)。
[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
【推薦】.NET Core開發(fā)實戰(zhàn)視頻課程?★★★
.NET Core實戰(zhàn)項目之CMS 第一章 入門篇-開篇及總體規(guī)劃
【.NET Core微服務實戰(zhàn)-統(tǒng)一身份認證】開篇及目錄索引
Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)
.NET Core中的一個接口多種實現(xiàn)的依賴注入與動態(tài)選擇看這篇就夠了
用abp vNext快速開發(fā)Quartz.NET定時任務管理界面
在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務輕松實現(xiàn)作業(yè)調(diào)度
