本文將介紹ASP.Net Core中Controller的使用方式。
在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/