如何在 ASP.NET Core 中寫出更干凈的 Controller
本文轉(zhuǎn)載自微信公眾號「 碼農(nóng)讀書」,作者 碼農(nóng)讀書。轉(zhuǎn)載本文請聯(lián)系 碼農(nóng)讀書公眾號。
你可以遵循一些最佳實(shí)踐來寫出更干凈的 Controller,一般我們稱這種方法寫出來的 Controller 為瘦Controller,瘦 Controller 的好處在于擁有更少的代碼,更加單一的職責(zé),也便于閱讀和維護(hù),而且隨著時間的推移也容易做 Controller 的多版本。
這篇文章我們一起討論那些讓 Controler 變胖變臃腫的一些壞味道,并且一起探索讓 Controller 變瘦的手段,雖然我的一些在 Controller 上的最佳實(shí)踐可能不是專業(yè)的,但我每一步都提供相關(guān)源代碼來進(jìn)行優(yōu)化,接下來的章節(jié)中,我們會討論什么是 胖Controller,什么是 壞味道,什么是 瘦Controller,它能帶給我們什么福利?并且如何讓 Controller 變瘦,變簡單,利測試,易維護(hù)。
從 Controller 中移除數(shù)據(jù)層代碼
當(dāng)在寫 Controller 的時候,你應(yīng)該遵守 單一職責(zé),也就意味著你的 Controller 只需做一件事情,換句話說,只有一個因素或者唯一一個因素能讓你修改 Controller 中的代碼,如果有點(diǎn)懵的話,考慮下面的代碼片段,它將 數(shù)據(jù)訪問代碼 糅進(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
- }
請注意上面的代碼在 Action 中使用了 dataContext 從數(shù)據(jù)庫讀取數(shù)據(jù),這就違反了單一職責(zé)原則,并直接導(dǎo)致了 Controller 的臃腫。
假如后續(xù)你需要修改 數(shù)據(jù)訪問層 代碼,可能基于更好的性能或者你能想到的原因,這時候只能被迫在 Controller 中修改,舉個例子吧:假如你想把上面的 EF 改成 Dapper 去訪問底層的 Database,更好的做法應(yīng)該是單獨(dú)拎出來一個 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 看起來是不是精簡多了,上面的代碼是不是就是最佳實(shí)踐呢?不完全是,為什么這么說呢?上面這種寫法導(dǎo)致 Controller 變成了 數(shù)據(jù)訪問組件,取出數(shù)據(jù)后必然少不了一些業(yè)務(wù)邏輯處理,這就讓 Controller 違反了 單一職責(zé),對吧,更通用的做法應(yīng)該是將 數(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<Author, AuthorDTO>();
- //Other code
- });
- }
- }
接下來可以在 Global.asax 中調(diào)用 Initialize() 初始化,如下代碼所示:
- protected void Application_Start()
- {
- AutoMapping.Initialize();
- }
最后,可以將 mapping 邏輯放在 service 層中,請注意下面的代碼是如何使用 AutoMapper 實(shí)現(xiàn)兩個不兼容對象之間的映射。
- 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ù)邏輯 或者 驗證邏輯, Controller 中應(yīng)該僅僅是接收一個請求,然后被下一個 action 執(zhí)行,別無其它,回到剛才的問題,這兩種邏輯該怎么處理呢?
- 業(yè)務(wù)邏輯
這些邏輯可以封裝 XXXService 類中,比如之前創(chuàng)建的 AuthorService。
- 驗證邏輯
這些邏輯可以用 AOP 的操作手法,比如將其塞入到 Request Pipeline 中處理。
使用依賴注入而不是硬組合
推薦在 Controller 中使用依賴注入的方式來實(shí)現(xiàn)對象之間的管理,依賴注入是 控制反轉(zhuǎn) 的一個子集,它通過外部注入對象之間的依賴從而解決內(nèi)部對象之間的依賴,很拗口是吧!
一旦你用了依賴注入方式,就不需要關(guān)心對象是怎么實(shí)例化的,怎么初始化的,下面的代碼展示了如何在 AuthorController 下的構(gòu)造函數(shù)中實(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 中的重復(fù)代碼
可以利用 action filter 在 Request pipeline 這個管道的某些點(diǎn)上安插一些你的自定義代碼,舉個例子,可以使用 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");
- }
總的來說,如果一個 Controller 被賦予了幾個職責(zé),那么只要是其中任何一個職責(zé)的原因,你都必須對 Controller 進(jìn)行修改,總的來說,一定要堅守 單一原則。
譯文鏈接:https://www.infoworld.com/article/3404472/how-to-write-efficient-controllers-in-aspnet-core.html