先貼文章鏈接
正文
ASP.NET Core MVC 2.1 特意為構建 HTTP API 提供了一些小特性,今天主角就是 ApiControllerAttribute. (注:文章是18年2月份的,所以文章提到了core2.1還沒發布)。
0. ApiControllerAttribute 繼承自 ControllerAttribute
ASP.NET Core MVC 已經有了ControllerAttribute,這個用來標注一個類型是否是Controller。標注了之后框架就知道哪些是系統里面的Controller了。(框架也有其他方法來獲取程序里面的Controller,所以,這個ControllerAttribute不是必須的)。
ApiControllerAttribute是ControllerAttribute的子類,所以,框架在處理Controller發現的時候和ControllerAttribute標注的對象是一樣的。
但是,因為ApiControllerAttribute 實現了IApiBehaviorMetadata接口,所以提供了一些額外的特這些特性是以HTTP Api為出發點的。下面介紹一下這些特性。
1. 自動模型狀態驗證
這個是重點,框架會幫你自動驗證model的state,也就是ModelState.(注:不過我就是因為用FluentValidation的時候模型驗證不管用了出問題了才找到這篇文章的).
框架會為你自動注冊ModelStateInvalidFilter,這個會運行在OnActionExecuting事件里面(具體來說:在action執行之前,model綁定之后)。他內部會檢查ModelState是否為Valid,如果為InValid會直接返回400 BadRequest,這樣就沒有必要執行后面的代碼,提高效率。
它會自動把model state 放到response里面,content type 是application/problem+json。當然你也可以自定義,因為畢竟你會有自己的驗證,后文會講。
下面,我們先來舉個例子說一下。
- 之前的寫法
[Route("[controller]")]
public class BookController : Controller
{
[HttpPost("")]
public IActionResult PostBook([FromBody]Book book)
{
if (ModelState.IsValid) //判斷狀態
{
return BadRequest(ModelState);
}
//其他代碼。。。
}
}
- 現在可以這么寫
[ApiController]
[Route("[controller]")]
public class BookController : Controller
{
[HttpPost("")]
public IActionResult PostBook(Book book)
{
//直接寫,不用驗證modelstate
}
}
順道說一下,ModelStateInvalidFilter是個公共類,所以,不用ApiControllerAttribute也可以使用它。
2.參數綁定策略的自動推斷
另一個非常有用的特性是action里面的參數的模型綁定可以自動推斷。
ASP.NET Core MVC里面有一個比較令人惱怒的問題你需要手動給參數指定[FromBody]這個特性,以便讓系統知道如何從Request body里面反序列化他們,比如反序列化json。因此,寫了很多第三方的庫來解決這個問題,比如:
現在,這些可以自動解決了。
除此之外,如果一個參數在route里面定義了,他會自動從先從path,也就是url上嘗試綁定,不行的話會去從查詢參數上綁定。IFormFlie默認從form表單上綁定獲取。
下面看代碼:
- 之前
[Route("[controller]")]
public class BookController : Controller
{
[HttpPost("")]
public IActionResult PostBook([FromBody]Book book)
{
// 寫代碼
}
}
- 現在
[ApiController]
[Route("[controller]")]
public class BookController : Controller
{
[HttpPost("")]
public IActionResult PostBook(Book book)//FromBody沒必要寫了
{
// 寫代碼
}
}
3. 處理multipart/form-data請求
如果你的action里面的一個參數指定了[FromFile]特性(這通常是用於文件上傳的),框架會自動假設請求是multipart/form-data。這個是用來解決社區里面提的這個問題。
不過這個也是可選的,只要你自己定義在action上定義一下[Consumes(...)]。
4.其他
有兩個注意點:
- ApiExplorer 的可見性。 默認所有的controller對
ApiExplorer都是可見的,所以,不影響swagger 等的生成。 - 只是一個基於特性的路由。集中的路由機制不會應用在API controller,框架要求只能使用基於特性的路由,即在action上指定
[Route("XXX")]的方式。
5. 行為自定義
像MVC框架的大部分組件一樣,ApiControllerAttribute的行為是高度可自定義的。首先,上面說的大部分內容都是可以簡單的用 on/off 來切換。
具體的設置是在startup方法里面通過ApiBehaviorOptions來實現,先來看一下這個類。
public class ApiBehaviorOptions
{
public Func<ActionContext, IActionResult> InvalidModelStateResponseFactory { get; set; }
public bool SuppressModelStateInvalidFilter { get; set; }
public bool SuppressInferBindingSourcesForParameters { get; set; }
public bool SuppressConsumesConstraintForFormFileParameters { get; set; }
}
所有bool類型的屬性默認都是false。Suppres有阻止的意思。可以通過以下方法進行設置。
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true;
options.SuppressConsumesConstraintForFormFileParameters = true;
});
來看一下InvalidModelStateResponseFactory屬性,他是一個返回IActionResult的Func,通過他,我們可以注入自己的委托來實現需要的返回類型,舉個例子。
services.Configure<ApiBehaviorOptions>(options =>
{
options.InvalidModelStateResponseFactory = actionContext =>
{
var errors = actionContext.ModelState
.Where(e => e.Value.Errors.Count > 0)
.Select(e => new Error
{
Name = e.Key,
Message = e.Value.Errors.First().ErrorMessage
}).ToArray();
return new BadRequestObjectResult(errors);
}
});
class Error
{
public string Name { get; set; }
public string Message { get; set; }
}
