【搬運】ASP.NET Core Web API Attributes
原文地址:http://www.dotnetcurry.com/aspnet/1390/aspnet-core-web-api-attributes
本文介紹了可應用於aspnet core 控制器中的特性,Route特性定義路由,HTTP字首的特性為Action的Http請求方法,From字首的為Action引數的取值來源,在結尾最後又列出了在不同的Http請求方法中對取值來源的支援。
With ASP.NET Core there are various attributes that instruct the framework where to expect data as part of an HTTP request - whether the body, header, query-string, etc.
With C#, attributes make decorating API endpoints expressive, readable, declarative and simple. These attributes are very powerful! They allow aliasing, route-templating and strong-typing through data binding; however, knowing which are best suited for each HTTP verb, is vital.
In this article, we'll explore how to correctly define APIs and the routes they signify. Furthermore, we will learn about these framework attributes.
As a precursor to this article, one is expected to be familiar with modern C#, REST, Web API and HTTP.
ASP.NET Core HTTP attributes
ASP.NET Core has HTTP attributes for seven of the eight HTTP verbs listed in the Internet Engineering Task Force (IETF) RFC-7231 Document. The HTTP TRACE verb is the only exclusion in the framework. Below lists the HTTP verb attributes that are provided:
- HttpGetAttribute
- HttpPostAttribute
- HttpPutAttribute
- HttpDeleteAttribute
- HttpHeadAttribute
- HttpPatchAttribute
- HttpOptionsAttribute
Likewise, the framework also provides a RouteAttribute. This will be detailed shortly.
In addition to the HTTP verb attributes, we will discuss the action signature level attributes. Where the HTTP verb attributes are applied to the action, these attributes are used within the parameter list. The list of attributes we will discuss are listed below:
- FromServicesAttribute
- FromRouteAttribute
- FromQueryAttribute
- FromBodyAttribute
- FromFormAttribute
Ordering System
Imagine if you will, that we are building out an ordering system. We have an order model that represents an order. We need to create a RESTful Web API that allows consumers to create, read, update and delete orders – this is commonly referred to as CRUD.
Route Attribute
ASP.NET Core provides a powerful Route attribute. This can be used to define a top-level route at the controller class – doing so leaves a common route that actions can expand upon. For example consider the following:
[Route("api/[Controller]")]
public class OrdersController : Controller
{
[HttpGet("{id}")]
public Task<Order> Get([FromRoute] int id)
=> _orderService.GetOrderAsync(id);
}
The Route attribute is given a template of "api/[Controller]". The "[Controller]" is a special naming convention that acts as a placeholder for the controller in context, i.e.; "Orders". Focusing our attention on the HttpGet we can see that we are providing a template argument of "{id}". This will make the HTTP Get route resemble "api/orders/1" – where the id is a variable.
HTTP GET Request
Let us consider an HTTP GET request.
In our collection of orders, each order has a unique identifier. We can walk up to the collection and ask for it by "id". Typical with RESTful best practices, this can be retrieved via its route, for example "api/orders/1". The action that handles this request could be written as such:
[HttpGet("api/orders/{id}")] //api/orders/7
public Task<Order> Get([FromRoute] int id,[FromServices] IOrderService orderService)
=> orderService.GetOrderAsync(id);
Note how easy it was to author an endpoint, we simply decorate the controller’s action with an HttpGet attribute.
This attribute will instruct the ASP.NET Core framework to treat this action as a handler of the HTTP GET verb and handle the routing. We supply an endpoint template as an argument to the attribute. The template serves as the route the framework will use to match on for incoming requests. Within this template, the {id} value corresponds to the portion of the route that is the "id" parameter.
This is a Task<Order> returning method, implying that the body of the method will represent an asynchronous operation that eventually yields an Order object once awaited. The method has two arguments, both of which leverage attributes.
First the FromRoute attribute tells the framework to look in the route (URL) for an "id" value and provide that as the id argument. Then the FromServices attribute – this resolves our IOrderService implementation. This attribute asks our dependency injection container for the corresponding implementation of the IOrderService. The implementation is provided as the orderService argument.
We then expressively define our intended method body as the order services’ GetOrderAsync function and pass to it the corresponding identifier.
We could have just as easily authored this to utilize the FromQuery attribute instead. This would then instruct the framework to anticipate a query-string with a name of "identifier" and corresponding integer value. The value is then passed into the action as the id parameters argument. Everything else is the same.
However, the most common approach is the aforementioned FromRoute usage – where the identifier is part of the URI.
[HttpGet("api/orders")] //api/orders?identifier=7
public Task<Order> Get([FromQuery(Name = "identifier")] int id,[FromServices] IOrderService orderService)
=> orderService.GetOrderAsync(id);
Notice how easy it is to alias the parameter?
We simply assign the Name property equal to the string "identifier" of the FromQuery attribute. This instructs the framework to look for a name that matches that in the query-string. If we were to omit this argument, then the name is assumed to be the name used as the actions parameter, "id". In other words, if we have a URL as "api/orders?id=17" the framework will not assign our “id” variable the number 17 as it is explicitly looking for a query-string with a name "identifier".
HTTP POST Request
Continuing with our ordering system, we will need to expose some functionality for consumers of our API to create orders.
Enter the HTTP POST request.
The syntax for writing this is seemingly identical to the aforementioned HTTP GET endpoints we just worked on. But rather than returning a resource, we will utilize an IActionResult. This interface has a large set of subclasses within the framework that are accessible via the Controller class. Since we inherit from Controller, we can leverage some of the conveniences exposed such as the StatusCode method.
With an HTTP GET, the request is for a resource; whereas an HTTP POST is a request to create a resource and the corresponding response is the status result of the POST request.
[HttpPost("api/orders")]
public async Task<IActionResult> Post([FromBody] Order order)
=> (await _orderService.CreateOrderAsync(order))
? (IActionResult)Created($"api/orders/{order.Id}", order) // HTTP 201
: StatusCode(500); // HTTP 500
We use the HttpPost attribute, providing the template argument.
This time we do not need an "{id}" in our template as we are being given the entire order object via the body of the HTTP POST request. Additionally, we will need to use the async keyword to enable the use of the await keyword within the method body.
We have a Task<IActionResult> that represents our asynchronous operation. The order parameter is decorated with the [FromBody] attribute. This attribute instructs the framework to pick the order out from the body of the HTTP POST request, deserialize it into our strongly-typed C# Order class object and provide it as the argument to this action.
The method body is an expression. Instead of asking for our order service to be provided via the FromServices attribute like we have demonstrated in our HTTP GET actions, we have a class-scope instance we can use. It is typically favorable to use constructor injection and assign a class-scope instance variable to avoid redundancies.
We delegate the create operation to the order services' invocation of CreateOrderAsync, giving it the order. The service returns a bool indicating success or failure. If the call is successful, we'll return an HTTP status code of 201, Created. If the call fails, we will return an HTTP status code of 500, Internal Server Error.
Instead of using the FromBody one could just as easily use the FromForm attribute to decorate our order parameter. This would treat the HTTP POST request differently in that our order argument no longer comes from the body, but everything else would stay the same. The other attributes are not really applicable with an HTTP POST and you should avoid trying to use them.
[HttpPost("api/orders")]
public async Task<IActionResult> Post([FromForm] Order order)
=> (await _orderService.CreateOrderAsync(order))
? Ok() // HTTP 200
: StatusCode(500);
Although this bends from HTTP conformity, it's not uncommon to see APIs that return an HTTP status code 200, Ok on success. I do not condone it.
By convention if a new resource is created, in this case an order, you should return a 201. If the server is unable to create the resource immediately, you could return a 202, accepted. The base controller class exposes the Ok(), Created() and Accepted() methods as a convenience to the developer.
HTTP PUT Request
Now that we're able to create and read orders, we will need to be able to update them.
The HTTP PUT verb is intended to be idempotent. This means that if an HTTP PUT request occurs, any subsequent HTTP PUT request with the same payload would result in the same response. In other words, multiple identical HTTP PUT requests are harmless and the resource is only impacted on the first request.
The HTTP PUT verb is very similar to the HTTP POST verb in that the ASP.NET Core attributes that pair together, are the same. Again, we will either leverage the FromBody or FromForm attributes. Consider the following:
[HttpPut("api/orders/{id}")]
public async Task<IActionResult> Put([FromRoute] int id, [FromBody] Order order)
=> (await _orderService.UpdateOrderAsync(id, order))
? Ok()
: StatusCode(500);
We start with the HttpPut attribute supply a template that is actually identical to the HTTP GET. As you will notice we are taking on the {id} for the order that is being updated. The FromRoute attribute provides the id argument.
The FromBody attribute is what will deserialize the HTTP PUT request body as our C# Order instance into the order parameter. We express our operation as the invocation to the order services’ UpdateOrderAsync function, passing along the id and order. Finally, based on whether we are able to successfully update the order – we return either an HTTP status code of 200 or 500 for failures to update.
The return HTTP status code of 301, Moved Permanently should also be a consideration. If we were to add some additional logic to our underlying order service – we could check the given “id” against the order attempting to be updated. If the “id” doesn't correspond to the give order, it might be applicable to return a RedirectPermanent - 301 passing in the new URL for where the order can be found.
HTTP DELETE Request
The last operation on our agenda is the delete operation and this is exposed via an action that handles the HTTP DELETE request.
There is a lot of debate about whether an HTTP DELETE should be idempotent or not. I lean towards it not being idempotent as the initial request actually deletes the resource and subsequent requests would actually return an HTTP status code of 204, No Content.
From the perspective of the route template, we look to REST for inspiration and follow its suggested patterns.
The HTTP DELETE verb is similar to the HTTP GET in that we will use the {id} as part of the route and invoke the delete call on the collection of orders. This will delete the order for the given id.
[HttpDelete("api/orders/{id}")]
public async Task<IActionResult> Delete([FromRoute] int id)
=> (await _orderService.DeleteOrderAsync(id))
? (IActionResult)Ok()
: NoContent();
While it is true that using the FromQuery with an HTTP DELETE request is possible, it is unconventional and ill-advised. It is best to stick with the FromRoute attribute.
Conclusion:
The ASP.NET Core framework makes authoring RESTful Web APIs simple and expressive. The power of the attributes allow your C# code to be decorated in a manner consistent with declarative programming paradigms. The controller actions' are self-documenting and constraints are easily legible. As a C# developer – reading an action is rather straight-forward and the code itself is elegant.
In conclusion and in accordance with RESTful best practices, the following table depicts which ASP.NET Core attributes complement each other the best.
- The FromQuery attribute can be used to take an identifier that is used as a HTTP DELETE request argument, but it is not as simple as leveraging the FromRoute attribute instead.
- The FromHeader attribute can be used as an additional parameter as part of an HTTP GET request, however it is not very common – instead use FromRoute or FromQuery.
This article was technically reviewed by Daniel Jimenez Garcia.
相關文章
- ASP.NET Core Web API 介面限流ASP.NETWebAPI
- ASP.NET Core Web API 與 SSLASP.NETWebAPI
- ASP.NET Core Web API 索引 (更新Redis in .NET Core)ASP.NETWebAPI索引Redis
- ASP.NET Core Web Api之JWT(一)ASP.NETWebAPIJWT
- ASP.NET Core Web API 整合測試ASP.NETWebAPI
- ASP.NET Core 1.0開發Web API程式ASP.NETWebAPI
- 使用 ASP.NET Core 和 MongoDB 建立 Web APIASP.NETMongoDBWebAPI
- ASP.NET Core Web API中使用SwaggerASP.NETWebAPISwagger
- ASP.NET Core Web API 流式返回,逐字顯示ASP.NETWebAPI
- 處理物料搬運單APIAPI
- ASP.NET Core Web API 教程 - Project ConfigurationASP.NETWebAPIProject
- 【ASP.NET Core】體驗一下 Mini Web APIASP.NETWebAPI
- 舊 WCF 專案成功遷移到 asp.net core web apiASP.NETWebAPI
- ASP.NET Core Web Api之JWT VS Session VS Cookie(二)ASP.NETWebAPIJWTSessionCookie
- ASP.NET Core Web Api之JWT重新整理Token(三)ASP.NETWebAPIJWT
- 針對ASP.NET Core Web API的先進架構ASP.NETWebAPI架構
- ASP.NET Core Web API 整合測試中使用 Bearer TokenASP.NETWebAPI
- ASP.NET Web API與Rest web api(一)ASP.NETWebAPIREST
- 詳情API介面上貨搬運獲取資訊API介面API
- ASP.NET Core 實戰:使用 ASP.NET Core Web API 和 Vue.js,搭建前後端分離框架ASP.NETWebAPIVue.js後端框架
- 從頭編寫 asp.net core 2.0 web api 基礎框架 (3)ASP.NETWebAPI框架
- 從頭編寫 asp.net core 2.0 web api 基礎框架 (1)ASP.NETWebAPI框架
- 從頭編寫 asp.net core 2.0 web api 基礎框架 (2)ASP.NETWebAPI框架
- ASP.NET Web API 特性ASP.NETWebAPI
- ASP.NET Web API 路由ASP.NETWebAPI路由
- 【ASP.NET Web API教程】2 建立各種Web APIASP.NETWebAPI
- 【ASP.NET Core】設定Web API 響應的資料格式——Produces 特性篇ASP.NETWebAPI
- 【ASP.NET Core】設定 Web API 響應資料的格式——FormatFilter特性篇ASP.NETWebAPIORMFilter
- 從頭編寫 asp.net core 2.0 web api 基礎框架 (5) EF CRUDASP.NETWebAPI框架
- 從頭編寫 asp.net core 2.0 web api 基礎框架 (4) EF配置ASP.NETWebAPI框架
- 使用ASP.NET Web API構建RESTful APIASP.NETWebAPIREST
- ASP.NET Core Web API Swagger 按標籤Tags分組排序顯示ASP.NETWebAPISwagger排序
- 使用靜態基類方案讓 ASP.NET Core 實現遵循 HATEOAS Restful Web APIASP.NETRESTWebAPI
- Vue與ASP.NET Core Web Api設定localhost與本地ip地址皆可訪問VueASP.NETWebAPIlocalhost
- ASP.NET Core 3.x API版本控制ASP.NETAPI
- 如何在 ASP.NET Core 中使用 API AnalyzerASP.NETAPI
- 測試 ASP.NET Core API ControllerASP.NETAPIController
- Asp.Net Core 統一Api返回值ASP.NETAPI