如何在 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章