Я использую Swashbuckle с ядром ASP.net. Он производит хороший сайт со списком моделей внизу.

enter image description here

Как я могу добавить модель в этот список, который еще не появляется?

Я возвращаю абстрактный класс в одном из своих запросов и хочу перечислить все варианты, которые наследуют этот абстрактный класс.

Заранее спасибо

19
chris31389 27 Фев 2018 в 13:09

4 ответа

Лучший ответ

Вы можете создать фильтр документов и зарегистрировать его глобально.

public class CustomModelDocumentFilter<T> : IDocumentFilter where T : class
{
    public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
    {
        context.SchemaRegistry.GetOrRegister(typeof(T));
    }
}

А затем зарегистрируйте его в своем классе Startup.

services.AddSwaggerGen(options =>
{
    ...
    options.DocumentFilter<CustomModelDocumentFilter<MyCustomModel>>();
    options.DocumentFilter<CustomModelDocumentFilter<MyOtherModel>>();
    ...
}

Для полиморфного класса вы можете использовать их для фильтрации (слегка улучшенные версии этого ответа).

public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
    public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
    {
        RegisterSubClasses(context.SchemaRegistry, typeof(T));
    }

    private static void RegisterSubClasses(ISchemaRegistry schemaRegistry, Type abstractType)
    {
        const string discriminatorName = "$type";

        string friendlyId = abstractType.FriendlyId();
        if (!schemaRegistry.Definitions.TryGetValue(friendlyId, out Schema parentSchema))
            parentSchema = schemaRegistry.GetOrRegister(abstractType);

        // set up a discriminator property (it must be required)
        parentSchema.Discriminator = discriminatorName;
        parentSchema.Required = new List<string> { discriminatorName };

        if (parentSchema.Properties == null)
            parentSchema.Properties = new Dictionary<string, Schema>();

        if (!parentSchema.Properties.ContainsKey(discriminatorName))
            parentSchema.Properties.Add(discriminatorName, new Schema { Type = "string", Default = abstractType.FullName });

        // register all subclasses
        var derivedTypes = abstractType.GetTypeInfo().Assembly.GetTypes()
            .Where(x => abstractType != x && abstractType.IsAssignableFrom(x));

        foreach (var item in derivedTypes)
            schemaRegistry.GetOrRegister(item);
    }
}

public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
    private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);

    public void Apply(Schema schema, SchemaFilterContext context)
    {
        if (!derivedTypes.Value.Contains(context.SystemType)) return;

        var type = context.SystemType;
        var clonedSchema = new Schema
        {
            Properties = schema.Properties,
            Type = schema.Type,
            Required = schema.Required
        };

        // schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in Swashbuckle.AspNetCore
        var parentSchema = new Schema { Ref = "#/definitions/" + typeof(T).Name };

        var assemblyName = Assembly.GetAssembly(type).GetName();
        schema.Discriminator = "$type";
        // This is required if you use Microsoft's AutoRest client to generate the JavaScript/TypeScript models
        schema.Extensions.Add("x-ms-discriminator-value", $"{type.FullName}, {assemblyName.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>();
    }

    private static HashSet<Type> Init()
    {
        var abstractType = typeof(T);
        var dTypes = abstractType.GetTypeInfo().Assembly
            .GetTypes()
            .Where(x => abstractType != x && abstractType.IsAssignableFrom(x));

        var result = new HashSet<Type>();

        foreach (var item in dTypes)
            result.Add(item);

        return result;
    }
}

Требуются два фильтра. Первый добавит все ваши доставленные классы в схему. Он также добавляет свойства, которые не существуют в базовом классе, в схему производного типа.

Второй фильтр добавляет некоторые свойства ($type для сериализации, когда модель возвращается) и расширения (для клиента / генератора Microsoft AutoRest), а также добавление свойств allOf к схеме Swagger, которые необходимы для создать схему наследования при генерации с помощью swagger-gen или AutoRest.

Регистрация аналогична, просто нужно зарегистрировать их попарно (требуется только регистрация базового класса)

// The following lines add polymorphism to the swagger.json schema, so that
// code generators can create properly inheritance hierarchies.
options.DocumentFilter<PolymorphismDocumentFilter<BaseClass>>();
options.SchemaFilter<PolymorphismSchemaFilter<BaseClass>>();

Обновление для ASP.NET Core 3 и Swashbuckle.AspNetCore 5.0

public class CustomModelDocumentFilter<T> : IDocumentFilter where T : class
{
    public void Apply(OpenApiDocument openapiDoc, DocumentFilterContext context)
    {
        context.SchemaGenerator.GenerateSchema(typeof(T), context.SchemaRepository);
    }
}

PolymorphismDocumentFilter / PolymorphismSchemaFilter обновлено для Swashbuckle.AspNetCore 5.0

