Squash Exceptions Thrown From CreatedAtRoute With These Tips
Tags: csharp, api, dotnet, rest
Published: 2021-02-04 13:55:03

ASP.Net Core's `CreatedAtRoute` method facilitates returning an HTTP 201 response and a Location header from a RESTful API. Implementing the method is straightforward; call CreatedAtRoute with a few parameters and the API will return the appropriate response. But pass invalid values for those parameters and all hell breaks loose. Instead of returning a 201 response, the API will return an HTTP 500 error. Here are some useful tips for avoiding exceptions raised by CreatedAtRoute.

RESTful API best practices dictate that an HTTP POST resulting in the creation of a resource returns a 201 status code and includes a Location header that points to the new resource's URL. 

ASP.Net Core's CreatedAtRoute method returns a `CreatedAtRouteResult` object which is an `ActionResult` that returns an HTTP 201 (Created) response with a Location header. The value returned as the Location header is specified via CreatedAtRoute's method parameters.

### Tip One: Use nameof() Instead of a String Value

CreatedAtRoute's `routeName` parameter is used to generate the URL returned as the Location header. Its value is a pointer which refers to a GET method in the API. Here's how it works; pass the API's GET method name as the routeName value. CreatedAtRoute uses that value to create the URL, which is then returned as the Location header. Suppose the API, named "resources", has a GET method defined as follows:
```c#
[HttpGet("{resourceId}", Name = "MyGetMethod")]
public async ActionResult GetResource(string resourceId)
```
Within the API's POST method, a call to CreatedAtRoute requires passing it "MyGetMethod" for the routeName parameter:
```c#
return CreatedAtRoute("MyGetMethod", ...);
```
The resulting CreatedAtRouteResult object will return a URL similar to the following in the Location header:

_https://localhost:8080/api/resources/123_

Since routeName is a string, it is susceptible to fat-fingered spelling mistakes. There is no way the compiler is going to know the string provided is the wrong value, so no compile-time error is raised. Mistakes made with the routeName parameter will lead to an _InvalidOperationException: No route matches the supplied values_ exception.

Avoid exceptions like these by using the `nameof` function instead of passing a string. Compile-time errors are raised if the nameof function is passed the wrong name of the API's GET method. The code snippet below shows the same call to CreatedAtRoute with the "MyGetMethod" string replaced with a call to nameof to return the GET method's name. In this case, nameof is taking the name of the GET method, _GetResource_, not the value from GET method's _name_ attribute, "MyGetMethod":
```c#
return CreatedAtRoute(nameof(GetResource), ...);
```
### Tip Two: Double-check the routeValues Parameter

The `routeValues` parameter is described in the Microsoft documentation as 

> the route data to use for generating the URL.

Essentially route data is any data that needs to be supplied to the GET method as method parameters, i.e., an id value. Refer back to the code snippet above where `GetResource` is defined. GetResource takes a resourceId parameter. In order to create the correct Location header, CreatedAtRoute needs to provide a value for the resourceId parameter. It does this using the routeValues parameter. 
```c#
string resourceId = "someUniqueValue";
return CreatedAtRoute(nameof(GetResource), new {resourceId}, ...);
```
In the preceding code snippet, a local variable, named resourceId, is assigned a value and then passed as an anonymous object to the routeValue parameter. Since a property name isn't specified in the anonymous object, the property name defaults to the local variable's name, which is the same name as GetResource's resourceId parameter. The correct Location header is returned because the property name matches GetResource's resourceId parameter name.

Exceptions occur when the anonymous object's property name doesn't match the GET method's parameter name. The following code, which names the local variable _resources_ instead of resourceId, throws an _InvalidOperationException_ exception at runtime because there is no GET method with a route path that matches with the name _resources_ in the API. 
```c#
string resources = "someUniqueValue";
return CreatedAtRoute(nameof(GetResource), new {resources}, ...);
```
Invalid parameter values passed to CreatedAtRoute throw an HTTP 500 exception of _No route matches the supplied values_. I have learned these tips the hard way. I have found that exceptions thrown from CreatedAtRoute can be difficult to troubleshoot and debug. The next time you get an InvalidOperationException from CreatedAtRoute, remember these tips, and your debugging session will go much smoother.