将IShemaFilter示例过渡到Swashbakle 5.0 [英] Transitioning ISchemaFilter examples to Swashbuckle 5.0

查看:35
本文介绍了将IShemaFilter示例过渡到Swashbakle 5.0的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在Swashbakle 5之前,可以定义和注册可以提供模型实现示例的ISchemaFilter

public class MyModelExampleSchemaFilter : ISchemaFilter
{
    public void Apply(Schema schema, SchemaFilterContext context)
    {
        if (context.SystemType.IsAssignableFrom(typeof(MyModel)))
        {
            schema.Example = new MyModel
            {
                Name = "model name",
                value = 42
            };
        }
    }
}

Schema.Example将接受任意对象,并且它将在生成OpenApi架构时正确序列化。

然而,随着向.NET Core 3和Swashbakle 5的迁移,Schema.Example属性不再是object,需要类型Microsoft.OpenApi.Any.IOpenApiAny。对于如何提供新的示例,似乎没有记录在案的前进路径。

基于查看Microsoft.OpenApi中的代码,我尝试构建我自己的IOpenApiAny实现,但在调用Write方法之前,任何使用它来生成示例的尝试都会在Microsoft.OpenApi.Writers.OpenApiWriterAnyExtensions.WriteObject(IOpenApiWriter writer, OpenApiObject entity)中失败。我不认为下面的代码是完全正确的,但我希望它至少能照亮一条道路以及如何前进。

/// <summary>
///     A class that recursively adapts a unidirectional POCO tree into an <see cref="IOpenApiAny" />
/// </summary>
/// <remarks>
///     <para>This will fail if a graph is provided (backwards and forwards references</para>
/// </remarks>
public class OpenApiPoco : IOpenApiAny
{
    /// <summary>
    ///     The model to be converted
    /// </summary>
    private readonly object _model;

    /// <summary>
    ///     Initializes a new instance of the <see cref="OpenApiPoco" /> class.
    /// </summary>
    /// <param name="model">the model to convert to an <see cref="IOpenApiAny" /> </param>
    public OpenApiPoco(object model)
    {
        this._model = model;
    }

    /// <inheritdoc />
    public AnyType AnyType => DetermineAnyType(this._model);

    #region From Interface IOpenApiExtension

    /// <inheritdoc />
    public void Write(IOpenApiWriter writer,
                        OpenApiSpecVersion specVersion)
    {
        this.Write(this._model, writer, specVersion);
    }

    #endregion

    private static AnyType DetermineAnyType(object model)
    {
        if (model is null)
        {
            return AnyType.Null;
        }

        var modelType = model.GetType();

        if (modelType.IsAssignableFrom(typeof(int))
            || modelType.IsAssignableFrom(typeof(long))
            || modelType.IsAssignableFrom(typeof(float))
            || modelType.IsAssignableFrom(typeof(double))
            || modelType.IsAssignableFrom(typeof(string))
            || modelType.IsAssignableFrom(typeof(byte))
            || modelType.IsAssignableFrom(typeof(byte[])) // Binary or Byte
            || modelType.IsAssignableFrom(typeof(bool))
            || modelType.IsAssignableFrom(typeof(DateTimeOffset)) // DateTime
            || modelType.IsAssignableFrom(typeof(DateTime))       // Date
        )
        {
            return AnyType.Primitive;
        }

        if (modelType.IsAssignableFrom(typeof(IEnumerable))) // test after primitive check so as to avoid catching string and byte[]
        {
            return AnyType.Array;
        }

        return AnyType.Object; // Assume object
    }

    private void Write(object model,
                        [NotNull] IOpenApiWriter writer,
                        OpenApiSpecVersion specVersion)
    {
        if (writer is null)
        {
            throw new ArgumentNullException(nameof(writer));
        }

        if (model is null)
        {
            writer.WriteNull();
            return;
        }

        var modelType = model.GetType();

        if (modelType.IsAssignableFrom(typeof(int))
            || modelType.IsAssignableFrom(typeof(long))
            || modelType.IsAssignableFrom(typeof(float))
            || modelType.IsAssignableFrom(typeof(double))
            || modelType.IsAssignableFrom(typeof(string))
            || modelType.IsAssignableFrom(typeof(byte[])) // Binary or Byte
            || modelType.IsAssignableFrom(typeof(bool))
            || modelType.IsAssignableFrom(typeof(DateTimeOffset)) // DateTime
            || modelType.IsAssignableFrom(typeof(DateTime))       // Date
        )
        {
            this.WritePrimitive(model, writer, specVersion);
            return;
        }

        if (modelType.IsAssignableFrom(typeof(IEnumerable))) // test after primitive check so as to avoid catching string and byte[]
        {
            this.WriteArray((IEnumerable) model, writer, specVersion);
            return;
        }

        this.WriteObject(model, writer, specVersion); // Assume object
    }