public class PolymorphismDocumentFilter<T> : IDocumentFilter
{
    public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context)
    {
        RegisterSubClasses(context, typeof(T));
    }

    private static void RegisterSubClasses(DocumentFilterContext context, Type abstractType)
    {
        const string discriminatorName = "$type";
        var schemaRepository = context.SchemaRepository.Schemas;
        var schemaGenerator = context.SchemaGenerator;

        if (!schemaRepository.TryGetValue(abstractType.Name, out OpenApiSchema parentSchema))
        {
            parentSchema = schemaGenerator.GenerateSchema(abstractType, context.SchemaRepository);
        }

        // set up a discriminator property (it must be required)
        parentSchema.Discriminator = new OpenApiDiscriminator { PropertyName = discriminatorName };
        parentSchema.Required.Add(discriminatorName);

        if (!parentSchema.Properties.ContainsKey(discriminatorName))
            parentSchema.Properties.Add(discriminatorName, new OpenApiSchema { Type = "string", Default = new OpenApiString(abstractType.FullName) });

        // register all subclasses
        var derivedTypes = abstractType.GetTypeInfo().Assembly.GetTypes()
            .Where(x => abstractType != x && abstractType.IsAssignableFrom(x));

        foreach (var type in derivedTypes)
            schemaGenerator.GenerateSchema(type, context.SchemaRepository);
    }
}

И

public class PolymorphismSchemaFilter<T> : ISchemaFilter
{
    private readonly Lazy<HashSet<Type>> derivedTypes = new Lazy<HashSet<Type>>(Init);

    public void Apply(OpenApiSchema schema, SchemaFilterContext context)
    {
        var type = context.ApiModel.Type;
        if (!derivedTypes.Value.Contains(type))
            return;

        var clonedSchema = new OpenApiSchema
        {
            Properties = schema.Properties,
            Type = schema.Type,
            Required = schema.Required
        };

        // schemaRegistry.Definitions[typeof(T).Name]; does not work correctly in SwashBuckle
        if(context.SchemaRepository.Schemas.TryGetValue(typeof(T).Name, out OpenApiSchema _))
        {
            schema.AllOf = new List<OpenApiSchema> {
                new OpenApiSchema { Reference = new OpenApiReference { Id = typeof(T).Name, Type = ReferenceType.Schema } },
                clonedSchema
            };
        }

        var assemblyName = Assembly.GetAssembly(type).GetName();
        schema.Discriminator = new OpenApiDiscriminator { PropertyName = "$type" };
        schema.AddExtension("x-ms-discriminator-value", new OpenApiString($"{type.FullName}, {assemblyName.Name}"));

        // reset properties for they are included in allOf, should be null but code does not handle it
        schema.Properties = new Dictionary<string, OpenApiSchema>();
    }

    private static HashSet<Type> Init()
    {
        var abstractType = typeof(T);
        var dTypes = abstractType.GetTypeInfo().Assembly
            .GetTypes()
            .Where(x => abstractType != x && abstractType.IsAssignableFrom(x));

        var result = new HashSet<Type>();

        foreach (var item in dTypes)
            result.Add(item);

        return result;
    }
}
20
Tseng 2 Дек 2019 в 11:07

Возможно, не самое чистое решение, но я достиг того же самого, установив атрибут ProducesResponseType над моим контроллером:

[ProducesResponseType(typeof(object), 200)]
public class FileController : Controller
{

Где вы заменяете объект на объект, который вы хотите отобразить в модели, и создаете новую строку для каждого дополнительного. Просто убедитесь, что вы используете разные коды состояния для каждого из них, иначе он будет отображать только последний.

2
404 27 Фев 2018 в 11:54

Оглядываясь назад, другой ответ, который я нашел ниже (и на других страницах), был лучше, а именно добавить этот вид атрибута:

[HttpGet("")]
[ProducesResponseType(typeof(MyResult), (int)System.Net.HttpStatusCode.OK)]
[ProducesResponseType(typeof(ErrorBase), (int)System.Net.HttpStatusCode.NotFound)]
... function definition ...

Почему: потому что функция предоставляет различные типы объектов на основе различных ситуаций, и с помощью этих атрибутов вы можете указать, какие объекты возвращаются в какой ситуации.

Например я могу вернуть

return Ok(myresult)

Или

return NotFound(myerror)

В моей функции, основанной на том, нашел ли я результат или нет.

0
Dawood Awan 30 Июл 2019 в 09:42

У меня была та же проблема, когда мои модели не показывались в чванстве, потому что типы возвращаемых данных моих функций имели абстрактный тип. Я изменил ответ выше, чтобы я мог выкинуть все из пространства имен в список моделей.

В Startup я определяю эту функцию:

public class GenericAPI_DocumentFilter<T> : IDocumentFilter where T : class
{

    public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
    {

        foreach (var t in Assembly.GetExecutingAssembly().GetTypes())
        {
            if (t.Namespace.Contains("MyAPI") && t.IsClass)
            {

                var a = t.GetCustomAttribute(typeof(DataContractAttribute));
                if (a != null)
                {
                    context.SchemaRegistry.GetOrRegister(t);
                }

            }
        }

    }
}

В инициализации swagger я добавляю одну строку:

services.AddSwaggerGen(opt =>
            {
...
opt.DocumentFilter<GenericAPI_DocumentFilter<object>>();
...
}
1
HMD 29 Июл 2019 в 15:03