我在c#中有一个Asp.Net Web API 5.2项目,并使用Swashbuckle生成文档.
我有一个包含继承的模型,比如从Animal抽象类中获取Animal属性,从中派生出Dog和Cat类.
Swashbuckle只显示Animal类的模式,所以我尝试使用ISchemaFilter(他们也建议),但我无法使它工作,我也找不到合适的例子.
有人可以帮忙吗?
似乎Swashbuckle没有正确实现多态性,我理解作者关于子类作为参数的观点(如果一个动作需要一个Animal类,如果用dog对象或cat对象调用它,行为会有所不同,那么你应该有2个不同的动作...)但作为返回类型我相信返回Animal是正确的,对象可能是Dog或Cat类型.
因此,要描述我的API并根据正确的指导方针生成一个合适的JSON模式(请注意我描述说明者的方式,如果你有自己的鉴别器,你可能需要特别更改那个部分),我使用文档和模式过滤器如下:
SwaggerDocsConfig configuration; ..... configuration.DocumentFilter>(); configuration.SchemaFilter >(); ..... public class PolymorphismSchemaFilter : ISchemaFilter { private readonly Lazy > derivedTypes = new Lazy >(Init); private static HashSet Init() { var abstractType = typeof(T); var dTypes = abstractType.Assembly .GetTypes() .Where(x => abstractType != x && abstractType.IsAssignableFrom(x)); var result = new HashSet (); 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 { parentSchema, clonedSchema }; //reset properties for they are included in allOf, should be null but code does not handle it schema.properties = new Dictionary (); } } public class PolymorphismDocumentFilter : 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 { 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); } }
前面的代码实现的内容在这里,在"支持多态性的模型"一节中指定.它基本上产生如下内容:
{ "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: ISchemaFilter { private readonly Lazy > derivedTypes = new Lazy >(Init); private static HashSet Init() { var abstractType = typeof(T); var dTypes = abstractType.Assembly .GetTypes() .Where(x => abstractType != x && abstractType.IsAssignableFrom(x)); var result = new HashSet (); 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 { parentSchema, clonedSchema }; //reset properties for they are included in allOf, should be null but code does not handle it model.Properties = new Dictionary (); } } public class PolymorphismDocumentFilter : 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 { 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)); } }
我想跟进克雷格的回答。
如果您使用Paulo的答案中解释的方法以及Craig的答案中进一步增强的方法,使用NSwag从Swashbuckle(在撰写本文时为3.x)生成的Swagger API文档中生成TypeScript定义,则可能会遇到以下问题:
即使生成的类将扩展基类,生成的TypeScript定义也将具有重复的属性。考虑以下C#类:
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
在基类和子类中均未正确定义。为了解决这个问题,我们可以修改类的Apply
方法PolymorphismSchemaFilter
以仅将拥有的属性包括在架构中,即从当前类型的架构中排除继承的属性。这是一个例子:
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定义将不会引用任何现有的中间抽象类的属性。考虑以下C#类:
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) }; ... }
这将确保子类正确引用中间类。
总之,最终的代码如下所示:
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{ parentSchema, clonedSchema }; // reset properties for they are included in allOf, should be null but code does not handle it model.Properties = new Dictionary (); }