Thanks for visiting my blog!
As you might know, in ASP.NET Core, the MVC6 stack now includes the Web API functionality. Having a single stack has advantages and I’m happy they’ve converged the two stacks.
While working with early builds, I noticed the patterns for doing content negotiation weren’t working as expected so I defaulted to the MVC approach to REST APIs. In the RC1 build, it seems to be working as expected. Let’s talk about it.
Out of the Box
ASP.NET Core gives you options. You could write APIs like I started to in earlier builds:
[Route("api/[controller]")]
public class PeopleController : Controller
{
...
// GET: api/values
[HttpGet]
public JsonResult Get()
{
return Json(_people.Get());
}
...
```csharp
Using **JsonResult **and returning data with the **Json() **helper method works great and can get you started, but there are some limitations. This approach means that you have to only return JSON and that returning result codes that aren’t **200 (OK) **is a problem. If something goes wrong, a **500 **error is returned which isn’t the right thing in many cases.
### Removing the JSON Ceremony
Instead of using a **JsonResult**, we might change this to allow for the data to be returned without the ceremony of telling the controller about JSON:
```csharp
[Route("api/[controller]")]
public class PeopleController : Controller
{
...
// GET: api/values
[HttpGet]
public IEnumerable<Person> Get()
{
return _people.Get();
}
...
```csharp
You’ll notice that we’re returning **IEnumerable<Person> **instead so the action seems to indicate the content that is being returned. This solves the JSON-only approach, but still gives us problems with result codes. So this is getting closer to supporting both content negotiation and good result codes.
### Using Result Codes
Instead of using **JsonResult** or raw CLR types, let’s return **IActionResult **so we can return good result codes:
```csharp
// GET: api/people
[HttpGet]
public IActionResult Get()
{
return Ok(_people.Get());
}
```csharp
Notice that we’re returning **IActionResult **then we can simply wrap the data we’re returning with a helper method called **Ok **that returns **200 **with the result. This becomes more apparent when we look at the POST method that can return **BadRequest **and **Created**:
```csharp
// POST api/people
[HttpPost]
public IActionResult Post([FromBody]Person value)
{
if (string.IsNullOrWhiteSpace(value.Name))
{
return HttpBadRequest("Name cannot be empty");
}
value.Id = _people.Get().Max(p => p.Id) + 1;
_people.Add(value);
return Created($"/api/people/{value.Id}", value);
}
```csharp
You can see that if the name is empty, we’re returning a **BadRequest (400) **or we’re returning a **Created (201)** if we succeed. This is the best of both worlds. No JSON Ceremony and good result codes. This is what I think it should look like. But does content negotiation still work?
### Testing Content Negotiation
Out of the box, ASP.NET Core only has Json (and possibly plain text) as content types. If you need to support XML, you have to add it back to the input and output formatters. That’s pretty easy. First you need a new Nuget package in your project.json:
```js
"dependencies": {
"Microsoft.AspNet.Mvc": "6.0.0-rc1-final",
"Microsoft.AspNet.Server.Kestrel": "1.0.0-rc1-final",
"Microsoft.AspNet.StaticFiles": "1.0.0-rc1-final",
"Microsoft.Extensions.Configuration.FileProviderExtensions" : "1.0.0-rc1-final",
"Microsoft.Extensions.Configuration.Json": "1.0.0-rc1-final",
"Microsoft.Extensions.Logging": "1.0.0-rc1-final",
"Microsoft.Extensions.Logging.Console": "1.0.0-rc1-final",
"Microsoft.Extensions.Logging.Debug": "1.0.0-rc1-final",
"Microsoft.AspNet.Mvc.Formatters.Xml": "6.0.0-rc1-final"
},
With this added, we can configure the formatters in the configuration of the MVC in ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
...
// Add framework services.
services.AddMvc(config =>
{
// Add XML Content Negotiation
config.RespectBrowserAcceptHeader = true;
config.InputFormatters.Add(new XmlSerializerInputFormatter());
config.OutputFormatters.Add(new XmlSerializerOutputFormatter());
});
}
Note that the call to RespectBrowserAcceptHeader is false by default, so if you want content negotiation, you have to enable this. Now that if you run this in the browser (which sets the text/xml as an Accept header) you’ll see XML in the result:
Now that content negotiation is working, you’ll still want to fix the JSON rendering (where it preserves the .NET case) by adding Camel-casing.
Fixing JSON Rendering
You can add camelCase support to JSON by configuring the JSON options:
services.AddMvc(config =>
{
// Add XML Content Negotiation
config.RespectBrowserAcceptHeader = true;
config.InputFormatters.Add(new XmlSerializerInputFormatter());
config.OutputFormatters.Add(new XmlSerializerOutputFormatter());
})
.AddJsonOptions(opts =>
{
// Force Camel Case to JSON
opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
});
By calling AddJsonOptions, you can set the contract resolver to use **camelCase **by default. With these small changes, you can build your API services very similar to the way we did in MVC5/Web API. I like the approach and except for the attributes for routing, I love the approach.
Playing with the Code
I’ve uploaded the example to github if you want to play with it:
I’ll be updating my http://shawnw.me/learnaspnetcore course with this new approach when I update it for the RC2 once that is released soon.