I'm using ASP.NET Core 3.0 with Swashbuckle 5. I'm trying to write a ISchemaFilter
for ProblemDetails
in ASP.NET Core 3.0. ProblemDetails
is returned for a lot of different status codes e.g. 400, 401, 403, 406, 415, 500 etc. I want to provide a different example of ProblemDetails
depending on the status code. How can I achieve this? Here is some code I've written to get started, just need to fill in the blank:
public class ProblemDetailsSchemaFilter : ISchemaFilter
{
private static readonly OpenApiObject Status400ProblemDetails = new OpenApiObject()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.1"),
["title"] = new OpenApiString("Bad Request"),
["status"] = new OpenApiInteger(StatusCodes.Status400BadRequest),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
["errors"] = new OpenApiObject()
{
["property1"] = new OpenApiArray()
{
new OpenApiString("The property field is required"),
},
},
};
private static readonly OpenApiObject Status401ProblemDetails = new OpenApiObject()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7235#section-3.1"),
["title"] = new OpenApiString("Unauthorized"),
["status"] = new OpenApiInteger(StatusCodes.Status401Unauthorized),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject Status403ProblemDetails = new OpenApiObject()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.3"),
["title"] = new OpenApiString("Forbidden"),
["status"] = new OpenApiInteger(StatusCodes.Status403Forbidden),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject Status404ProblemDetails = new OpenApiObject()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.4"),
["title"] = new OpenApiString("Not Found"),
["status"] = new OpenApiInteger(StatusCodes.Status404NotFound),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject Status406ProblemDetails = new OpenApiObject()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.6"),
["title"] = new OpenApiString("Not Acceptable"),
["status"] = new OpenApiInteger(StatusCodes.Status406NotAcceptable),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject Status409ProblemDetails = new OpenApiObject()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.8"),
["title"] = new OpenApiString("Conflict"),
["status"] = new OpenApiInteger(StatusCodes.Status409Conflict),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject Status415ProblemDetails = new OpenApiObject()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.13"),
["title"] = new OpenApiString("Unsupported Media Type"),
["status"] = new OpenApiInteger(StatusCodes.Status415UnsupportedMediaType),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject Status422ProblemDetails = new OpenApiObject()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc4918#section-11.2"),
["title"] = new OpenApiString("Unprocessable Entity"),
["status"] = new OpenApiInteger(StatusCodes.Status422UnprocessableEntity),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject Status500ProblemDetails = new OpenApiObject()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.6.1"),
["title"] = new OpenApiString("Internal Server Error"),
["status"] = new OpenApiInteger(StatusCodes.Status500InternalServerError),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (context.ApiModel.Type == typeof(ProblemDetails))
{
// TODO: Set the default and example based on the status code.
// schema.Default = ???;
// schema.Example = ???;
}
}
}
I've taken your examples and converted them into an operation filter where you have access to the response.
You can use the following OperationFilter:
using System.Collections.Generic;
using System.Net.Mime;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
public class ProblemDetailsOperationFilter : IOperationFilter
{
private static readonly OpenApiObject _status400ProblemDetails = new()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.1"),
["title"] = new OpenApiString(ReasonPhrases.GetReasonPhrase(StatusCodes.Status400BadRequest)),
["status"] = new OpenApiInteger(StatusCodes.Status400BadRequest),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
["errors"] = new OpenApiObject
{
["property1"] = new OpenApiArray
{
new OpenApiString("The property field is required"),
},
},
};
private static readonly OpenApiObject _status401ProblemDetails = new()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7235#section-3.1"),
["title"] = new OpenApiString(ReasonPhrases.GetReasonPhrase(StatusCodes.Status401Unauthorized)),
["status"] = new OpenApiInteger(StatusCodes.Status401Unauthorized),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject _status403ProblemDetails = new()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.3"),
["title"] = new OpenApiString(ReasonPhrases.GetReasonPhrase(StatusCodes.Status403Forbidden)),
["status"] = new OpenApiInteger(StatusCodes.Status403Forbidden),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject _status404ProblemDetails = new()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.4"),
["title"] = new OpenApiString(ReasonPhrases.GetReasonPhrase(StatusCodes.Status404NotFound)),
["status"] = new OpenApiInteger(StatusCodes.Status404NotFound),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject _status406ProblemDetails = new()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.6"),
["title"] = new OpenApiString(ReasonPhrases.GetReasonPhrase(StatusCodes.Status406NotAcceptable)),
["status"] = new OpenApiInteger(StatusCodes.Status406NotAcceptable),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject _status409ProblemDetails = new()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.8"),
["title"] = new OpenApiString(ReasonPhrases.GetReasonPhrase(StatusCodes.Status409Conflict)),
["status"] = new OpenApiInteger(StatusCodes.Status409Conflict),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject _status415ProblemDetails = new()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.5.13"),
["title"] = new OpenApiString(ReasonPhrases.GetReasonPhrase(StatusCodes.Status415UnsupportedMediaType)),
["status"] = new OpenApiInteger(StatusCodes.Status415UnsupportedMediaType),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject _status422ProblemDetails = new()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc4918#section-11.2"),
["title"] = new OpenApiString(ReasonPhrases.GetReasonPhrase(StatusCodes.Status422UnprocessableEntity)),
["status"] = new OpenApiInteger(StatusCodes.Status422UnprocessableEntity),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
private static readonly OpenApiObject _status500ProblemDetails = new()
{
["type"] = new OpenApiString("https://tools.ietf.org/html/rfc7231#section-6.6.1"),
["title"] = new OpenApiString(ReasonPhrases.GetReasonPhrase(StatusCodes.Status500InternalServerError)),
["status"] = new OpenApiInteger(StatusCodes.Status500InternalServerError),
["traceId"] = new OpenApiString("00-982607166a542147b435be3a847ddd71-fc75498eb9f09d48-00"),
};
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
var problemDetails = new Dictionary<string, IOpenApiAny>
{
{ StatusCodes.Status400BadRequest.ToString(), _status400ProblemDetails },
{ StatusCodes.Status401Unauthorized.ToString(), _status401ProblemDetails },
{ StatusCodes.Status403Forbidden.ToString(), _status403ProblemDetails },
{ StatusCodes.Status404NotFound.ToString(), _status404ProblemDetails },
{ StatusCodes.Status406NotAcceptable.ToString(), _status406ProblemDetails },
{ StatusCodes.Status409Conflict.ToString(), _status409ProblemDetails },
{ StatusCodes.Status415UnsupportedMediaType.ToString(), _status415ProblemDetails },
{ StatusCodes.Status422UnprocessableEntity.ToString(), _status422ProblemDetails },
{ StatusCodes.Status500InternalServerError.ToString(), _status500ProblemDetails },
};
foreach (var operationResponse in operation.Responses)
{
if (problemDetails.TryGetValue(operationResponse.Key, out var problemDetail))
operationResponse.Value.Content[MediaTypeNames.Application.Json].Example = problemDetail;
}
}
}
Register it with
services.AddSwaggerGen(x =>
{
...
x.OperationFilter<ProblemDetailsOperationFilter>();
...
});