当模型具有列表属性时,swashbuckle\swagger-ui(5.6 - 使用 swagger-ui)似乎无法正确生成示例 XML。
看到这个问题:
1 - 创建一个空的 webapi 项目(我使用的是 asp.net)
2 - 添加几个示例模型(我用 Customer + Order 进行测试)
public class Customer
{
public string AccountNumber { get; set; }
[XmlArray("Orders"),XmlArrayItem("Order")]
public List<Order> Orders { get;set; }
}
public class Order
{
public string OrderNumber { get;set; }
}
3 - 使用 FromBody 创建控制器以绑定到模型
public class CustomerController : ApiController
{
public void Post([FromBody]Customer customer)
{
customer.ToString();
}
}
4 - 更改 web api 配置以允许简单的 XML
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Formatters.XmlFormatter.UseXmlSerializer = true; //ADD THIS
}
}
5 - 运行站点并使用 /swagger ui 将参数内容类型更改为 xml 并选择示例模型。你会发现例子如下。
<?xml version="1.0"?>
<Customer>
<AccountNumber>string</AccountNumber>
<Orders>
<OrderNumber>string</OrderNumber>
</Orders>
</Customer>
6 - 在控制器中的 customer.ToString() 行上使用断点提交此代码,您会发现 Orders 集合为空
7 - 将swagger-ui中的XML修改为如下内容并提交:
<?xml version="1.0"?>
<Customer>
<AccountNumber>string</AccountNumber>
<Orders>
<Order><OrderNumber>string</OrderNumber></Order>
</Orders>
</Customer>
8 -
Customer.Orders
集合现在已正确填充。
在 Swashbuckle 中修复或解决此问题的最佳方法是什么?
(围绕这个问题进行了一些讨论,以及它是否是 swagger-ui 或 Swashbuckle 中的错误,但我特别感兴趣的是使用 Swashbuckle 解决它)
我找到了以下作品:
1 - 添加 ISchemaFilter 的实现
internal class ApplySchemaVendorExtensions : ISchemaFilter
{
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
// Fix issues with xml array examples not generating correctly
if (!type.IsValueType)
{
schema.xml = new Xml { name = type.Name };
if(schema.properties != null)
{
foreach (var property in schema.properties)
{
//Array property, which wraps its elements
if (property.Value.type == "array")
{
property.Value.xml = new Xml
{
name = $"{property.Key}",
wrapped = true
};
}
}
}
}
}
}
2 - 将这一行注释到 SwaggerConfig.cs
c.SchemaFilter<ApplySchemaVendorExtensions>();
重复问题中的测试,示例 XML 现在可以直接运行。一如既往,我很好奇是否有更好的解决方案......
编辑:实际上这在我遇到这个问题的原始项目中很奇怪,但在这个问题的小型复制项目中它的行为略有不同!当我找到原因时,我会编辑这个答案......
感谢@mutex,但是我发现我需要再对它做一个调整:
internal class SwaggerFixArraysInXmlFilter : Swashbuckle.Swagger.ISchemaFilter
{
// this fixes a Swagger bug that wasn't generating correct XML elements around List<> or array[] types
public void Apply(Swashbuckle.Swagger.Schema schema, Swashbuckle.Swagger.SchemaRegistry schemaRegistry, System.Type type)
{
// Fix issues with xml array examples not generating correctly
if (!type.IsValueType)
{
schema.xml = new Swashbuckle.Swagger.Xml { name = type.Name };
if (schema.properties != null)
{
foreach (var property in schema.properties)
{
//Array property, which wraps its elements
if (property.Value.type == "array")
{
property.Value.xml = new Swashbuckle.Swagger.Xml
{
name = $"{property.Key}",
wrapped = true
};
property.Value.items.xml = new Swashbuckle.Swagger.Xml
{
name = $"{property.Value.items.type}",
wrapped = true
};
}
}
}
}
}
}
感谢@Abacus,但我发现我需要再次调整它。 (字符串不是 ValueType,因此它正在将任何字符串值重命名为
internal class SwaggerFixArraysInXmlFilter : Swashbuckle.Swagger.ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
Type type = context.Type;
// Fix issues with xml array examples not generating correctly
if (!type.IsValueType && type.Name != "String")
{
schema.Xml = new OpenApiXml { Name = type.Name };
if (schema.Properties != null)
{
foreach (var property in schema.Properties)
{
//Array property, which wraps its elements
if (property.Value.Type == "array")
{
property.Value.Xml = new OpenApiXml
{
Name = $"{property.Key}",
Wrapped = true
};
property.Value.Items.Xml = new OpenApiXml
{
Name = $"{property.Value.Items.Type}",
Wrapped = true
};
}
}
}
}
}
如果您使用 .Net Core 2.2 和 Swagger v5,您将需要以下代码集
internal class SwaggerFixArraysInXmlFilter : ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
Type type = context.Type;
// Fix issues with xml array examples not generating correctly
if (!type.IsValueType)
{
schema.Xml = new OpenApiXml { Name = type.Name };
if (schema.Properties != null)
{
foreach (var property in schema.Properties)
{
//Array property, which wraps its elements
if (property.Value.Type == "array")
{
property.Value.Xml = new OpenApiXml
{
Name = $"{property.Key}",
Wrapped = true
};
property.Value.Items.Xml = new OpenApiXml
{
Name = $"{property.Value.Items.Type}",
Wrapped = true
};
}
}
}
}
}
}
我正在使用以下
ISchemaFilter
(.NET 6.0,Swashbuckle.AspNetCore 6.5.0):
public class XmlSchemaFilter : ISchemaFilter
{
private static string TakeChars(string src, int charCount)
{
if (charCount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(charCount), $@"{nameof(charCount)} must be greater than 0");
}
return src.Length <= charCount
? src
: src[..charCount];
}
private static string SafeSubstring(string src, int startIndex)
{
if (startIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(startIndex), $@"{nameof(startIndex)} must be greater than or equal to 0");
}
return src.Length - 1 < startIndex
? string.Empty
: src[startIndex..];
}
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (schema.Properties == null)
return;
var type = context.Type;
if (type.GetCustomAttribute<ApplyXmlSchemaFilterAttribute>() == null)
return;
var typeXmlRootAttribute = type.GetCustomAttribute<XmlRootAttribute>();
if (typeXmlRootAttribute != null)
{
schema.Xml ??= new OpenApiXml();
schema.Xml.Name = typeXmlRootAttribute.ElementName;
}
var typeProperties = type.GetProperties();
var excludedProperties = typeProperties
.Where(t =>
t.GetCustomAttribute<XmlIgnoreAttribute>()
!= null)
.ToList();
// remove excluded properties ([XmlIgnore])
foreach (var schemaProperty in schema.Properties.ToArray())
{
if (excludedProperties.Any(ep =>
string.Equals(ep.Name, schemaProperty.Key, StringComparison.InvariantCultureIgnoreCase)))
{
schema.Properties.Remove(schemaProperty.Key);
}
}
var restTypeProperties = typeProperties.Except(excludedProperties).ToList();
// rename properties according to [XmlAttribute], [XmlElement], [XmlRoot], etc.
foreach (var typeProperty in restTypeProperties)
{
var camelCaseKey = TakeChars(typeProperty.Name, 1).ToLowerInvariant() + SafeSubstring(typeProperty.Name, 1);
var normalKey = typeProperty.Name;
var (propSchemaKey, propSchema) = typeProperty.Name switch
{
not null when schema.Properties.TryGetValue(camelCaseKey, out var openApiSchema) => (camelCaseKey, openApiSchema),
not null when schema.Properties.TryGetValue(normalKey, out var openApiSchema) => (normalKey, openApiSchema),
_ => (null, null),
};
if (propSchemaKey == null || propSchema == null)
{
continue;
}
propSchema.Xml ??= new OpenApiXml();
if (typeProperty.GetCustomAttribute<XmlTextAttribute>() is not null)
{
schema.Properties.Remove(propSchemaKey);
schema.Type = "string";
schema.Format = null;
}
else if (typeProperty.GetCustomAttribute<XmlElementAttribute>() is { } xmlElementAttribute)
{
propSchema.Xml.Name = xmlElementAttribute.ElementName;
}
else if (typeProperty.GetCustomAttribute<XmlAttributeAttribute>() is { } xmlAttributeAttribute)
{
propSchema.Xml.Name = xmlAttributeAttribute.AttributeName;
propSchema.Xml.Attribute = true;
}
else if (typeProperty.GetCustomAttribute<XmlArrayAttribute>() is { } xmlArrayAttribute)
{
propSchema.Xml.Name = xmlArrayAttribute.ElementName;
}
else
{
propSchema.Xml.Name = typeProperty.Name;
}
if (typeProperty.GetCustomAttribute<XmlArrayItemAttribute>() is { } xmlArrayItemAttribute
&& type.Name != "String"
&& propSchema.Type == "array")
{
// array property, which wraps its elements
propSchema.Xml.Wrapped = true;
if (typeProperty.PropertyType.IsGenericType)
{
propSchema.Items = context.SchemaGenerator.GenerateSchema(
typeProperty.PropertyType.GetGenericArguments()[0],
context.SchemaRepository,
typeProperty);
}
propSchema.Items.Xml ??= new OpenApiXml();
propSchema.Items.Xml.Name = xmlArrayItemAttribute.ElementName;
propSchema.Items.Xml.Wrapped = true;
}
}
}
}
模型应该有
ApplyXmlSchemaFilterAttribute
:
[System.AttributeUsage(System.AttributeTargets.Class)]
public class ApplyXmlSchemaFilterAttribute : System.Attribute
{
}