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スキーマの生成時に適切にシリアル化します。
ただし、.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
メソッドが呼び出される前でさえ。以下のコードが完全に正しいとは私は主張しませんが、少なくともパスを照らし、前進する方法を期待しています。
_/// <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
の例を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;
}
}
}