如何使用包括在Swashbuckle扬鞭API文档/ OpenAPI的规范子?

问题描述 投票:14回答:3

我在C#中Asp.Net的Web API 5.2项目,并生成文档与Swashbuckle。

我有一个包含继承的东西就像从动物抽象类,狗和猫类,从中获得一种动物属性模型。

Swashbuckle只显示了动物类的架构,所以我试图用ISchemaFilter玩(他们有什么建议过),但我不能让它工作,也是我无法找到一个适当的例子。

任何人都可以帮忙吗?

c# api swagger subclassing openapi
3个回答
26
投票

这似乎Swashbuckle不正确地实现多态性与我了解的笔者看有关的子类作为参数的点(如果一个动作需要的动物类,如果你与狗对象或猫对象调用它的行为不同,那么你应该有2个不同的动作...),但作为返回类型,我相信这是正确的返回动物和物体可能是狗或猫的类型。

所以,来形容我的API,并产生与正确的指导原则(知道我形容disciminator的方式,如果你有自己的鉴别,你可能需要改变,特别是一部分),我使用文档和模式滤波器线适当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"
          ]
        }
      ]
    }
  }
}

12
投票

从保罗的伟大回答遵循,如果你使用扬鞭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));
    }
}

4
投票

我想跟进克雷格的答案。

如果您使用NSwag使用产生从Swashbuckle(3.X在写作的时候)产生的扬鞭API文档打字稿定义的方法Paulo's answer解释,并进一步加强在Craig's answer你可能会面临以下问题:

  1. 生成的打字稿定义将有重复的特性,即使生成的类将扩展基类。考虑下面的C#类: public abstract class BaseClass { public string BaseProperty { get; set; } } public class ChildClass : BaseClass { public string ChildProperty { get; set; } } 当使用上述答案的IBaseClassIChildClass接口所产生的打字稿的定义是这样的: export interface IBaseClass { baseProperty : string | undefined; } export interface IChildClass extends IBaseClass { baseProperty : string | undefined; childProperty: string | undefined; } 正如你所看到的,baseProperty错误地在这两个基地和子类中定义。为了解决这个问题,我们可以通过修改Apply类的PolymorphismSchemaFilter<T>方法只包括国有性质的模式,即排除从目前的类型模式的继承属性。下面是一个例子: 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 }; ... }
  2. 产生打字稿定义不会从任何存在的中间抽象类引用属性。考虑下面的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; } } 在这种情况下,所产生的打字稿的定义是这样的: 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<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>();
    }
© www.soinside.com 2019 - 2024. All rights reserved.