Skip to content
This repository has been archived by the owner on Nov 13, 2021. It is now read-only.

Provide guideline for content negotiation #135

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9514baa
Update startup
FilipVanRaemdonck Oct 8, 2019
7ce78c7
Update formatter usage
FilipVanRaemdonck Oct 9, 2019
927802c
Fix typo
FilipVanRaemdonck Oct 9, 2019
6814c3c
rename
FilipVanRaemdonck Oct 9, 2019
bbaa4ed
Restructure
FilipVanRaemdonck Oct 9, 2019
68b3900
update structure
FilipVanRaemdonck Oct 9, 2019
9c3c5c8
Fix typo
FilipVanRaemdonck Oct 9, 2019
5682ea1
Fix typo
FilipVanRaemdonck Oct 9, 2019
8b7c9b2
typos
FilipVanRaemdonck Oct 9, 2019
5960ab1
typos
FilipVanRaemdonck Oct 9, 2019
2b4433a
Structure readme
FilipVanRaemdonck Oct 9, 2019
3b499e7
Updates in readme
FilipVanRaemdonck Oct 9, 2019
15fb8de
Update readme
FilipVanRaemdonck Oct 9, 2019
7e2b4a9
Review PR comments
FilipVanRaemdonck Oct 9, 2019
b7d8526
Add suggestions to improve readability
FilipVanRaemdonck Oct 9, 2019
969eb21
Update createCar example
FilipVanRaemdonck Oct 9, 2019
cb0fcee
Update - add CompaitbilityVersion
FilipVanRaemdonck Oct 10, 2019
c31adb6
Merge with master
FilipVanRaemdonck Oct 10, 2019
9bdd199
add summary + move long text to separate md
FilipVanRaemdonck Oct 10, 2019
8dca836
Undo code changes for readme
FilipVanRaemdonck Oct 10, 2019
23528ce
Update PR review
FilipVanRaemdonck Oct 10, 2019
4abfa89
Fix sentence in readme
FilipVanRaemdonck Oct 10, 2019
2df1802
Review readme contents
FilipVanRaemdonck Oct 10, 2019
fb67085
Remove startup changes + update readme
FilipVanRaemdonck Oct 10, 2019
04afee2
Add links to more information
FilipVanRaemdonck Oct 10, 2019
3cb9690
Review PR comments
FilipVanRaemdonck Oct 10, 2019
d2af233
Undo coding changes
FilipVanRaemdonck Oct 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions maturity-level-two/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,55 @@ You should:
- Add your OpenAPI specs to source control given this is part of your application
- Validate changes to your OpenAPI specs to avoid specification violations ([user guide](docs/validating-open-api-specs.md))
- Unit test Open API validation to automatically detect breaking changes

