Since ASP.NET core 2.2 Microsoft has been using ValidationProblemDetails by default when returning model state validation errors. BadRequest() Status Code 400
Note ValidationProblemDetails inherits from ProblemDetails but adds the "Errors" extension to hold model state validation errors.
The problem begins when the OpenAPI spec document is generated as it currently uses ProblemDetails. As a consequence when the C# client gets generated the ProblemDetails class below is created. Note there is no Errors extension
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.0.28.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class ProblemDetails
{
[Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Type { get; set; }
[Newtonsoft.Json.JsonProperty("title", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Title { get; set; }
[Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public int? Status { get; set; }
[Newtonsoft.Json.JsonProperty("detail", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Detail { get; set; }
[Newtonsoft.Json.JsonProperty("instance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Instance { get; set; }
[Newtonsoft.Json.JsonProperty("extensions", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Collections.Generic.IDictionary<string, object> Extensions { get; set; }
}
To confirm ValidationProblemDetails results are being returned for 400 BadRequest responses I set the generated clients property ReadResponseAsString to true.
client.ReadResponseAsString = true;
When a Status Code 400 error is generated the APIException Result property looks like the following: Note Extensions is NULL and there is no Errors property.
In that same exception here is the contents of the Response property: Note it contains the errors extension which confirms Microsoft is returning JSON results compatible with ValidationProblemDetails
As a temporary work around I've set NSwag studio to not generate the ProblemDetails class. Instead I have my own ProblemDetails outside of the generated client but in the same namespace. It looks like this with the newly added Errors extension.
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.0.28.0 (Newtonsoft.Json v12.0.0.0)")]
public partial class ProblemDetails
{
[Newtonsoft.Json.JsonProperty("type", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Type { get; set; }
[Newtonsoft.Json.JsonProperty("title", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Title { get; set; }
[Newtonsoft.Json.JsonProperty("status", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public int? Status { get; set; }
[Newtonsoft.Json.JsonProperty("detail", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Detail { get; set; }
[Newtonsoft.Json.JsonProperty("instance", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public string Instance { get; set; }
[Newtonsoft.Json.JsonProperty("extensions", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Collections.Generic.IDictionary<string, object> Extensions { get; set; }
[Newtonsoft.Json.JsonProperty("errors", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public System.Collections.Generic.IDictionary<string, object> Errors { get; set; }
}
I'm curious, where does the ProblemDetails defintion come from when its written to the OpenAPI 3.0 document. Is it coming from APIExplorer?
As far as I can tell, if a more appropriate ProblemDetails (i.e. ValidationProblemDetails) is generated in the spec it will flow through the entire tool chain and generated clients will just work. ValidationProblemDetails would be the obvious choice as its just an extension of ProblemDetails
I've found the answer. I'm just surprised after so many hours of looking I could not find what in retrospect was obvious.
[ProducesResponseType(typeof(ValidationProblemDetails), 400)]
public IActionResult CreateOrder([FromBody]Order order)
So api explorer should report ValidationProblemDetails for 400 instead of ProblemDetails?
I did not realize where ProblemDetails was coming from and I could find no documentation anywhere although I'm sure it exists. It turns out ProducesResponseType uses ProblemDetails by default and it took me a couple of days to finally discover this. By being explicit about using [ProducesResponseType(typeof(ValidationProblemDetails), 400)] it solves my problem. The NSwag client genertor is now using ValidationProblemDetails for Status Code 400 which is what I wanted all along. There is no problem and everything is good which is why I closed the issue.
@dougbu shouldnt asp use ValidationProblemDetails as default for 400?
shouldnt asp use ValidationProblemDetails as default for 400?
@RicoSuter I'm not sure about the reasoning around ValidationProblemDetails and related types. @pranavkm is our expert. @pranavkm 鉂斺潝
I agree ValidationProblemDetails should probably by the default but I wouldn't know what the knock on effects of doing so are. The Microsoft folks obviously would.
There's likely a missing feature in MVC that is causing this to not be reported correctly. I don't think assuming 400 = ValidationProblemDetails is correct because it's trivial for users to customize it. What we'd need is for some way for MVC to report the default error response type for 400s, and for a way for users to customize it globally. However, since we're already talking about atleast 5.0 before we could surface this information via ApiExplorer, you could consider working around this for now.
I just ran into this.
But I don't agree that it's ok to set [ProducesResponseType(typeof(ValidationProblemDetails), 400)] in every single method that has model validation.
Is there any other workaround?
@andreujuanc you can write an operation processor just like this:
that always adds a 400 response if it has model validation
Makes sense! Thanks
Correct, with a globally registered operation processor (register in AddOpenApiDocument()) you can do that... maybe also with an custom ASP.NET Core API Convention?
Most helpful comment
There's likely a missing feature in MVC that is causing this to not be reported correctly. I don't think assuming 400 =
ValidationProblemDetailsis correct because it's trivial for users to customize it. What we'd need is for some way for MVC to report the default error response type for 400s, and for a way for users to customize it globally. However, since we're already talking about atleast 5.0 before we could surface this information via ApiExplorer, you could consider working around this for now.