[鐵人賽Day14] ASP.Net Core MVC 進化之路 - Controller

本文將介紹ASP.Net CoreController的使用方式。

 

MVC的設計模式中,

Controller扮演著承上啟下的工頭角色,

除了接收前端的Request

還需分派工作給Model層做(一般會把Model再細切Service層),

最後將工作的處置狀況透過Request回報給瀏覽器。

 

過去ASP.Net MVC5中Controller分為一般MVC及WebApi專用的兩種,

而ASP.Net Core將其基底合併為ControllerBase

透過繼承這個物件,你可以將兩者的程式碼合併在同一個Controller類別中。

 

那要如何建立一個Controller呢?

官方提供了3種實作Controller的方式

  • 類別命名後面加上"Controller"
  • 繼承類別命名後面加上"Controller"的類別。
  • 類別上方掛上 [Controller] Attribute裝飾。

但在ASP.Net Core範本專案中,

HomeController.cs預設繼承Controller 抽象類別,

而非使用上述官方介紹的方式。

透過查看Controller抽象類別源碼,

可以發現裡面裝載各種的ActionResult及中繼物件(如ViewBag、ViewData、TempData),

實際上這個是方便MVC開發而幫我們包裝好的類別,

它繼承了ControllerBase

如果想要開發團隊自訂的Controller,

繼承Controller 後再往上疊會比較方便。

 

官方還有介紹幾個比較古怪的Attribute - [NonController] 、[NonAction] ,

[NonAction] 大概是用在一些雞肋的小功能(例如瀏覽人數),

[NonController] 的使用情境感覺就比較少了?

如果同時掛上[Controller] 跟[NonController] ,

[NonController] 的優先順序會比較高喔。

 

Routing to Controller

Controller是接收請求與傳送回應的中介,

如果沒有搞懂Route的使用方式,

還蠻容易發生請求Mapping不到的的現象。

 

在前面路由的文章中有介紹過,

Controller預設會吃Startup Configure中的路由規則。

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    //app.UseMvcWithDefaultRoute();
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });
}

 

但我們可以在Controller中使用Attribute做自訂路由,

Mapping過程預設會忽略大小寫,

首先我們在TestController上面掛一個[Route("MyTest")]

[Route("MyTest")]
public class TestController : Controller
{
    public IActionResult Index()
    {
        return Content("Hello Controller");
    }
}

https://localhost:{your_port}/MyTest/Index會顯示找不到頁面(404),

https://localhost:{your_port}/MyTest/才會顯示Index的內容。

 

上面的Routing設定方式只能Mapping到唯一的Action,

否則當Action有兩個以上時它就無法判斷要套用哪條路由規則。

所以一旦在Controller上方掛上[Route]

下面的Action都要加上對應的[Route]才行,

所以我們在Index上方再掛上一個[Route("Index")]

[Route("MyTest")]
public class TestController : Controller
{
    [Route("MyIndex")]
    public IActionResult Index()
    {
        return Content("Hello Controller");
    }
}

https://localhost:44363/MyTest/MyIndex會顯示Index的內容。

 

假設在不同Controller中想設定不同的Default Action,

如果從Startup中設定需要設定很多規則,

透過[Route("")]可以設定預設路由。

[Route("[Controller]")]
public class TestController : Controller
{
    [Route("")]
    public IActionResult Index()
    {
        return Content("Hello Test / Index");
    }
}


[Route("[Controller]")]
public class SampleController : Controller
{

    [Route("")]
    public IActionResult Code()
    {
        return Content("Hello Sample / Code");
    }
}

https://localhost:{your_port}/Test/:Hello Test / Index。

https://localhost:{your_port}/Sample/:Hello Sample/ Code。

不管是Action或Controller都僅能在同一層級中設定一個預設路由,

否則會造成路由混淆的錯誤。

 

我們也可以透過[Http{Verb}("")]形式設定自訂路由,

範例如下:

[Route("[Controller]")]
public class TestController : Controller
{
     
    [HttpGet("Page")]
    public IActionResult Index()
    {
        return Content("Hello Test / Index Get");
    }
}

 

接著介紹一下ActionResult

在ASP.Net Core的版本,

我們可以回傳任意形式的Action,

如string、DateTime、Guid或自訂的物件等。

[HttpGet("Date")]
public DateTime Date()
{
    return DateTime.Now;
}

但根據回傳的型別也會產生不同的content-type,

當型別為string時會回傳text/plain

其餘則預設使用application/json作為content-type。

 

繼承Controller的控制器,

可以使用許多已經包裝好的ActionResult,

例如代表StatusCode的NotFound() 、BadRequest() ,

還有一般寫MVC最常用的View()

如果要轉址的話可以使用Redirect()

還有檔案下載的File()

那這些方法哪來的呢?

它們其實分別繼承自ControllerBase及Controller抽象類別,

階層關係說明如下圖。

這種黑魔法在開發上當然非常方便,

但針對設計架構上學習反而是更重要的事。

 

在Controller的Action中也有提供非同步(async)方式,

使用方式非常簡單,

只要將回傳結果包裝成Task<IActionResult> 就可以了,

示意程式如下:

public async Task<IActionResult> AsyncAction()
{
    var str = await DoSomethingAsync();

    return View();
}

public async Task<string> DoSomethingAsync()
{
    return "test";
}

 

最後補充一下DI的部分,

我們可以在Controller中使用建構式注入獲得已註冊的服務,

但如果只有單一Action會使用到這個服務,

建議使用[FromService] 於Action自身中注入服務,

使用方式可參考前面ModelBinding的文章。

 

Controller的介紹就先告一段落,

有寫錯的部分再麻煩各位大神指正。

 

參考

https://docs.microsoft.com/zh-tw/aspnet/core/mvc/controllers/actions?view=aspnetcore-2.1

https://www.strathweb.com/2016/09/controller-and-noncontroller-attributes-in-asp-net-core-mvc/