    private void WritePrimitive(object model,
                                IOpenApiWriter writer,
                                OpenApiSpecVersion specVersion)
    {
        switch (model.GetType())
        {
            case TypeInfo typeInfo
                when typeInfo.IsAssignableFrom(typeof(string)): // string
                writer.WriteValue((string) model);
                break;

            case TypeInfo typeInfo
                when typeInfo.IsAssignableFrom(typeof(byte[])): // assume Binary; can't differentiate from Byte and Binary based on type alone
                // if we chose to treat byte[] as Byte we would Base64 it to string. eg: writer.WriteValue(Convert.ToBase64String((byte[]) propertyValue));
                writer.WriteValue(Encoding.UTF8.GetString((byte[]) model));
                break;

            case TypeInfo typeInfo
                when typeInfo.IsAssignableFrom(typeof(bool)): // boolean
                writer.WriteValue((bool) model);
                break;

            case TypeInfo typeInfo
                when typeInfo.IsAssignableFrom(typeof(DateTimeOffset)): // DateTime as DateTimeOffset
                writer.WriteValue((DateTimeOffset) model);
                break;

            case TypeInfo typeInfo
                when typeInfo.IsAssignableFrom(typeof(DateTime)): // Date as DateTime
                writer.WriteValue((DateTime) model);
                break;

            case TypeInfo typeInfo
                when typeInfo.IsAssignableFrom(typeof(double)): // Double
                writer.WriteValue((double) model);
                break;

            case TypeInfo typeInfo
                when typeInfo.IsAssignableFrom(typeof(float)): // Float
                writer.WriteValue((float) model);
                break;

            case TypeInfo typeInfo
                when typeInfo.IsAssignableFrom(typeof(int)): // Integer
                writer.WriteValue((int) model);
                break;

            case TypeInfo typeInfo
                when typeInfo.IsAssignableFrom(typeof(long)): // Long
                writer.WriteValue((long) model);
                break;

            case TypeInfo typeInfo
                when typeInfo.IsAssignableFrom(typeof(Guid)): // Guid (as a string)
                writer.WriteValue(model.ToString());
                break;

            default:
                throw new ArgumentOutOfRangeException(nameof(model),
                                                        model?.GetType()
                                                            .Name,
                                                        "unexpected model type");
        }
    }

    private void WriteArray(IEnumerable model,
                            IOpenApiWriter writer,
                            OpenApiSpecVersion specVersion)
    {
        writer.WriteStartArray();

        foreach (var item in model)
        {
            this.Write(item, writer, specVersion); // recursive call
        }

        writer.WriteEndArray();
    }

    private void WriteObject(object model,
                                IOpenApiWriter writer,
                                OpenApiSpecVersion specVersion)
    {
        var propertyInfos = model.GetType()
                                    .GetProperties();

        writer.WriteStartObject();

        foreach (var property in propertyInfos)
        {
            writer.WritePropertyName(property.Name);

            var propertyValue = property.GetValue(model);

            switch (propertyValue.GetType())
            {
                case TypeInfo typeInfo                                        // primitives
                    when typeInfo.IsAssignableFrom(typeof(string))            // string
                            || typeInfo.IsAssignableFrom(typeof(byte[]))         // assume Binary or Byte
                            || typeInfo.IsAssignableFrom(typeof(bool))           // boolean
                            || typeInfo.IsAssignableFrom(typeof(DateTimeOffset)) // DateTime as DateTimeOffset
                            || typeInfo.IsAssignableFrom(typeof(DateTime))       // Date as DateTime
                            || typeInfo.IsAssignableFrom(typeof(double))         // Double
                            || typeInfo.IsAssignableFrom(typeof(float))          // Float
                            || typeInfo.IsAssignableFrom(typeof(int))            // Integer
                            || typeInfo.IsAssignableFrom(typeof(long))           // Long
                            || typeInfo.IsAssignableFrom(typeof(Guid)):          // Guid (as a string)
                    this.WritePrimitive(propertyValue, writer, specVersion);
                    break;

                case TypeInfo typeInfo                                   // Array test after primitive check so as to avoid catching string and byte[]
                    when typeInfo.IsAssignableFrom(typeof(IEnumerable)): // Enumerable as array of objects
                    this.WriteArray((IEnumerable) propertyValue, writer, specVersion);
                    break;

                case TypeInfo typeInfo                              // object
                    when typeInfo.IsAssignableFrom(typeof(object)): // Object
                default:
                    this.Write(propertyValue, writer, specVersion); // recursive call
                    break;
            }
        }

        writer.WriteEndObject();
    }
}

ISchemaFilter示例转换到Swashbakle 5.0以便遵守相应的序列化规则的正确方法是什么?

推荐答案

他们有一个关于回购的示例:
https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/9bb9be9b318c576d236152f142aafa8c860fb946/test/WebSites/Basic/Swagger/ExamplesSchemaFilter.cs#L8

public class ExamplesSchemaFilter : ISchemaFilter
{
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        schema.Example = GetExampleOrNullFor(context.Type);
    }

    private IOpenApiAny GetExampleOrNullFor(Type type)
    {
        switch (type.Name)
        {
            case "Product":
                return new OpenApiObject
                {
                    [ "id" ] = new OpenApiInteger(123),
                    [ "description" ] = new OpenApiString("foobar"),
                    [ "price" ] = new OpenApiDouble(14.37)
                };
            default:
                return null;
        }
    }
}

这篇关于将IShemaFilter示例过渡到Swashbakle 5.0的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