## Content negotiation
MassimoC marked this conversation as resolved.
Show resolved Hide resolved
When no specific compatibility requirements regarding the rest request and response formats are set, it is recommended to use the JSON format (application/json). But in some situations a client might require the data to be formatted in a certain format in order to interpret the data correctly. With content negotiation we determine in what format we'd like to receive requests & responses. For REST API's, the most common formats are JSON and XML.
As an API consumer, you can specify what format you are expecting by adding HTTP headers:
- `Content-Type` - Specify the format of the request
MassimoC marked this conversation as resolved.
Show resolved Hide resolved
- `Accept` - Specify the requested format of the response. Default format will be used when not specified.
When a format is not supported, the API should return an [HTTP 406 Not Acceptable](https://httpstatuses.com/406).
Because of it's lightness and fastness, JSON has become the standard over the last couple of years but in certain situations other formats still have their advantages. In addition to this, ASP.NET Core also uses some additional formatters for special cases, such as the TextOutputFormatter and the HttpNoContentOutputFormatter.

When you would like to make sure your api only uses the JSON format, you can specify this in the startup of your ASP.NET Core project. This will make sure you'll refuse all non-JSON 'Accept' headers, including the plain text headers (on these requests you will return response code 406). If no 'Accept' header is specified you can return JSON as it is the only supported type. You can find an example of the changes you have to do to the startup below:
```csharp
services.AddMvc(options =>
{
var jsonInputFormatters = options.InputFormatters.OfType<JsonInputFormatter>();
var jsonInputFormatter = jsonInputFormatters.First();
MassimoC marked this conversation as resolved.
Show resolved Hide resolved
options.InputFormatters.Clear();
options.InputFormatters.Add(jsonInputFormatter);
var jsonOutputFormatters = options.OutputFormatters.OfType<JsonOutputFormatter>();
var jsonOutputFormatter = jsonOutputFormatters.First();
options.OutputFormatters.Clear();
options.OutputFormatters.Add(jsonOutputFormatter);
});
```
In case you'd like to add other formatting possibilities, it is possible to add these formatters to your api formatters. In case of xml you can use the XmlSerializerInputFormatter and the XmlSerializerOutputFormatter or add the xml formatters using the Mvc. With this approach the default format is still JSON.
MassimoC marked this conversation as resolved.
Show resolved Hide resolved
```csharp
services.AddMvc()
.AddNewtonsoftJson()
.AddXmlSerializerFormatters();
```
Be aware that the formatters you specify in the above section are all the formatters your api will know. Thus if an api call is done towards an action in which an unknown request or response format is used/requested, the api will not answer the call with a success status code but rather with a 406 - Not Acceptable. This means that if you have one action on which the user can request a custom/other response format, you'll have to add a formatter for this type as well - and by default the other actions will support this format too.

You can (not should) further restrict the request and respnse formats for one specific acion or controller by using the [Produces] and [Consumes] attributes. However you should be careful when using these attributes: if you use these attributes your method will not be able to return another response format then format specified in your attribute. If you return another response format the content-type of your response will be overwritten.
```csharp
/// <summary>
MassimoC marked this conversation as resolved.
Show resolved Hide resolved
/// Create a car
/// </summary>
/// <param name="newCarRequest">New car information</param>
/// <remarks>Create a car</remarks>
/// <returns>a Car instance</returns>
[Produces("application/json")]
[Consumes("application/json")]
[HttpPost(Name = Constants.RouteNames.v1.CreateCar)]
[SwaggerResponse((int)HttpStatusCode.OK, "Car created", typeof(CarCreatedDto))]
[SwaggerResponse((int)HttpStatusCode.Conflict, "Car already exists")]
[SwaggerResponse((int)HttpStatusCode.InternalServerError, "API is not available")]
public async Task<IActionResult> CreateCar([FromBody] NewCarRequest newCarRequest)
```

### Error response codes
By default error response codes in ASP.NET Core will use the application/xml or application/json content types. These return types will work well with the above mentioned way of working: if you remove the xml from the supported formats, your method will return a json content type instead. However, using a custom format for your response code (e.g. application/problem+json) will conflict with the use of the [Produces] attribute: the [Produces] attribute will overwrite the content type from you response and set it to application/json.


18 changes: 16 additions & 2 deletions maturity-level-two/src/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
using Codit.LevelTwo.DB;
using System.Linq;
MassimoC marked this conversation as resolved.
Show resolved Hide resolved
using Codit.LevelTwo.DB;
using Codit.LevelTwo.Entities;
using Codit.LevelTwo.Extensions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

Expand All @@ -28,6 +31,17 @@ public void ConfigureServices(IServiceCollection services)
services.ConfigureOpenApiGeneration();
services.ConfigureRouting();
services.ConfigureInvalidStateHandling();
services.AddMvc(options =>
MassimoC marked this conversation as resolved.
Show resolved Hide resolved
{
var jsonInputFormatters = options.InputFormatters.OfType<JsonInputFormatter>();
var jsonInputFormatter = jsonInputFormatters.First();
options.InputFormatters.Clear();
options.InputFormatters.Add(jsonInputFormatter);
var jsonOutputFormatters = options.OutputFormatters.OfType<JsonOutputFormatter>();
var jsonOutputFormatter = jsonOutputFormatters.First();
options.OutputFormatters.Clear();
options.OutputFormatters.Add(jsonOutputFormatter);
});
}

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
Expand All @@ -44,7 +58,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, CoditoCo

// Seed DB
coditoContext.DataSeed();

// Configure API
app.UseHttpsRedirection();
app.UseExceptionHandlerWithProblemJson();
Expand Down