Search code examples
asp.net-core-webapixml-serializationswagger-ui

Unclear why this Swagger UI fails with 'FormatterNotFoundException' exception only for this specific property


How to make the example working in Swagger while still having [Produces("application/json", "application/xml")] definition in controller method.

In .Net Core 6 API, have set up Swagger along with Swashbuckle.AspNetCore.Filters and could view the request and response Examples in Swagger UI page. In order to support XML format, have set up serializer in program.cs

builder.Services.AddControllers(options =>
{
    options.InputFormatters.Add(new XmlSerializerInputFormatter(options));
    options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
});

This worked great until the swagger.json generation failed with below error
Swashbuckle.AspNetCore.Filters.MvcOutputFormatter+FormatterNotFoundException: OutputFormatter not found for 'application/xml; charset=utf-8'

Upon analysis found that the issue is caused by one of the controller method that returns a specific response type. The object is not super complex and relatively simple type that produces resposne json like the one shown below

{
    "acceptedRequests": [
        {
            "requestId": "7F8092B1-F712-46E7-B991-8D8AEFC3310A",
            "dataAcceptanceStamp": 1687407503,
            "totalDataFilesCount": 3,
            "totalRecordCount": 1674
        }
    ],
    "erroredData": [
        {
            "requestId": "56100665-AF7D-4663-A52E-7D688A5432F6",
            "totalDataFilesCount": 2,
            "receivedFilesCount": 2,
            "processedFilesCount": 1,
            "errors": {
                "statusCode": 422,
                "message": "xyz.dat has multiple validation errors",
                "errors": {
                    "key1": "error desc 1",
                    "key2": "error desc 2"
                }
            }
        }
    ],
    "yetToBeProcessedRequestIds": [
        "G6100665-AF7D-4663-A52E-7D688A5432F6"
    ]
}

The definition of the controller method that results in the swagger.json failure is provided below and the problem is due to the SeekApprovalResponse type and to be more specific, only when the 'Produces' include "application/xml".

[Route("seek/approval")]
[HttpPost]
[Produces("application/json", "application/xml")]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(SeekApprovalResponse))]
public async Task<IActionResult> SeekRequestApproval([FromBody] SeekApprovalRequest requests)
{
    return Ok(new SeekApprovalResponse());
}

It is unclear what makes this 'SeekApprovalResponse' type special while other types similar to this works just fine.

The definition of this type and other types needed by this type are provided below

public class SeekApprovalResponse
{
    public ProcessedRequests[] AcceptedRequests { get; set; }

    public ErrorsInRequest[] ErroredData { get; set; }

    public string[] YetToBeProcessedRequestIds { get; set; }
}

{
    public class ProcessedRequests
    {
        public string RequestId { get; set; }
        public long DataAcceptanceStamp { get; set; }
        public byte TotalDataFilesCount { get; set; }
        public uint TotalRecordCount { get; set; }
    }
}

public class ErrorsInRequest
{
    public string RequestId { get; set; }
    public byte TotalDataFilesCount { get; set; }
    public byte ReceivedFilesCount { get; set; }
    public byte ProcessedFilesCount { get; set; }
    public ErrorResponse Errors { get; set; }
}

public class ErrorResponse
{
    public int StatusCode { get; set; }
    public string Message { get; set; }
    public Dictionary<string, string> Errors { get; set; }
}

The Swagger Example class (that matches the json provided above) is provided below

public class SeekApprovalResponseExample : IMultipleExamplesProvider<SeekApprovalResponse>
{
    public IEnumerable<SwaggerExample<SeekApprovalResponse>> GetExamples()
    {
        yield return SwaggerExample.Create(
            "Partially approved",
            new SeekApprovalResponse()
            {
                AcceptedRequests = new ProcessedRequests[]
                {
                    new ProcessedRequests
                    {
                        RequestId = "7F8092B1-F712-46E7-B991-8D8AEFC3310A",
                        DataAcceptanceStamp = 1687407503,
                        TotalDataFilesCount = 3,
                        TotalRecordCount = 1674
                    }
                },
                ErroredData = new DataErrorRSDE001[]
                {
                    new DataErrorRSDE001
                    {
                        RequestId = "56100665-AF7D-4663-A52E-7D688A5432F6",
                        TotalDataFilesCount = 2,
                        ReceivedFilesCount = 2,
                        ProcessedFilesCount = 1,
                        Errors = new ErrorResponse
                        {
                            StatusCode = 422,
                            Message = "xyz.dat has multiple validation errors",
                            Errors = new Dictionary<string, string>
                            {
                                {"key1", "error desc 1"},
                                {"key2", "error desc 2"}
                            }
                        }
                    }
                },
                YetToBeProcessedRequestIds = new string[] { "G6100665-AF7D-4663-A52E-7D688A5432F6" }
            }
            );
    }
}

When "application/xml" is removed, then Swagger UI works as expected showing json example as expected indicating that error is not misleading. While "application/xml" is added back to 'Produces', poked the SeekApprovalResponse type and it appears that public Dictionary<string, string> Errors in ErrorResponse class is causing trouble for the formatter. Once that property is temporarily removed, the swagger.json is created and page comes up fine. How to get the Dictionary<string, string> property work with Swagger UI?


Solution

  • You do not have XML Formatters enabled. Please add this line of code to Program.cs:

    builder.Services.AddControllers().AddXmlDataContractSerializerFormatters();
    

    Test Result: enter image description here