Page 55 - MSDN Magazine, July 2017
P. 55
itself (GetById). Here, the prefix is identified to use a GET call, for the same applies to PutCartItem, PatchCartItemQuantity, Delete- CartItem and PostCheckout.
Those last few action methods could work, but even if they did, how readable would they be? And more important, if a new person on your team looked only at PostCheckout, he might not know it refers to a POST call to submit your cart for checkout, or if he would interpret the method as called after (or post) you had already checked out. For readability and to make your method more developer-friendly, you’ll decorate your method with attributes.
Verb attributes give developers the ability to decorate action methods to specify what HTTP actions can be performed. With the GetById method, you can make it so that only GET calls can access it:
[HttpGet]
public Cart GetById(int id){ return _carts.FirstOrDefault(x => x.Id == id); }
Attribute Routing takes precedence over naming by conven- tion. For example, if you decide to change the name of the method from GetById to PutById, it would still only allow the HTTP GET verb. Additionally, more than one verb can be applied on a method. In the following example you can see two different ways of doing that. Other than personal preference on how you want to code it, there’s no difference between those two implementations:
[HttpGet]
[HttpPost]
public Cart FunctionName(int id){ //Implementation }
[HttpGet, HttpPost]
public Cart FunctionName (int id){ //Implementation }
Route Attributes help when Web API Methods start to become more complex. It’s also good practice to use them on the simple methods, as well, because they’ll continue the process of making methods more readable and, at the same time, create more con- strained protection from adverse calls.
As a reminder, because the controller is already decorated with a route prefix, you don’t need to repeat that at the method level. This information makes the route attribute on your current method simple. The curly brackets in your template identify that a parameter is to be passed into your route. Previously, this was the number 5:
[HttpGet]
[Route("{id}")]
public Cart GetById(int id){ return _carts.FirstOrDefault(x => x.Id == id); }
The name of the template parameter is important because that tells you that you need to have a parameter on your method with the same name. The following API method is a valid call from “api/ Carts/5.” The only problem is that cartId will be 0 and not 5 because the value 5 is in a parameter with the name “id”:
[HttpGet]
[Route("{id}")]
public Cart GetById(int cartId){ return _carts.FirstOrDefault(x => x.Id == id); }
API route template parameter constraints help filter which calls get made in a particular method. These constraints are import- ant because you don’t want your method doing too much. Web API methods should be simple and bring back specific data, not a bunch of data that the client will then have to filter and dig out for information. By specifying the int constraint “{id: int}” on the template parameter, you specify that this method will only be called by a route that has an integer in this template position. Now, if you decide to create a GetByName method, you know which method msdnmagazine.com
will be called for each, and more important, you don’t have to put logic into your method to determine if the parameter passed in was an integer or string value.
Route templates can be attached to a method in a few different ways. All of the following code examples will perform the same way:
[HttpGet]
[Route("{id: int}")]
public Cart GetById(int cartId){ return _carts.FirstOrDefault(x => x.Id == id); }
[HttpGet, Route("{id: int}")]
public Cart GetById(int cartId){ return _carts.FirstOrDefault(x => x.Id == id); }
[HttpGet("{id: int}")]
public Cart GetById(int cartId){ return _carts.FirstOrDefault(x => x.Id == id); }
The Return Type specified doesn’t always have to be the actual class/value type you’re returning. In many cases, your method will have more flexibility if you return IActionResult.
Web API methods should be simple and bring back specific data, not a bunch of data that the client will then have to filter and dig out the information.
Your GetById method would actually benefit from this return type. When a cart can’t be found, currently null is passed back to the client, but as a success. You don’t want your client to have to perform that check, nor do you want your clients to have to deter- mine in their ajax catch if this call had a data problem or a code problem, which could affect any messaging relayed to the user. You pass back built-in result objects, which automatically create the appropriate response message and status code:
[HttpGet, Route("{id: int}")]
public IActionResult GetById(int cartId){
var cart = _carts.FirstOrDefault(x => x.Id == id); if (cart != null) return Ok(cart);
return BadRequest();
}
Another added benefit of passing back IActionResult is that it doesn’t care what data type you pass back within the result object. If you had a method GetItem, you could return Ok(new ObjectA()) or Ok(new ObjectB()). The type returned within the OkObjectResult here is of different types.
Waiting ... Waiting ... Waiting ...
Your current Web API method is now in good shape, but it isn’t streamlined. It’s synchronous and still holding onto threads. You need to not only use async/await, but make sure you take in a CancellationToken parameter, as well. Using an asynchronous call lets you have more throughput come into your application, but if a process starts taking a long time and a user refreshes the page, navigates away, or closes the browser, that process still runs and occupies resources that could be allocated for another user. That’s why you always should implement a CancellationToken for every asynchronous GET method at a minimum. Adding in a
July 2017 49