.NET Core 6 Swagger支持XML输出时Dictionary引发FormatterNotFoundException
问题背景
在.NET Core 6 API项目中,已配置Swagger及Swashbuckle.AspNetCore.Filters组件,同时为支持XML输出添加了以下序列化配置:
builder.Services.AddControllers(options => { options.InputFormatters.Add(new XmlSerializerInputFormatter(options)); options.OutputFormatters.Add(new XmlSerializerOutputFormatter()); });
但当控制器方法添加[Produces("application/json", "application/xml")]特性,并返回包含Dictionary<string, string>属性的SeekApprovalResponse类型时,生成swagger.json时抛出错误:
Swashbuckle.AspNetCore.Filters.MvcOutputFormatter+FormatterNotFoundException: OutputFormatter not found for 'application/xml; charset=utf-8'
经排查,问题核心是ErrorResponse类中的Dictionary<string, string> Errors属性——默认的XmlSerializerOutputFormatter无法原生处理Dictionary类型的XML序列化,导致Swashbuckle在生成XML格式示例时找不到可用的格式化器。
解决方案
方案一:改用DataContractSerializer处理XML序列化
默认的XmlSerializer不支持Dictionary的原生序列化,而DataContractSerializer原生支持该类型。替换XML格式化器并配置类的序列化特性即可解决问题:
- 修改控制器配置,替换XML格式化器:
builder.Services.AddControllers(options => { // 移除原有的XmlSerializer格式化器 var existingXmlOutput = options.OutputFormatters.OfType<XmlSerializerOutputFormatter>().FirstOrDefault(); if (existingXmlOutput != null) { options.OutputFormatters.Remove(existingXmlOutput); } var existingXmlInput = options.InputFormatters.OfType<XmlSerializerInputFormatter>().FirstOrDefault(); if (existingXmlInput != null) { options.InputFormatters.Remove(existingXmlInput); } // 添加DataContractSerializer的XML格式化器 options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options)); options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); });
- 给所有需要序列化的类添加
[DataContract]和[DataMember]特性:
using System.Runtime.Serialization; [DataContract] public class SeekApprovalResponse { [DataMember] public ProcessedRequests[] AcceptedRequests { get; set; } [DataMember] public ErrorsInRequest[] ErroredData { get; set; } [DataMember] public string[] YetToBeProcessedRequestIds { get; set; } } [DataContract] public class ProcessedRequests { [DataMember] public string RequestId { get; set; } [DataMember] public long DataAcceptanceStamp { get; set; } [DataMember] public byte TotalDataFilesCount { get; set; } [DataMember] public uint TotalRecordCount { get; set; } } [DataContract] public class ErrorsInRequest { [DataMember] public string RequestId { get; set; } [DataMember] public byte TotalDataFilesCount { get; set; } [DataMember] public byte ReceivedFilesCount { get; set; } [DataMember] public byte ProcessedFilesCount { get; set; } [DataMember] public ErrorResponse Errors { get; set; } } [DataContract] public class ErrorResponse { [DataMember] public int StatusCode { get; set; } [DataMember] public string Message { get; set; } [DataMember] public Dictionary<string, string> Errors { get; set; } }
此方案既能保留XML输出支持,又能让Swagger正常生成JSON和XML格式的示例。
方案二:通过中转属性适配XmlSerializer
如果不想替换序列化器,可以给ErrorResponse添加一个可被XmlSerializer处理的中转属性,同时忽略原Dictionary属性:
using System.Xml.Serialization; public class ErrorResponse { public int StatusCode { get; set; } public string Message { get; set; } // 供XmlSerializer序列化的中转属性 [XmlArray("Errors")] [XmlArrayItem("Error")] public List<KeyValuePair<string, string>> ErrorsList { get => Errors?.ToList() ?? new List<KeyValuePair<string, string>>(); set => Errors = value?.ToDictionary(kv => kv.Key, kv => kv.Value) ?? new Dictionary<string, string>(); } // 忽略原Dictionary属性的XML序列化 [XmlIgnore] public Dictionary<string, string> Errors { get; set; } }
Swagger示例类无需修改,赋值Errors属性时会自动同步到ErrorsList,XmlSerializer会序列化ErrorsList为XML节点,同时API的JSON输出仍使用原Errors属性。
方案三:仅为JSON格式生成Swagger示例(备选)
如果XML格式的Swagger示例不是必需的,可以配置Swashbuckle只针对JSON生成示例:
builder.Services.AddSwaggerGen(c => { // 其他Swagger配置... // 指定仅为JSON格式生成示例 c.ExampleFilters(() => new[] { "application/json" }); });
此方案会保留API的XML输出支持,但Swagger UI中XML格式不会显示示例。
内容的提问来源于stack exchange,提问作者Beli

