如何使用 Swashbuckle 在 Swagger API 文档/OpenAPI 规范中包含子类?
How do I include subclasses in Swagger API documentation/ OpenAPI specification using Swashbuckle?
我在 c# 中有一个 Asp.Net web API 5.2 项目并使用 Swashbuckle 生成文档。
我有一个包含继承的模型,例如从 Animal 抽象 class 获得 Animal 属性 以及派生自它的 Dog 和 Cat class。
Swashbuckle 仅显示 Animal class 的架构,所以我尝试使用 ISchemaFilter(他们也建议这样做)但我无法使其工作,而且我找不到合适的示例。
有人可以帮忙吗?
似乎 Swashbuckle 没有正确实现多态性,我理解作者关于 subclasses 作为参数的观点(如果一个动作期望一个 Animal class 并且行为不同,如果你用狗对象或猫对象调用它,那么你应该有 2 个不同的动作......)但是作为 return 类型我相信它是正确的 return 动物并且对象可以是狗或猫类型。
因此,为了描述我的 API 并根据正确的准则生成适当的 JSON 模式(请注意我描述鉴别器的方式,如果您有自己的鉴别器,则可能需要特别更改那部分),我按如下方式使用文档和模式过滤器:
SwaggerDocsConfig configuration;
.....
configuration.DocumentFilter<PolymorphismDocumentFilter<YourBaseClass>>();
configuration.SchemaFilter<PolymorphismSchemaFilter<YourBaseClass>>();
.....
public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);
private static HashSet<Type> Init()
{
var abstractType = typeof(T);
var dTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
var result = new HashSet<Type>();
foreach (var item in dTypes)
result.Add(item);
return result;
}
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
if (!derivedTypes.Value.Contains(type)) return;
var clonedSchema = new Schema
{
properties = schema.properties,
type = schema.type,
required = schema.required
};
//schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in SwashBuckle
var parentSchema = new Schema { @ref = "#/definitions/" + typeof(T).Name };
schema.allOf = new List<Schema> { parentSchema, clonedSchema };
//reset properties for they are included in allOf, should be null but code does not handle it
schema.properties = new Dictionary<string, Schema>();
}
}
public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, System.Web.Http.Description.IApiExplorer apiExplorer)
{
RegisterSubClasses(schemaRegistry, typeof(T));
}
private static void RegisterSubClasses(SchemaRegistry schemaRegistry, Type abstractType)
{
const string discriminatorName = "discriminator";
var parentSchema = schemaRegistry.Definitions[SchemaIdProvider.GetSchemaId(abstractType)];
//set up a discriminator property (it must be required)
parentSchema.discriminator = discriminatorName;
parentSchema.required = new List<string> { discriminatorName };
if (!parentSchema.properties.ContainsKey(discriminatorName))
parentSchema.properties.Add(discriminatorName, new Schema { type = "string" });
//register all subclasses
var derivedTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
foreach (var item in derivedTypes)
schemaRegistry.GetOrRegister(item);
}
}
在“支持多态性的模型”部分中指定了here之前代码实现的内容。它基本上产生如下内容:
{
"definitions": {
"Pet": {
"type": "object",
"discriminator": "petType",
"properties": {
"name": {
"type": "string"
},
"petType": {
"type": "string"
}
},
"required": [
"name",
"petType"
]
},
"Cat": {
"description": "A representation of a cat",
"allOf": [
{
"$ref": "#/definitions/Pet"
},
{
"type": "object",
"properties": {
"huntingSkill": {
"type": "string",
"description": "The measured skill for hunting",
"default": "lazy",
"enum": [
"clueless",
"lazy",
"adventurous",
"aggressive"
]
}
},
"required": [
"huntingSkill"
]
}
]
},
"Dog": {
"description": "A representation of a dog",
"allOf": [
{
"$ref": "#/definitions/Pet"
},
{
"type": "object",
"properties": {
"packSize": {
"type": "integer",
"format": "int32",
"description": "the size of the pack the dog is from",
"default": 0,
"minimum": 0
}
},
"required": [
"packSize"
]
}
]
}
}
}
要继续 Paulo 的精彩回答,如果您使用的是 Swagger 2.0,则需要修改 类,如下所示:
public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);
private static HashSet<Type> Init()
{
var abstractType = typeof(T);
var dTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
var result = new HashSet<Type>();
foreach (var item in dTypes)
result.Add(item);
return result;
}
public void Apply(Schema model, SchemaFilterContext context)
{
if (!derivedTypes.Value.Contains(context.SystemType)) return;
var clonedSchema = new Schema
{
Properties = model.Properties,
Type = model.Type,
Required = model.Required
};
//schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in SwashBuckle
var parentSchema = new Schema { Ref = "#/definitions/" + typeof(T).Name };
model.AllOf = new List<Schema> { parentSchema, clonedSchema };
//reset properties for they are included in allOf, should be null but code does not handle it
model.Properties = new Dictionary<string, Schema>();
}
}
public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
private static void RegisterSubClasses(ISchemaRegistry schemaRegistry, Type abstractType)
{
const string discriminatorName = "discriminator";
var parentSchema = schemaRegistry.Definitions[abstractType.Name];
//set up a discriminator property (it must be required)
parentSchema.Discriminator = discriminatorName;
parentSchema.Required = new List<string> { discriminatorName };
if (!parentSchema.Properties.ContainsKey(discriminatorName))
parentSchema.Properties.Add(discriminatorName, new Schema { Type = "string" });
//register all subclasses
var derivedTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
foreach (var item in derivedTypes)
schemaRegistry.GetOrRegister(item);
}
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
RegisterSubClasses(context.SchemaRegistry, typeof(T));
}
}
我想跟进 Craig 的回答。
如果您使用 NSwag 从使用 Swashbuckle 生成的 Swagger API 文档(3.x 在撰写本文时)使用 and further enhanced in 中解释的方法生成 TypeScript 定义,您可能会面临以下问题:
生成的 TypeScript 定义将具有重复的属性,即使生成的 classes 将扩展基础 classes。考虑以下 C# classes:
public abstract class BaseClass
{
public string BaseProperty { get; set; }
}
public class ChildClass : BaseClass
{
public string ChildProperty { get; set; }
}
使用上述答案时,IBaseClass
和 IChildClass
接口的最终 TypeScript 定义如下所示:
export interface IBaseClass {
baseProperty : string | undefined;
}
export interface IChildClass extends IBaseClass {
baseProperty : string | undefined;
childProperty: string | undefined;
}
如您所见,baseProperty
在基础和子 class 中的定义都不正确。为了解决这个问题,我们可以修改 PolymorphismSchemaFilter<T>
class 的 Apply
方法以仅将拥有的属性包含到模式中,即从当前类型模式中排除继承的属性。这是一个例子:
public void Apply(Schema model, SchemaFilterContext context)
{
...
// Prepare a dictionary of inherited properties
var inheritedProperties = context.SystemType.GetProperties()
.Where(x => x.DeclaringType != context.SystemType)
.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
var clonedSchema = new Schema
{
// Exclude inherited properties. If not excluded,
// they would have appeared twice in nswag-generated typescript definition
Properties =
model.Properties.Where(x => !inheritedProperties.ContainsKey(x.Key))
.ToDictionary(x => x.Key, x => x.Value),
Type = model.Type,
Required = model.Required
};
...
}
生成的 TypeScript 定义不会引用任何现有中间抽象 classes 中的属性。考虑以下 C# classes:
public abstract class SuperClass
{
public string SuperProperty { get; set; }
}
public abstract class IntermediateClass : SuperClass
{
public string IntermediateProperty { get; set; }
}
public class ChildClass : BaseClass
{
public string ChildProperty { get; set; }
}
在这种情况下,生成的 TypeScript 定义将如下所示:
export interface ISuperClass {
superProperty: string | undefined;
}
export interface IIntermediateClass extends ISuperClass {
intermediateProperty : string | undefined;
}
export interface IChildClass extends ISuperClass {
childProperty: string | undefined;
}
注意生成的 IChildClass
接口如何直接扩展 ISuperClass
,忽略 IIntermediateClass
接口,有效地使 IChildClass
的任何实例都没有 intermediateProperty
属性.
我们可以使用下面的代码来解决这个问题:
public void Apply(Schema model, SchemaFilterContext context)
{
...
// Use the BaseType name for parentSchema instead of typeof(T),
// because we could have more classes in the hierarchy
var parentSchema = new Schema
{
Ref = "#/definitions/" + (context.SystemType.BaseType?.Name ?? typeof(T).Name)
};
...
}
这将确保子 class 正确引用中间 class。
总之,最终的代码应该是这样的:
public void Apply(Schema model, SchemaFilterContext context)
{
if (!derivedTypes.Value.Contains(context.SystemType))
{
return;
}
// Prepare a dictionary of inherited properties
var inheritedProperties = context.SystemType.GetProperties()
.Where(x => x.DeclaringType != context.SystemType)
.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
var clonedSchema = new Schema
{
// Exclude inherited properties. If not excluded,
// they would have appeared twice in nswag-generated typescript definition
Properties =
model.Properties.Where(x => !inheritedProperties.ContainsKey(x.Key))
.ToDictionary(x => x.Key, x => x.Value),
Type = model.Type,
Required = model.Required
};
// Use the BaseType name for parentSchema instead of typeof(T),
// because we could have more abstract classes in the hierarchy
var parentSchema = new Schema
{
Ref = "#/definitions/" + (context.SystemType.BaseType?.Name ?? typeof(T).Name)
};
model.AllOf = new List<Schema> { parentSchema, clonedSchema };
// reset properties for they are included in allOf, should be null but code does not handle it
model.Properties = new Dictionary<string, Schema>();
}
我们最近升级到 .NET Core 3.1 和 Swashbuckle.AspNetCore 5.0
API 有所改变。
如果有人需要这个过滤器,这里是通过最少的更改获得类似行为的代码:
public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
RegisterSubClasses(context.SchemaRepository, context.SchemaGenerator, typeof(T));
}
private static void RegisterSubClasses(SchemaRepository schemaRegistry, ISchemaGenerator schemaGenerator, Type abstractType)
{
const string discriminatorName = "$type";
OpenApiSchema parentSchema = null;
if (schemaRegistry.TryGetIdFor(abstractType, out string parentSchemaId))
parentSchema = schemaRegistry.Schemas[parentSchemaId];
else
parentSchema = schemaRegistry.GetOrAdd(abstractType, parentSchemaId, () => new OpenApiSchema());
// set up a discriminator property (it must be required)
parentSchema.Discriminator = new OpenApiDiscriminator() { PropertyName = discriminatorName };
parentSchema.Required = new HashSet<string> { discriminatorName };
if (parentSchema.Properties == null)
parentSchema.Properties = new Dictionary<string, OpenApiSchema>();
if (!parentSchema.Properties.ContainsKey(discriminatorName))
parentSchema.Properties.Add(discriminatorName, new OpenApiSchema() { Type = "string", Default = new OpenApiString(abstractType.FullName) });
// register all subclasses
var derivedTypes = abstractType.GetTypeInfo().Assembly.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
foreach (var item in derivedTypes)
schemaGenerator.GenerateSchema(item, schemaRegistry);
}
}
public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (!derivedTypes.Value.Contains(context.Type)) return;
Type type = context.Type;
var clonedSchema = new OpenApiSchema
{
Properties = schema.Properties,
Type = schema.Type,
Required = schema.Required
};
// schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in Swashbuckle.AspNetCore
var parentSchema = new OpenApiSchema
{
Reference = new OpenApiReference() { ExternalResource = "#/definitions/" + typeof(T).Name }
};
var assemblyName = Assembly.GetAssembly(type).GetName();
schema.Discriminator = new OpenApiDiscriminator() { PropertyName = "$type" };
// This is required if you use Microsoft's AutoRest client to generate the JavaScript/TypeScript models
schema.Extensions.Add("x-ms-discriminator-value", new OpenApiObject() { ["name"] = new OpenApiString($"{type.FullName}, {assemblyName.Name}") });
schema.AllOf = new List<OpenApiSchema> { parentSchema, clonedSchema };
// reset properties for they are included in allOf, should be null but code does not handle it
schema.Properties = new Dictionary<string, OpenApiSchema>();
}
private static HashSet<Type> Init()
{
var abstractType = typeof(T);
var dTypes = abstractType.GetTypeInfo().Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
var result = new HashSet<Type>();
foreach (var item in dTypes)
result.Add(item);
return result;
}
}
我没有完全检查结果,但它似乎给出了相同的行为。
另请注意,您需要导入这些命名空间:
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Any;
using System.Reflection;
using Swashbuckle.AspNetCore.SwaggerGen;
从 this merge 到 Swashbuckle.AspNetCore,您可以使用以下方法获得对多态模式的基本支持:
services.AddSwaggerGen(c =>
{
c.GeneratePolymorphicSchemas();
}
您还可以通过注释库中的属性表达您的派生类型:
[SwaggerSubTypes(typeof(SubClass), Discriminator = "value")]
This article 详细介绍了如何使用 Newtonsoft 反序列化派生类型。
从 5.6.3 版本开始有效:
services.AddSwaggerGen(options =>
{
options.UseOneOfForPolymorphism();
options.SelectDiscriminatorNameUsing(_ => "type");
});
我在 c# 中有一个 Asp.Net web API 5.2 项目并使用 Swashbuckle 生成文档。
我有一个包含继承的模型,例如从 Animal 抽象 class 获得 Animal 属性 以及派生自它的 Dog 和 Cat class。
Swashbuckle 仅显示 Animal class 的架构,所以我尝试使用 ISchemaFilter(他们也建议这样做)但我无法使其工作,而且我找不到合适的示例。
有人可以帮忙吗?
似乎 Swashbuckle 没有正确实现多态性,我理解作者关于 subclasses 作为参数的观点(如果一个动作期望一个 Animal class 并且行为不同,如果你用狗对象或猫对象调用它,那么你应该有 2 个不同的动作......)但是作为 return 类型我相信它是正确的 return 动物并且对象可以是狗或猫类型。
因此,为了描述我的 API 并根据正确的准则生成适当的 JSON 模式(请注意我描述鉴别器的方式,如果您有自己的鉴别器,则可能需要特别更改那部分),我按如下方式使用文档和模式过滤器:
SwaggerDocsConfig configuration;
.....
configuration.DocumentFilter<PolymorphismDocumentFilter<YourBaseClass>>();
configuration.SchemaFilter<PolymorphismSchemaFilter<YourBaseClass>>();
.....
public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);
private static HashSet<Type> Init()
{
var abstractType = typeof(T);
var dTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
var result = new HashSet<Type>();
foreach (var item in dTypes)
result.Add(item);
return result;
}
public void Apply(Schema schema, SchemaRegistry schemaRegistry, Type type)
{
if (!derivedTypes.Value.Contains(type)) return;
var clonedSchema = new Schema
{
properties = schema.properties,
type = schema.type,
required = schema.required
};
//schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in SwashBuckle
var parentSchema = new Schema { @ref = "#/definitions/" + typeof(T).Name };
schema.allOf = new List<Schema> { parentSchema, clonedSchema };
//reset properties for they are included in allOf, should be null but code does not handle it
schema.properties = new Dictionary<string, Schema>();
}
}
public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
public void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, System.Web.Http.Description.IApiExplorer apiExplorer)
{
RegisterSubClasses(schemaRegistry, typeof(T));
}
private static void RegisterSubClasses(SchemaRegistry schemaRegistry, Type abstractType)
{
const string discriminatorName = "discriminator";
var parentSchema = schemaRegistry.Definitions[SchemaIdProvider.GetSchemaId(abstractType)];
//set up a discriminator property (it must be required)
parentSchema.discriminator = discriminatorName;
parentSchema.required = new List<string> { discriminatorName };
if (!parentSchema.properties.ContainsKey(discriminatorName))
parentSchema.properties.Add(discriminatorName, new Schema { type = "string" });
//register all subclasses
var derivedTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
foreach (var item in derivedTypes)
schemaRegistry.GetOrRegister(item);
}
}
在“支持多态性的模型”部分中指定了here之前代码实现的内容。它基本上产生如下内容:
{
"definitions": {
"Pet": {
"type": "object",
"discriminator": "petType",
"properties": {
"name": {
"type": "string"
},
"petType": {
"type": "string"
}
},
"required": [
"name",
"petType"
]
},
"Cat": {
"description": "A representation of a cat",
"allOf": [
{
"$ref": "#/definitions/Pet"
},
{
"type": "object",
"properties": {
"huntingSkill": {
"type": "string",
"description": "The measured skill for hunting",
"default": "lazy",
"enum": [
"clueless",
"lazy",
"adventurous",
"aggressive"
]
}
},
"required": [
"huntingSkill"
]
}
]
},
"Dog": {
"description": "A representation of a dog",
"allOf": [
{
"$ref": "#/definitions/Pet"
},
{
"type": "object",
"properties": {
"packSize": {
"type": "integer",
"format": "int32",
"description": "the size of the pack the dog is from",
"default": 0,
"minimum": 0
}
},
"required": [
"packSize"
]
}
]
}
}
}
要继续 Paulo 的精彩回答,如果您使用的是 Swagger 2.0,则需要修改 类,如下所示:
public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);
private static HashSet<Type> Init()
{
var abstractType = typeof(T);
var dTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
var result = new HashSet<Type>();
foreach (var item in dTypes)
result.Add(item);
return result;
}
public void Apply(Schema model, SchemaFilterContext context)
{
if (!derivedTypes.Value.Contains(context.SystemType)) return;
var clonedSchema = new Schema
{
Properties = model.Properties,
Type = model.Type,
Required = model.Required
};
//schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in SwashBuckle
var parentSchema = new Schema { Ref = "#/definitions/" + typeof(T).Name };
model.AllOf = new List<Schema> { parentSchema, clonedSchema };
//reset properties for they are included in allOf, should be null but code does not handle it
model.Properties = new Dictionary<string, Schema>();
}
}
public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
private static void RegisterSubClasses(ISchemaRegistry schemaRegistry, Type abstractType)
{
const string discriminatorName = "discriminator";
var parentSchema = schemaRegistry.Definitions[abstractType.Name];
//set up a discriminator property (it must be required)
parentSchema.Discriminator = discriminatorName;
parentSchema.Required = new List<string> { discriminatorName };
if (!parentSchema.Properties.ContainsKey(discriminatorName))
parentSchema.Properties.Add(discriminatorName, new Schema { Type = "string" });
//register all subclasses
var derivedTypes = abstractType.Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
foreach (var item in derivedTypes)
schemaRegistry.GetOrRegister(item);
}
public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
{
RegisterSubClasses(context.SchemaRegistry, typeof(T));
}
}
我想跟进 Craig 的回答。
如果您使用 NSwag 从使用 Swashbuckle 生成的 Swagger API 文档(3.x 在撰写本文时)使用
生成的 TypeScript 定义将具有重复的属性,即使生成的 classes 将扩展基础 classes。考虑以下 C# classes:
public abstract class BaseClass { public string BaseProperty { get; set; } } public class ChildClass : BaseClass { public string ChildProperty { get; set; } }
使用上述答案时,
IBaseClass
和IChildClass
接口的最终 TypeScript 定义如下所示:export interface IBaseClass { baseProperty : string | undefined; } export interface IChildClass extends IBaseClass { baseProperty : string | undefined; childProperty: string | undefined; }
如您所见,
baseProperty
在基础和子 class 中的定义都不正确。为了解决这个问题,我们可以修改PolymorphismSchemaFilter<T>
class 的Apply
方法以仅将拥有的属性包含到模式中,即从当前类型模式中排除继承的属性。这是一个例子:public void Apply(Schema model, SchemaFilterContext context) { ... // Prepare a dictionary of inherited properties var inheritedProperties = context.SystemType.GetProperties() .Where(x => x.DeclaringType != context.SystemType) .ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase); var clonedSchema = new Schema { // Exclude inherited properties. If not excluded, // they would have appeared twice in nswag-generated typescript definition Properties = model.Properties.Where(x => !inheritedProperties.ContainsKey(x.Key)) .ToDictionary(x => x.Key, x => x.Value), Type = model.Type, Required = model.Required }; ... }
生成的 TypeScript 定义不会引用任何现有中间抽象 classes 中的属性。考虑以下 C# classes:
public abstract class SuperClass { public string SuperProperty { get; set; } } public abstract class IntermediateClass : SuperClass { public string IntermediateProperty { get; set; } } public class ChildClass : BaseClass { public string ChildProperty { get; set; } }
在这种情况下,生成的 TypeScript 定义将如下所示:
export interface ISuperClass { superProperty: string | undefined; } export interface IIntermediateClass extends ISuperClass { intermediateProperty : string | undefined; } export interface IChildClass extends ISuperClass { childProperty: string | undefined; }
注意生成的
IChildClass
接口如何直接扩展ISuperClass
,忽略IIntermediateClass
接口,有效地使IChildClass
的任何实例都没有intermediateProperty
属性.我们可以使用下面的代码来解决这个问题:
public void Apply(Schema model, SchemaFilterContext context) { ... // Use the BaseType name for parentSchema instead of typeof(T), // because we could have more classes in the hierarchy var parentSchema = new Schema { Ref = "#/definitions/" + (context.SystemType.BaseType?.Name ?? typeof(T).Name) }; ... }
这将确保子 class 正确引用中间 class。
总之,最终的代码应该是这样的:
public void Apply(Schema model, SchemaFilterContext context)
{
if (!derivedTypes.Value.Contains(context.SystemType))
{
return;
}
// Prepare a dictionary of inherited properties
var inheritedProperties = context.SystemType.GetProperties()
.Where(x => x.DeclaringType != context.SystemType)
.ToDictionary(x => x.Name, StringComparer.OrdinalIgnoreCase);
var clonedSchema = new Schema
{
// Exclude inherited properties. If not excluded,
// they would have appeared twice in nswag-generated typescript definition
Properties =
model.Properties.Where(x => !inheritedProperties.ContainsKey(x.Key))
.ToDictionary(x => x.Key, x => x.Value),
Type = model.Type,
Required = model.Required
};
// Use the BaseType name for parentSchema instead of typeof(T),
// because we could have more abstract classes in the hierarchy
var parentSchema = new Schema
{
Ref = "#/definitions/" + (context.SystemType.BaseType?.Name ?? typeof(T).Name)
};
model.AllOf = new List<Schema> { parentSchema, clonedSchema };
// reset properties for they are included in allOf, should be null but code does not handle it
model.Properties = new Dictionary<string, Schema>();
}
我们最近升级到 .NET Core 3.1 和 Swashbuckle.AspNetCore 5.0 API 有所改变。 如果有人需要这个过滤器,这里是通过最少的更改获得类似行为的代码:
public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
RegisterSubClasses(context.SchemaRepository, context.SchemaGenerator, typeof(T));
}
private static void RegisterSubClasses(SchemaRepository schemaRegistry, ISchemaGenerator schemaGenerator, Type abstractType)
{
const string discriminatorName = "$type";
OpenApiSchema parentSchema = null;
if (schemaRegistry.TryGetIdFor(abstractType, out string parentSchemaId))
parentSchema = schemaRegistry.Schemas[parentSchemaId];
else
parentSchema = schemaRegistry.GetOrAdd(abstractType, parentSchemaId, () => new OpenApiSchema());
// set up a discriminator property (it must be required)
parentSchema.Discriminator = new OpenApiDiscriminator() { PropertyName = discriminatorName };
parentSchema.Required = new HashSet<string> { discriminatorName };
if (parentSchema.Properties == null)
parentSchema.Properties = new Dictionary<string, OpenApiSchema>();
if (!parentSchema.Properties.ContainsKey(discriminatorName))
parentSchema.Properties.Add(discriminatorName, new OpenApiSchema() { Type = "string", Default = new OpenApiString(abstractType.FullName) });
// register all subclasses
var derivedTypes = abstractType.GetTypeInfo().Assembly.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
foreach (var item in derivedTypes)
schemaGenerator.GenerateSchema(item, schemaRegistry);
}
}
public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
if (!derivedTypes.Value.Contains(context.Type)) return;
Type type = context.Type;
var clonedSchema = new OpenApiSchema
{
Properties = schema.Properties,
Type = schema.Type,
Required = schema.Required
};
// schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in Swashbuckle.AspNetCore
var parentSchema = new OpenApiSchema
{
Reference = new OpenApiReference() { ExternalResource = "#/definitions/" + typeof(T).Name }
};
var assemblyName = Assembly.GetAssembly(type).GetName();
schema.Discriminator = new OpenApiDiscriminator() { PropertyName = "$type" };
// This is required if you use Microsoft's AutoRest client to generate the JavaScript/TypeScript models
schema.Extensions.Add("x-ms-discriminator-value", new OpenApiObject() { ["name"] = new OpenApiString($"{type.FullName}, {assemblyName.Name}") });
schema.AllOf = new List<OpenApiSchema> { parentSchema, clonedSchema };
// reset properties for they are included in allOf, should be null but code does not handle it
schema.Properties = new Dictionary<string, OpenApiSchema>();
}
private static HashSet<Type> Init()
{
var abstractType = typeof(T);
var dTypes = abstractType.GetTypeInfo().Assembly
.GetTypes()
.Where(x => abstractType != x && abstractType.IsAssignableFrom(x));
var result = new HashSet<Type>();
foreach (var item in dTypes)
result.Add(item);
return result;
}
}
我没有完全检查结果,但它似乎给出了相同的行为。
另请注意,您需要导入这些命名空间:
using Microsoft.OpenApi.Models;
using Microsoft.OpenApi.Any;
using System.Reflection;
using Swashbuckle.AspNetCore.SwaggerGen;
从 this merge 到 Swashbuckle.AspNetCore,您可以使用以下方法获得对多态模式的基本支持:
services.AddSwaggerGen(c =>
{
c.GeneratePolymorphicSchemas();
}
您还可以通过注释库中的属性表达您的派生类型:
[SwaggerSubTypes(typeof(SubClass), Discriminator = "value")]
This article 详细介绍了如何使用 Newtonsoft 反序列化派生类型。
从 5.6.3 版本开始有效:
services.AddSwaggerGen(options =>
{
options.UseOneOfForPolymorphism();
options.SelectDiscriminatorNameUsing(_ => "type");
});