在Swashbuckle 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 Schema时将正确序列化。

但是,随着迁移到.NET Core 3和Swashbuckle 5,Schema.Example属性不再是object,而是需要类型Microsoft.OpenApi.Any.IOpenApiAny。关于如何提供新示例,似乎没有记录的前进之路。

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

/// <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();
    }
}

将ojit_code示例转换为Swashbuckle 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;
        }
    }
}

关于c# - 将ISchemaFilter示例转换为Swashbuckle 5.0,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/60515825/

10-10 16:50