From 9514baa5f3f7fcfdb7ba4d3b75811752f9c23419 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Tue, 8 Oct 2019 17:07:38 +0200 Subject: [PATCH 01/26] Update startup --- maturity-level-two/src/Startup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maturity-level-two/src/Startup.cs b/maturity-level-two/src/Startup.cs index b279d45..c17fe9d 100644 --- a/maturity-level-two/src/Startup.cs +++ b/maturity-level-two/src/Startup.cs @@ -44,7 +44,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, CoditoCo // Seed DB coditoContext.DataSeed(); - + // Configure API app.UseHttpsRedirection(); app.UseExceptionHandlerWithProblemJson(); From 7ce78c7539542f07dd62ae2bb7ca1f8144255577 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 15:45:30 +0200 Subject: [PATCH 02/26] Update formatter usage --- maturity-level-two/README.md | 46 +++++++++++++++++++++++++++++++ maturity-level-two/src/Startup.cs | 16 ++++++++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 0074dff..6555d30 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -11,3 +11,49 @@ 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 + By default ASP.NET Core web applications will expect JSON requests and send JSON responses. Aside from this approach, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. + When no specific compatibility requirements are set it is recommended to use the JSON format if possible (this might not be possible for e.g. file downloads). Herefore content negotiation may not be required, since every request and every response should be in a proper JSON format. If every api call of your api uses the json format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: + ```csharp +services.AddMvc(options => +{ + var jsonInputFormatters = options.InputFormatters.OfType(); + var jsonInputFormatter = jsonInputFormatters.First(); + options.InputFormatters.Clear(); + options.InputFormatters.Add(jsonInputFormatter); + var jsonOutputFormatters = options.OutputFormatters.OfType(); + 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 + ```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 action too. + +You can restrict the request and respnse formats for one specific api call 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 the specified one. If you return another response format the content-type of your response will be overwritten. +```csharp +/// +/// Get car by Id +/// +/// car identifier +/// Get a car by Id +/// a Car instance +[HttpPost("{id}", Name = Constants.RouteNames.v1.GetCar)] +[Produces("application/json")] +[Consumes("application/json")] +[SwaggerResponse((int)HttpStatusCode.OK, "Car created", typeof(CarCreatedDto))] +[SwaggerResponse((int)HttpStatusCode.NotFound, "Car id not found")] +[SwaggerResponse((int)HttpStatusCode.InternalServerError, "API is not available")] +public async Task CreateCar(int id) +``` + +## Error response codes +By default error response codes in ASP.NET Core will use the application/xml or application/json content types but it prefers xml at first). These return types will by default 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. + + diff --git a/maturity-level-two/src/Startup.cs b/maturity-level-two/src/Startup.cs index c17fe9d..7ef91c8 100644 --- a/maturity-level-two/src/Startup.cs +++ b/maturity-level-two/src/Startup.cs @@ -1,8 +1,11 @@ -using Codit.LevelTwo.DB; +using System.Linq; +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; @@ -28,6 +31,17 @@ public void ConfigureServices(IServiceCollection services) services.ConfigureOpenApiGeneration(); services.ConfigureRouting(); services.ConfigureInvalidStateHandling(); + services.AddMvc(options => + { + var jsonInputFormatters = options.InputFormatters.OfType(); + var jsonInputFormatter = jsonInputFormatters.First(); + options.InputFormatters.Clear(); + options.InputFormatters.Add(jsonInputFormatter); + var jsonOutputFormatters = options.OutputFormatters.OfType(); + 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. From 927802c9151aa7f90df9ed2932efd1e56342cd20 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 15:52:41 +0200 Subject: [PATCH 03/26] Fix typo --- maturity-level-two/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 6555d30..e75f919 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -13,7 +13,7 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation - By default ASP.NET Core web applications will expect JSON requests and send JSON responses. Aside from this approach, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. + By default ASP.NET Core web applications will expect JSON requests and send JSON responses. Besides from this approach, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. When no specific compatibility requirements are set it is recommended to use the JSON format if possible (this might not be possible for e.g. file downloads). Herefore content negotiation may not be required, since every request and every response should be in a proper JSON format. If every api call of your api uses the json format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: ```csharp services.AddMvc(options => From 6814c3cf36daaef818c28d58771148fde5e1325c Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 15:57:36 +0200 Subject: [PATCH 04/26] rename --- maturity-level-two/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index e75f919..14282b6 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -13,7 +13,7 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation - By default ASP.NET Core web applications will expect JSON requests and send JSON responses. Besides from this approach, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. + By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. When no specific compatibility requirements are set it is recommended to use the JSON format if possible (this might not be possible for e.g. file downloads). Herefore content negotiation may not be required, since every request and every response should be in a proper JSON format. If every api call of your api uses the json format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: ```csharp services.AddMvc(options => From bbaa4ed221d630108a74f577d97e57fd3433bb32 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 16:04:34 +0200 Subject: [PATCH 05/26] Restructure --- maturity-level-two/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 14282b6..fc115c3 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -13,8 +13,8 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation - By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. - When no specific compatibility requirements are set it is recommended to use the JSON format if possible (this might not be possible for e.g. file downloads). Herefore content negotiation may not be required, since every request and every response should be in a proper JSON format. If every api call of your api uses the json format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: + When no specific compatibility requirements are set concerning the rest request and response format, it is recommended to use the JSON format (application/json) where possible. But in some situations a client might require the data to be formatted in a certain format in order to be able to interpret the data correctly. Herefore content negotiation may not be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. + When every request and every response should be in a proper JSON format. If every api call of your api uses the json format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: ```csharp services.AddMvc(options => { From 68b3900c7f262802b958ad860e1564f54688dd70 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 16:08:11 +0200 Subject: [PATCH 06/26] update structure --- maturity-level-two/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index fc115c3..31a6d13 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -13,7 +13,7 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation - When no specific compatibility requirements are set concerning the rest request and response format, it is recommended to use the JSON format (application/json) where possible. But in some situations a client might require the data to be formatted in a certain format in order to be able to interpret the data correctly. Herefore content negotiation may not be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. + When no specific compatibility requirements the rest request and response formatare 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 be able to interpret the data correctly. Herefore content negotiation may not be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. When every request and every response should be in a proper JSON format. If every api call of your api uses the json format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: ```csharp services.AddMvc(options => From 9c3c5c838ac18b0f88c7e8a6c48162bbac609b7a Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 16:10:25 +0200 Subject: [PATCH 07/26] Fix typo --- maturity-level-two/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 31a6d13..6164ce1 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -13,7 +13,7 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation - When no specific compatibility requirements the rest request and response formatare 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 be able to interpret the data correctly. Herefore content negotiation may not be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. + When no specific compatibility requirements regarding the rest request and response formatare 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 be able to interpret the data correctly. Herefore content negotiation may not be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. When every request and every response should be in a proper JSON format. If every api call of your api uses the json format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: ```csharp services.AddMvc(options => From 5682ea1ad2dbaa0fa0621f6e7347877b5fe90c4f Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 16:10:48 +0200 Subject: [PATCH 08/26] Fix typo --- maturity-level-two/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 6164ce1..4f58011 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -13,7 +13,7 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation - When no specific compatibility requirements regarding the rest request and response formatare 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 be able to interpret the data correctly. Herefore content negotiation may not be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. + 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 be able to interpret the data correctly. Herefore content negotiation may not be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. When every request and every response should be in a proper JSON format. If every api call of your api uses the json format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: ```csharp services.AddMvc(options => From 8b7c9b2fd4baf5bb782a3e05fe3822703e4020fb Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 16:15:33 +0200 Subject: [PATCH 09/26] typos --- maturity-level-two/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 4f58011..3a0e8cd 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -13,7 +13,7 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation - 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 be able to interpret the data correctly. Herefore content negotiation may not be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. + 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. Herefore content negotiation may be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. When every request and every response should be in a proper JSON format. If every api call of your api uses the json format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: ```csharp services.AddMvc(options => From 5960ab177d42bf940cb6465ab4f976f121c8906e Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 16:17:13 +0200 Subject: [PATCH 10/26] typos --- maturity-level-two/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 3a0e8cd..8b10cd2 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -14,7 +14,8 @@ You should: ## Content negotiation 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. Herefore content negotiation may be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. - When every request and every response should be in a proper JSON format. If every api call of your api uses the json format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: + + When you would make sure your api only uses the JSON format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: ```csharp services.AddMvc(options => { From 2b4433a3ddaa2b909ad5b1db04f62ce68c983891 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 16:18:23 +0200 Subject: [PATCH 11/26] Structure readme --- maturity-level-two/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 8b10cd2..a49164a 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -15,7 +15,7 @@ You should: ## Content negotiation 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. Herefore content negotiation may be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. - When you would make sure your api only uses the JSON format, you can specify this in the startup of your ASP.NET Core project. Make sure refuse all non-JSON 'Accept' headers, including the plain text headers (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 below: + When you would 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 (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 below of the changes you have to do to the startup below: ```csharp services.AddMvc(options => { From 3b499e7faec97702cdd6b479b1e14e8580b9cac4 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 16:26:36 +0200 Subject: [PATCH 12/26] Updates in readme --- maturity-level-two/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index a49164a..43dce57 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -13,9 +13,9 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation - 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. Herefore content negotiation may be required. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some Special case formatters, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. + 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. Herefore content negotiation may be required, in which the client specifies in which format a request may be send and in which response formats the client can accept. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. 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 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 (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 below of the changes you have to do to the startup below: + When you would 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 => { @@ -29,15 +29,15 @@ services.AddMvc(options => 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 +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. ```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 action too. +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 restrict the request and respnse formats for one specific api call 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 the specified one. If you return another response format the content-type of your response will be overwritten. +You can 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 /// /// Get car by Id @@ -55,6 +55,6 @@ public async Task CreateCar(int id) ``` ## Error response codes -By default error response codes in ASP.NET Core will use the application/xml or application/json content types but it prefers xml at first). These return types will by default 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. +By default error response codes in ASP.NET Core will use the application/xml or application/json content types. These return types will by default 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. From 15fb8ded4efc132ea6be99d64d9604922c67514f Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 16:27:50 +0200 Subject: [PATCH 13/26] Update readme --- maturity-level-two/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 43dce57..91bfd60 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -37,7 +37,7 @@ services.AddMvc() ``` 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 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. +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 /// /// Get car by Id From 7e2b4a97593bffef502c152d661a6756d37f92f2 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 16:52:03 +0200 Subject: [PATCH 14/26] Review PR comments --- maturity-level-two/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 91bfd60..4f52545 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -40,10 +40,10 @@ Be aware that the formatters you specify in the above section are all the format 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 /// -/// Get car by Id +/// Create a car /// /// car identifier -/// Get a car by Id +/// Create a car /// a Car instance [HttpPost("{id}", Name = Constants.RouteNames.v1.GetCar)] [Produces("application/json")] @@ -51,10 +51,10 @@ You can (not should) further restrict the request and respnse formats for one sp [SwaggerResponse((int)HttpStatusCode.OK, "Car created", typeof(CarCreatedDto))] [SwaggerResponse((int)HttpStatusCode.NotFound, "Car id not found")] [SwaggerResponse((int)HttpStatusCode.InternalServerError, "API is not available")] -public async Task CreateCar(int id) +public async Task 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 by default 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. +### 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. From b7d852653a8e408d13d9fc433b61dec5341d633a Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 17:01:28 +0200 Subject: [PATCH 15/26] Add suggestions to improve readability --- maturity-level-two/README.md | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 4f52545..8538cf7 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -13,9 +13,14 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation - 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. Herefore content negotiation may be required, in which the client specifies in which format a request may be send and in which response formats the client can accept. By default ASP.NET Core web applications will expect JSON requests and send JSON responses. In addition to this, ASP.NET Core also uses some additional formatters for special cases, such as the TextOutputFormatter and the HttpNoContentOutputFormatter. + 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 +- `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 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: + 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 => { From 969eb210b10966136cd03426c82b71994d8fa53e Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Wed, 9 Oct 2019 17:12:58 +0200 Subject: [PATCH 16/26] Update createCar example --- maturity-level-two/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 8538cf7..1963561 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -47,14 +47,14 @@ You can (not should) further restrict the request and respnse formats for one sp /// /// Create a car /// -/// car identifier +/// New car information /// Create a car /// a Car instance -[HttpPost("{id}", Name = Constants.RouteNames.v1.GetCar)] [Produces("application/json")] [Consumes("application/json")] +[HttpPost(Name = Constants.RouteNames.v1.CreateCar)] [SwaggerResponse((int)HttpStatusCode.OK, "Car created", typeof(CarCreatedDto))] -[SwaggerResponse((int)HttpStatusCode.NotFound, "Car id not found")] +[SwaggerResponse((int)HttpStatusCode.Conflict, "Car already exists")] [SwaggerResponse((int)HttpStatusCode.InternalServerError, "API is not available")] public async Task CreateCar([FromBody] NewCarRequest newCarRequest) ``` From cb0fcee6ba69d8f7fd1e8cde4543e2f08f45cae8 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Thu, 10 Oct 2019 11:13:55 +0200 Subject: [PATCH 17/26] Update - add CompaitbilityVersion --- maturity-level-two/README.md | 4 ++-- maturity-level-two/src/Startup.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 1963561..9b64adc 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -32,9 +32,9 @@ services.AddMvc(options => var jsonOutputFormatter = jsonOutputFormatters.First(); options.OutputFormatters.Clear(); options.OutputFormatters.Add(jsonOutputFormatter); -}); +}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2); ``` -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. +In here we have to add the 'SetCompatibilityVersion'as well to make sure the supported formats are documented correctly in e.g. the swagger. 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. ```csharp services.AddMvc() .AddNewtonsoftJson() diff --git a/maturity-level-two/src/Startup.cs b/maturity-level-two/src/Startup.cs index 7ef91c8..150b5bb 100644 --- a/maturity-level-two/src/Startup.cs +++ b/maturity-level-two/src/Startup.cs @@ -41,7 +41,7 @@ public void ConfigureServices(IServiceCollection services) var jsonOutputFormatter = jsonOutputFormatters.First(); options.OutputFormatters.Clear(); options.OutputFormatters.Add(jsonOutputFormatter); - }); + }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. From 9bdd199486cc24fff3dc8f7b73cf8de594b0a25b Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Thu, 10 Oct 2019 14:24:28 +0200 Subject: [PATCH 18/26] add summary + move long text to separate md --- maturity-level-two/README.md | 65 +++++-------------- .../docs/content-negotiation.md | 52 +++++++++++++++ 2 files changed, 68 insertions(+), 49 deletions(-) create mode 100644 maturity-level-two/docs/content-negotiation.md diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 4afcd67..9e9337b 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -1,6 +1,7 @@ # Practical API Guidelines - Should have 1. [Validating OpenAPI Specifications](docs/validating-open-api-specs.md) +1. [Content Negotiation](docs/content-negotiation.md) 1. [APISecurity](docs/api-security.md) @@ -15,56 +16,22 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation - 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: +With content negotiation a consumer specifies in which format he/she will communicate (send and receive data) with the server. In here the you, the API consumer, can specify what format you would like to receive from the server and send to to server: - `Content-Type` - Specify the format of the request -- `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(); - var jsonInputFormatter = jsonInputFormatters.First(); - options.InputFormatters.Clear(); - options.InputFormatters.Add(jsonInputFormatter); - var jsonOutputFormatters = options.OutputFormatters.OfType(); - var jsonOutputFormatter = jsonOutputFormatters.First(); - options.OutputFormatters.Clear(); - options.OutputFormatters.Add(jsonOutputFormatter); -}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2); -``` -In here we have to add the 'SetCompatibilityVersion'as well to make sure the supported formats are documented correctly in e.g. the swagger. 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. - ```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 -/// -/// Create a car -/// -/// New car information -/// Create a car -/// a Car instance -[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 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. - - +- `Accept` - Specify the requested format of the response. Default format will be used when not + +When you send a `Content-Type` the server doesn't understand, the server should return an HTTP 415: Unsupported Media Type. If the server cannot respond to your request in an `Accept` format, it will return an HTTP 406: Not Acceptable + +When adding Content-Negotiation to your project you should: +* Think whether content negotiation is really necessary. For most of the cases you only need JSON and thus no content negotiation. +* Remove input and output formatters when multi-format (JSON, XML, CSV, ...) is not necessary. +* Carefully evaluate whether you should use the [Produces] and [Consumes] attributes to further restrict the request and response formats for one specific acion or controller. + * [Produces] and [Consumes] are not meant to document the supported media types. + * It is strongly advised to not use these attributes if you are supporting more than one media type (e.g. application/json and application/problem+json) + +Notes: +* `Content-Type` is not needed for HTTP GET requests since a GET request has no request body +* If you want to explicetly specify what content types are produced/consumed in your swagger file, we advise to use a custom attribute (to be checked whether something). ## API Security API security is an essential part when designing the API. All different levels of security are discussed within the API-Security document ([user guide](docs/api-security.md)). diff --git a/maturity-level-two/docs/content-negotiation.md b/maturity-level-two/docs/content-negotiation.md new file mode 100644 index 0000000..39ac72b --- /dev/null +++ b/maturity-level-two/docs/content-negotiation.md @@ -0,0 +1,52 @@ +# Content Negotiation + + 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 +- `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(); + var jsonInputFormatter = jsonInputFormatters.First(); + options.InputFormatters.Clear(); + options.InputFormatters.Add(jsonInputFormatter); + var jsonOutputFormatters = options.OutputFormatters.OfType(); + var jsonOutputFormatter = jsonOutputFormatters.First(); + options.OutputFormatters.Clear(); + options.OutputFormatters.Add(jsonOutputFormatter); +}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2); +``` +In here we have to add the 'SetCompatibilityVersion'as well to make sure the supported formats are documented correctly in e.g. the swagger. 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. + ```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 +/// +/// Create a car +/// +/// New car information +/// Create a car +/// a Car instance +[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 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. + + From 8dca836e6345352137ad82afc71c8629c9d93596 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Thu, 10 Oct 2019 14:28:39 +0200 Subject: [PATCH 19/26] Undo code changes for readme --- maturity-level-two/src/Startup.cs | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/maturity-level-two/src/Startup.cs b/maturity-level-two/src/Startup.cs index 150b5bb..e59c432 100644 --- a/maturity-level-two/src/Startup.cs +++ b/maturity-level-two/src/Startup.cs @@ -31,17 +31,6 @@ public void ConfigureServices(IServiceCollection services) services.ConfigureOpenApiGeneration(); services.ConfigureRouting(); services.ConfigureInvalidStateHandling(); - services.AddMvc(options => - { - var jsonInputFormatters = options.InputFormatters.OfType(); - var jsonInputFormatter = jsonInputFormatters.First(); - options.InputFormatters.Clear(); - options.InputFormatters.Add(jsonInputFormatter); - var jsonOutputFormatters = options.OutputFormatters.OfType(); - var jsonOutputFormatter = jsonOutputFormatters.First(); - options.OutputFormatters.Clear(); - options.OutputFormatters.Add(jsonOutputFormatter); - }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -58,7 +47,7 @@ public void Configure(IApplicationBuilder app, IHostingEnvironment env, CoditoCo // Seed DB coditoContext.DataSeed(); - + // Configure API app.UseHttpsRedirection(); app.UseExceptionHandlerWithProblemJson(); From 23528cea44ab980fa53beb6042debf2a3c7be5e0 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Thu, 10 Oct 2019 14:56:23 +0200 Subject: [PATCH 20/26] Update PR review --- maturity-level-two/README.md | 12 ++++++------ maturity-level-two/docs/content-negotiation.md | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 9e9337b..1d6428b 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -16,22 +16,22 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation -With content negotiation a consumer specifies in which format he/she will communicate (send and receive data) with the server. In here the you, the API consumer, can specify what format you would like to receive from the server and send to to server: -- `Content-Type` - Specify the format of the request +With content negotiation a consumer specifies in which format he/she will communicate (send and receive data) with the server. In here you, the API consumer, can specify what content type you would like to receive from and send to the server: +- `Content-Type` - Specify the format of the payload - `Accept` - Specify the requested format of the response. Default format will be used when not -When you send a `Content-Type` the server doesn't understand, the server should return an HTTP 415: Unsupported Media Type. If the server cannot respond to your request in an `Accept` format, it will return an HTTP 406: Not Acceptable +When you send a `Content-Type` the server doesn't understand, the server should return an HTTP 415: Unsupported Media Type. If the server cannot respond to your request in an `Accept` format, it will return an HTTP 406: Not Acceptable. When adding Content-Negotiation to your project you should: * Think whether content negotiation is really necessary. For most of the cases you only need JSON and thus no content negotiation. * Remove input and output formatters when multi-format (JSON, XML, CSV, ...) is not necessary. -* Carefully evaluate whether you should use the [Produces] and [Consumes] attributes to further restrict the request and response formats for one specific acion or controller. +* Carefully evaluate whether you should use the [Produces] and [Consumes] attributes to further restrict the supported request and response media types for one specific acion or controller. * [Produces] and [Consumes] are not meant to document the supported media types. * It is strongly advised to not use these attributes if you are supporting more than one media type (e.g. application/json and application/problem+json) Notes: -* `Content-Type` is not needed for HTTP GET requests since a GET request has no request body -* If you want to explicetly specify what content types are produced/consumed in your swagger file, we advise to use a custom attribute (to be checked whether something). +* `Content-Type` is not needed for HTTP GET requests since a GET request has no request body. +* If you want to explicitly specify what content types are produced/consumed in your swagger file, we advise to use a custom attribute (to be checked whether something). ## API Security API security is an essential part when designing the API. All different levels of security are discussed within the API-Security document ([user guide](docs/api-security.md)). diff --git a/maturity-level-two/docs/content-negotiation.md b/maturity-level-two/docs/content-negotiation.md index 39ac72b..5d21c5e 100644 --- a/maturity-level-two/docs/content-negotiation.md +++ b/maturity-level-two/docs/content-negotiation.md @@ -1,8 +1,8 @@ # Content Negotiation - 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. + When no specific compatibility requirements regarding the rest request and response formats are set, it is recommended to use the JSON format (application/json). However 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 send requests & receive 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 +- `Content-Type` - Specify the format of the payload. - `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. @@ -27,7 +27,7 @@ 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. +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, or a 415, Unsupported. 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 @@ -47,6 +47,6 @@ public async Task CreateCar([FromBody] NewCarRequest newCarReques ``` ### 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. +By default error response codes in ASP.NET Core will use the application/problem+xml or application/problem+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. From 4abfa897ba63e3f998a250dc86a95cd37b3d8754 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Thu, 10 Oct 2019 15:00:24 +0200 Subject: [PATCH 21/26] Fix sentence in readme --- maturity-level-two/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 1d6428b..6c2c293 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -16,7 +16,7 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation -With content negotiation a consumer specifies in which format he/she will communicate (send and receive data) with the server. In here you, the API consumer, can specify what content type you would like to receive from and send to the server: +With content negotiation a consumer specifies in which format he/she will communicate (send and receive data) with the server. Here you, the API consumer, can specify what content type you would like to receive from and send to the server. You can do this by using the following headers in your request: - `Content-Type` - Specify the format of the payload - `Accept` - Specify the requested format of the response. Default format will be used when not From 2df180221d01a7cbf2f993311f7a70f7e98aee8f Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Thu, 10 Oct 2019 15:06:33 +0200 Subject: [PATCH 22/26] Review readme contents --- maturity-level-two/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 6c2c293..dfde9c2 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -16,14 +16,14 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation -With content negotiation a consumer specifies in which format he/she will communicate (send and receive data) with the server. Here you, the API consumer, can specify what content type you would like to receive from and send to the server. You can do this by using the following headers in your request: -- `Content-Type` - Specify the format of the payload -- `Accept` - Specify the requested format of the response. Default format will be used when not +With content negotiation a consumer specifies in which format he/she will communicate (send and receive data) with the server. You can do this by using the following headers in your request: +- `Content-Type` - Specify the format of the payload you send to the server. +- `Accept` - Specify the requested format of the server response. A default format will be used when this header is not specified. -When you send a `Content-Type` the server doesn't understand, the server should return an HTTP 415: Unsupported Media Type. If the server cannot respond to your request in an `Accept` format, it will return an HTTP 406: Not Acceptable. +When you send a `Content-Type` the server doesn't understand, the server should return an HTTP 415: Unsupported Media Type. If the server cannot respond to your request in a format specified in your `Accept` header, it will return an HTTP 406: Not Acceptable. When adding Content-Negotiation to your project you should: -* Think whether content negotiation is really necessary. For most of the cases you only need JSON and thus no content negotiation. +* Think whether content negotiation is really necessary. For most of the cases you only need JSON and thus no content negotiation is needed. * Remove input and output formatters when multi-format (JSON, XML, CSV, ...) is not necessary. * Carefully evaluate whether you should use the [Produces] and [Consumes] attributes to further restrict the supported request and response media types for one specific acion or controller. * [Produces] and [Consumes] are not meant to document the supported media types. @@ -31,7 +31,7 @@ When adding Content-Negotiation to your project you should: Notes: * `Content-Type` is not needed for HTTP GET requests since a GET request has no request body. -* If you want to explicitly specify what content types are produced/consumed in your swagger file, we advise to use a custom attribute (to be checked whether something). +* If you want to explicitly specify which content types are produced/consumed in your swagger file, we advise to use a custom attribute (to be checked whether something). ## API Security API security is an essential part when designing the API. All different levels of security are discussed within the API-Security document ([user guide](docs/api-security.md)). From fb67085b08a86578f323d5e4a43850627f218e4c Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Thu, 10 Oct 2019 15:44:59 +0200 Subject: [PATCH 23/26] Remove startup changes + update readme --- maturity-level-two/README.md | 2 +- maturity-level-two/docs/content-negotiation.md | 14 +++++++------- maturity-level-two/src/Startup.cs | 3 +-- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index dfde9c2..d0507d5 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -18,7 +18,7 @@ You should: ## Content negotiation With content negotiation a consumer specifies in which format he/she will communicate (send and receive data) with the server. You can do this by using the following headers in your request: - `Content-Type` - Specify the format of the payload you send to the server. -- `Accept` - Specify the requested format of the server response. A default format will be used when this header is not specified. +- `Accept` - Specify your preferred payload format(s) of the server response. A default format will be used when this header is not specified. When you send a `Content-Type` the server doesn't understand, the server should return an HTTP 415: Unsupported Media Type. If the server cannot respond to your request in a format specified in your `Accept` header, it will return an HTTP 406: Not Acceptable. diff --git a/maturity-level-two/docs/content-negotiation.md b/maturity-level-two/docs/content-negotiation.md index 5d21c5e..a65079f 100644 --- a/maturity-level-two/docs/content-negotiation.md +++ b/maturity-level-two/docs/content-negotiation.md @@ -1,13 +1,13 @@ # Content Negotiation - When no specific compatibility requirements regarding the rest request and response formats are set, it is recommended to use the JSON format (application/json). However 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 send requests & receive responses. For REST API's, the most common formats are JSON and XML. + When no specific compatibility requirements regarding the rest request and response formats are set, it is recommended to use the JSON format (application/json). However in some situations a client might be restricted in the payload formats it can send to and receive from the server. With content negotiation we determine what format we'd like to use requests & response payloads. 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 payload. -- `Accept` - Specify the requested format of the response. Default format will be used when not specified. +- `Content-Type` - Specify the format of the payload you send to the server. +- `Accept` - Specify your preferred payload format(s) of the server response. A default format will be used when this header is 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: + 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 in the startup example below: ```csharp services.AddMvc(options => { @@ -27,7 +27,7 @@ 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, or a 415, Unsupported. 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. +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 an HTTP 406, Not Acceptable, or an HTTP 415, Unsupported. This means that if you have one action on which the user can request a custom/other response format, you 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 @@ -46,7 +46,7 @@ You can (not should) further restrict the request and respnse formats for one sp public async Task CreateCar([FromBody] NewCarRequest newCarRequest) ``` -### Error response codes -By default error response codes in ASP.NET Core will use the application/problem+xml or application/problem+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. +## Error response codes +By default error response codes in ASP.NET Core will use the application/problem+xml or application/problem+json content types. These return types will usually 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 the use of the content type application/problem+json will conflict with the use of the [Produces] attribute: the [Produces] attribute will overwrite the content type from your error response and set it to application/json. diff --git a/maturity-level-two/src/Startup.cs b/maturity-level-two/src/Startup.cs index e59c432..6a271b0 100644 --- a/maturity-level-two/src/Startup.cs +++ b/maturity-level-two/src/Startup.cs @@ -1,5 +1,4 @@ -using System.Linq; -using Codit.LevelTwo.DB; +using Codit.LevelTwo.DB; using Codit.LevelTwo.Entities; using Codit.LevelTwo.Extensions; using Microsoft.AspNetCore.Builder; From 04afee2f0ecaf5b80c0c9a291d15cb8894a25d43 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Thu, 10 Oct 2019 15:52:38 +0200 Subject: [PATCH 24/26] Add links to more information --- maturity-level-two/README.md | 4 ++-- maturity-level-two/docs/content-negotiation.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index d0507d5..7d29985 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -16,9 +16,9 @@ You should: - Unit test Open API validation to automatically detect breaking changes ## Content negotiation -With content negotiation a consumer specifies in which format he/she will communicate (send and receive data) with the server. You can do this by using the following headers in your request: +With [content negotiation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation) a consumer specifies in which format he/she will communicate (send and receive data) with the server. You can do this by using the following headers in your request: - `Content-Type` - Specify the format of the payload you send to the server. -- `Accept` - Specify your preferred payload format(s) of the server response. A default format will be used when this header is not specified. +- `Accept` - Specify your preferred payload format(s) of the server response. A default format will be used when this header is not specified. Also other accept headers can be used, you can find more info about this [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation). When you send a `Content-Type` the server doesn't understand, the server should return an HTTP 415: Unsupported Media Type. If the server cannot respond to your request in a format specified in your `Accept` header, it will return an HTTP 406: Not Acceptable. diff --git a/maturity-level-two/docs/content-negotiation.md b/maturity-level-two/docs/content-negotiation.md index a65079f..bd466e9 100644 --- a/maturity-level-two/docs/content-negotiation.md +++ b/maturity-level-two/docs/content-negotiation.md @@ -1,6 +1,6 @@ # Content Negotiation - When no specific compatibility requirements regarding the rest request and response formats are set, it is recommended to use the JSON format (application/json). However in some situations a client might be restricted in the payload formats it can send to and receive from the server. With content negotiation we determine what format we'd like to use requests & response payloads. For REST API's, the most common formats are JSON and XML. + When no specific compatibility requirements regarding the rest request and response formats are set, it is recommended to use the JSON format (application/json). However in some situations a client might be restricted in the payload formats it can send to and receive from the server. With [content negotiation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Content_negotiation) we determine what format we'd like to use requests & response payloads. 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 payload you send to the server. - `Accept` - Specify your preferred payload format(s) of the server response. A default format will be used when this header is not specified. From 3cb96908580029f4a7b78509108b251478f3b4df Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Thu, 10 Oct 2019 16:45:35 +0200 Subject: [PATCH 25/26] Review PR comments --- maturity-level-two/README.md | 4 ++-- .../docs/content-negotiation.md | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/maturity-level-two/README.md b/maturity-level-two/README.md index 7d29985..1dfed86 100644 --- a/maturity-level-two/README.md +++ b/maturity-level-two/README.md @@ -26,8 +26,8 @@ When adding Content-Negotiation to your project you should: * Think whether content negotiation is really necessary. For most of the cases you only need JSON and thus no content negotiation is needed. * Remove input and output formatters when multi-format (JSON, XML, CSV, ...) is not necessary. * Carefully evaluate whether you should use the [Produces] and [Consumes] attributes to further restrict the supported request and response media types for one specific acion or controller. - * [Produces] and [Consumes] are not meant to document the supported media types. - * It is strongly advised to not use these attributes if you are supporting more than one media type (e.g. application/json and application/problem+json) + * [Produces] and [Consumes] are not meant to document the supported media types for all actions. + * It is strongly advised to not use these attributes if you are supporting more than one media type (e.g. application/json and application/problem+json). Notes: * `Content-Type` is not needed for HTTP GET requests since a GET request has no request body. diff --git a/maturity-level-two/docs/content-negotiation.md b/maturity-level-two/docs/content-negotiation.md index bd466e9..b1bf9a7 100644 --- a/maturity-level-two/docs/content-negotiation.md +++ b/maturity-level-two/docs/content-negotiation.md @@ -27,7 +27,7 @@ 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 an HTTP 406, Not Acceptable, or an HTTP 415, Unsupported. This means that if you have one action on which the user can request a custom/other response format, you have to add a formatter for this type as well - and by default the other actions will support this format too. +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 an HTTP 406, Not Acceptable, or an HTTP 415, Unsupported. 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 @@ -46,6 +46,24 @@ You can (not should) further restrict the request and respnse formats for one sp public async Task CreateCar([FromBody] NewCarRequest newCarRequest) ``` +In case you have an action which returns media type(s) only this action will return, you can use the [Produces] and [Consumes] keywords too. But be aware that in this case your api might not know how it should serialize the response, so you might have to take care of this yourself. In order to do so you can return a ContentResult (e.g. FileContentResult or ContentResult in the Microsoft.AspNetCore.Mvc namespace). An example is given below: +```csharp + /// +/// Get all cars +/// +/// /// Filter a specific body Type (optional) +/// Get all cars +/// List of cars +[HttpGet(Name = Constants.RouteNames.v1.GetCars)] +[SwaggerResponse((int)HttpStatusCode.OK, "List of Cars")] +[SwaggerResponse((int)HttpStatusCode.InternalServerError, "API is not available")] +// [Produces("application/json", "application/problem+json")] +public async Task GetCars([FromQuery] CarBodyType? bodyType) +{ + return File(GetCars(), "application/pdf", "carlist.pdf"); +} +``` + ## Error response codes By default error response codes in ASP.NET Core will use the application/problem+xml or application/problem+json content types. These return types will usually 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 the use of the content type application/problem+json will conflict with the use of the [Produces] attribute: the [Produces] attribute will overwrite the content type from your error response and set it to application/json. From d2af233b13db523859cee85aeea7469480379d58 Mon Sep 17 00:00:00 2001 From: Filip Van Raemdonck Date: Thu, 10 Oct 2019 16:52:02 +0200 Subject: [PATCH 26/26] Undo coding changes --- maturity-level-two/src/Startup.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/maturity-level-two/src/Startup.cs b/maturity-level-two/src/Startup.cs index 6a271b0..b279d45 100644 --- a/maturity-level-two/src/Startup.cs +++ b/maturity-level-two/src/Startup.cs @@ -3,8 +3,6 @@ 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;