当前位置:  开发笔记 > 编程语言 > 正文

如何使用Swashbuckle在Swagger API文档/ OpenAPI规范中包含子类?

如何解决《如何使用Swashbuckle在SwaggerAPI文档/OpenAPI规范中包含子类?》经验,为你挑选了3个好方法。

我在c#中有一个Asp.Net Web API 5.2项目,并使用Swashbuckle生成文档.

我有一个包含继承的模型,比如从Animal抽象类中获取Animal属性,从中派生出Dog和Cat类.

Swashbuckle只显示Animal类的模式,所以我尝试使用ISchemaFilter(他们也建议),但我无法使它工作,我也找不到合适的例子.

有人可以帮忙吗?



1> Paolo Vigori..:

似乎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"
          ]
        }
      ]
    }
  }
}


`SchemaIdProvider`必须是你自己的类?我想你可以使用Swagger的默认约定,添加一个`Using Swashbuckle.Swagger`然后将那行代码改为`var parentSchema = schemaRegistry.Definitions [abstractType.FriendlyId];`
@PaoloVigori:我在Swashbuckle.AspNetCore上使用了它,调用了`PolymorphismDocumentFilter`并在代码中设置了discriminator,但是在生成的swagger定义中没有.`allOf`条目在那里.有任何想法吗?

2> Craig.Nicol..:

要继续使用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));
    }
}



3> Dejan Janjuš..:

我想跟进克雷格的回答。

如果您使用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; }
    }
    

    使用上述答案时,IBaseClassIChildClass接口的最终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();
    }

推荐阅读
mobiledu2402851377
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有