iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 14
0

前言

本文將介紹ASP.Net Core中Controller的使用方式。

同步發表於個人點部落 - [鐵人賽Day14] ASP.Net Core MVC 進化之路 - Controller

Controller

MVC的設計模式中,
Controller扮演著承上啟下的工頭角色,
除了接收前端的Request,
還需分派工作給Model層做(一般會把Model再細切Service層),
最後將工作的處置狀況透過Request回報給瀏覽器。

過去ASP.Net MVC5Controller分為一般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預設會吃StartupConfigure中的路由規則。

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。
不管是ActionController都僅能在同一層級中設定一個預設路由,
否則會造成路由混淆的錯誤。

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

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

接著介紹一下ActionResult
ASP.Net Core的版本,
我們可以回傳任意形式的Action,
stringDateTimeGuid或自訂的物件等。

[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()
那這些方法哪來的呢?
它們其實分別繼承自ControllerBaseController抽象類別,
階層關係說明如下圖。

這種黑魔法在開發上當然非常方便,
但針對設計架構上學習反而是更重要的事。

在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/


上一篇
[鐵人賽Day13] - View(3) / Partial View及View Component
下一篇
[鐵人賽Day15] - Model Validation(1) / 前端 vs. 後端驗證
系列文
菜鳥練等區-ASP.Net Core MVC進化之路30

尚未有邦友留言

立即登入留言