Swashbuckle Swagger - извлечение информации из атрибутов и помещение ее в определение схемы - PullRequest
0 голосов
/ 13 июня 2018

Я пытаюсь получить атрибут DisplayAttribute и DescriptionAttribute из частей модели Swagger.Например, у меня может быть параметр Body, у которого есть свойства с атрибутами, которые я также хотел бы сгенерировать в swagger.json и увидеть в SwaggerUI.

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

Проблемы:

Не удается найти ключ для schemaRegistry для некоторыхтипы, как список.

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

Может потребоваться рекурсия для циклического обхода дочерних свойств, содержащих сложные объекты.

public class SwaggerParameterFilter : IParameterFilter
{
    private SchemaRegistrySettings _settings;
    private SchemaIdManager _schemaIdManager;

    public SwaggerParameterFilter(SchemaRegistrySettings settings = null)
    {

        this._settings = settings ?? new SchemaRegistrySettings();
        this._schemaIdManager = new SchemaIdManager(this._settings.SchemaIdSelector);
    }


    public void Apply(IParameter parameter, ParameterFilterContext context)
    {
        try
        {


            if (context.ApiParameterDescription?.ModelMetadata?.Properties == null) return;
            if (parameter is BodyParameter bodyParameter)
            {
                string idFor = _schemaIdManager.IdFor(context.ApiParameterDescription.Type);
                var schemaRegistry = (SchemaRegistry)context.SchemaRegistry;
                //not perfect, crashes with some cases
                var schema = schemaRegistry.Definitions[idFor];
                //bodyParameter.Schema,  this doesn't seem right, no properties
                foreach (var modelMetadata in context.ApiParameterDescription.ModelMetadata.Properties)
                {

                    if (modelMetadata is DefaultModelMetadata defaultModelMetadata)
                    {

                        //not sure right now how to get the right key for the schema.Properties...
                        var name = defaultModelMetadata.Name;
                        name = Char.ToLowerInvariant(name[0]) + name.Substring(1);

                        if (schema.Properties.ContainsKey(name))
                        {
                            var subSchema = schema.Properties[name];
                            var attributes = defaultModelMetadata.Attributes.Attributes.Select(x => (Attribute)x);
                            var descriptionAttribute = (DescriptionAttribute)attributes.FirstOrDefault(x => x is DescriptionAttribute);
                            if (descriptionAttribute != null)
                                subSchema.Description = descriptionAttribute.Description;
                        }
                    }
                }
            }
        }
        catch (Exception e)
        {
            //eat because above is broken
        }
    }


}

Редактировать, добавить цикл.

public class SwaggerParameterFilter : IParameterFilter
{
    private SchemaRegistrySettings _settings;
    private SchemaIdManager _schemaIdManager;

    public SwaggerParameterFilter(SchemaRegistrySettings settings = null)
    {

        this._settings = settings ?? new SchemaRegistrySettings();
        this._schemaIdManager = new SchemaIdManager(this._settings.SchemaIdSelector);
    }


    public void Apply(IParameter parameter, ParameterFilterContext context)
    {
        try
        {
            if (context.ApiParameterDescription?.ModelMetadata?.Properties == null) return;
            //Only BodyParameters are complex and stored in the schema
            if (parameter is BodyParameter bodyParameter)
            {
                var idFor = _schemaIdManager.IdFor(context.ApiParameterDescription.Type);
                //not perfect, crashes with some cases
                var schema = context.SchemaRegistry.Definitions[idFor];
                UpdateSchema(schema, (SchemaRegistry) context.SchemaRegistry, context.ApiParameterDescription.ModelMetadata);
            }
        }
        catch (Exception e)
        {
            //eat because above is broken
        }
    }

    private void UpdateSchema(Schema schema, SchemaRegistry schemaRegistry, ModelMetadata modelMetadata)
    {
        if (schema.Ref != null)
        {
            var schemaReference = schema.Ref.Replace("#/definitions/", "");
            UpdateSchema(schemaRegistry.Definitions[schemaReference], schemaRegistry, modelMetadata);
            return;
        }

        if (schema.Properties == null) return;
        foreach (var properties in modelMetadata.Properties)
        {

            if (properties is DefaultModelMetadata defaultModelMetadata)
            {
                //not sure right now how to get the right key for the schema.Properties...
                var name = defaultModelMetadata.Name;
                name = Char.ToLowerInvariant(name[0]) + name.Substring(1);
                if (schema.Properties.ContainsKey(name) == false) return;
                var subSchema = schema.Properties[name];
                var attributes = defaultModelMetadata.Attributes.Attributes.Select(x => (Attribute) x).ToList();
                var descriptionAttribute =
                    (DescriptionAttribute) attributes.FirstOrDefault(x => x is DescriptionAttribute);
                if (descriptionAttribute != null)
                    subSchema.Description = descriptionAttribute.Description;
                var displayAttribute = (DisplayAttribute) attributes.FirstOrDefault(x => x is DisplayAttribute);
                if (displayAttribute != null)
                    subSchema.Title = displayAttribute.Name;
                if (modelMetadata.ModelType.IsPrimitive) return;
                UpdateSchema(subSchema, schemaRegistry, defaultModelMetadata);
            }
        }
    }
}

Операция Filter

public class SwaggerOperationFilter : IOperationFilter
{
    private SchemaRegistrySettings _settings;
    private SchemaIdManager _schemaIdManager;
    private IModelMetadataProvider _metadataProvider;

    public SwaggerOperationFilter(IModelMetadataProvider metadataProvider, SchemaRegistrySettings settings = null)
    {
        this._metadataProvider = metadataProvider;
        this._settings = settings ?? new SchemaRegistrySettings();
        this._schemaIdManager = new SchemaIdManager(this._settings.SchemaIdSelector);
    }
    public void Apply(Operation operation, OperationFilterContext context)
    {
        try
        {
            foreach (var paramDescription in context.ApiDescription.ParameterDescriptions)
            {
                if (paramDescription?.ModelMetadata?.Properties == null)
                {
                    continue;
                }

                if (paramDescription.ModelMetadata.ModelType.IsPrimitive)
                {
                    continue;
                }

                if (paramDescription.ModelMetadata.ModelType == typeof(string))
                {
                    continue;
                }
                var idFor = _schemaIdManager.IdFor(paramDescription.Type);
                var schema = context.SchemaRegistry.Definitions[idFor];
                UpdateSchema(schema, (SchemaRegistry)context.SchemaRegistry, paramDescription.ModelMetadata);
            }

        }
        catch (Exception e)
        {
            //eat because above is broken
        }
    }
    private void UpdateSchema(Schema schema, SchemaRegistry schemaRegistry, ModelMetadata modelMetadata)
    {

            if (schema.Ref != null)
            {
                var schemaReference = schema.Ref.Replace("#/definitions/", "");
                UpdateSchema(schemaRegistry.Definitions[schemaReference], schemaRegistry, modelMetadata);
                return;
            }

            if (schema.Type == "array")
            {
                if (schema.Items.Ref != null)
                {
                    var schemaReference = schema.Items.Ref.Replace("#/definitions/", "");
                    var modelTypeGenericTypeArgument = modelMetadata.ModelType.GenericTypeArguments[0];

                    modelMetadata = _metadataProvider.GetMetadataForType(modelTypeGenericTypeArgument);
                    UpdateSchema(schemaRegistry.Definitions[schemaReference], schemaRegistry, modelMetadata);

                }
                return;
            }
            if (schema.Properties == null) return;
            foreach (var properties in modelMetadata.Properties)
            {

                if (properties is DefaultModelMetadata defaultModelMetadata)
                {

                    //not sure right now how to get the right key for the schema.Properties...
                    var name = defaultModelMetadata.Name;
                    name = Char.ToLowerInvariant(name[0]) + name.Substring(1);
                    if (schema.Properties.ContainsKey(name) == false) return;
                    var subSchema = schema.Properties[name];
                    var attributes = defaultModelMetadata.Attributes.Attributes.Select(x => (Attribute)x).ToList();
                    var descriptionAttribute =
                        (DescriptionAttribute)attributes.FirstOrDefault(x => x is DescriptionAttribute);
                    if (descriptionAttribute != null)
                        subSchema.Description = descriptionAttribute.Description;
                    var displayAttribute = (DisplayAttribute)attributes.FirstOrDefault(x => x is DisplayAttribute);
                    if (displayAttribute != null)
                        subSchema.Title = displayAttribute.Name;
                    if (defaultModelMetadata.ModelType.IsPrimitive) return;
                    UpdateSchema(subSchema, schemaRegistry, defaultModelMetadata);
                }
            }
    }
}

1 Ответ

0 голосов
/ 13 июня 2018

Так что после некоторого устранения неполадок это, кажется, работает для меня, но может потребоваться модификация для других случаев.

public class SwashbuckleAttributeReaderDocumentFilter : IDocumentFilter
{
    private readonly SchemaIdManager _schemaIdManager;
    private readonly IModelMetadataProvider _metadataProvider;
    private List<string> _updatedSchemeList;
    public SwashbuckleAttributeReaderDocumentFilter(IModelMetadataProvider metadataProvider, SchemaRegistrySettings settings = null)
    {
        _metadataProvider = metadataProvider;
        var registrySettings = settings ?? new SchemaRegistrySettings();
        _schemaIdManager = new SchemaIdManager(registrySettings.SchemaIdSelector);         
    }
    public void Apply(SwaggerDocument swaggerDoc, DocumentFilterContext context)
    {   
        _updatedSchemeList = new List<string>();
        foreach (var apiDescription in context.ApiDescriptions)
        {
            foreach (var responseTypes in apiDescription.SupportedResponseTypes)
            {
                ProcessModelMetadata(context, responseTypes.ModelMetadata);
            }
            foreach (var paramDescription in apiDescription.ParameterDescriptions)
            {
                ProcessModelMetadata(context, paramDescription.ModelMetadata);
            }
        }

    }

    private void ProcessModelMetadata(DocumentFilterContext context, ModelMetadata currentModelMetadata)
    {

        if (currentModelMetadata?.Properties == null)
        {
            return;
        }

        if (currentModelMetadata.ModelType.IsValueType)
        {
            return;
        }

        if (currentModelMetadata.ModelType == typeof(string))
        {
            return;
        }

        if (currentModelMetadata.ModelType.IsGenericType)
        {
            foreach (var modelType in currentModelMetadata.ModelType.GenericTypeArguments)
            {
                var modelMetadata = _metadataProvider.GetMetadataForType(modelType);
                UpdateSchema(context.SchemaRegistry, modelMetadata);
            }
        }
        else if (currentModelMetadata.IsCollectionType)
        {
            var modelType = currentModelMetadata.ModelType.GetElementType();
            var modelMetadata = _metadataProvider.GetMetadataForType(modelType);
            UpdateSchema(context.SchemaRegistry, modelMetadata);

        }
        else
        {

            UpdateSchema(context.SchemaRegistry, currentModelMetadata);

        }
    }
    public static void SetSchema(Schema schema, ModelMetadata modelMetadata)
    {
        if (!(modelMetadata is DefaultModelMetadata metadata)) return;
        var attributes = GetAtributes(metadata);
        SetDescription(attributes, schema);
        SetTitle(attributes, schema);
    }

    private static List<Attribute> GetAtributes(DefaultModelMetadata modelMetadata)
    {
        return modelMetadata.Attributes.Attributes.Select(x => (Attribute)x).ToList();
    }

    private static void SetTitle(List<Attribute> attributes, Schema schema)
    {
        //LastOrDefault because we want the attribute from the dervived class.
        var displayAttribute = (DisplayNameAttribute)attributes.LastOrDefault(x => x is DisplayNameAttribute);
        if (displayAttribute != null)
            schema.Title = displayAttribute.DisplayName;
    }

    private static void SetDescription(List<Attribute> attributes, Schema schema)
    {
        //LastOrDefault because we want the attribute from the dervived class. not sure if this works.
        var descriptionAttribute = (DescriptionAttribute)attributes.LastOrDefault(x => x is DescriptionAttribute);
        if (descriptionAttribute != null)
            schema.Description = descriptionAttribute.Description;
    }

    private void UpdateSchema(ISchemaRegistry schemaRegistry, ModelMetadata modelMetadata, Schema schema = null)
    {
        if (modelMetadata.ModelType.IsValueType) return;
        if (modelMetadata.ModelType == typeof(string)) return;
        var idFor = _schemaIdManager.IdFor(modelMetadata.ModelType);
        if (_updatedSchemeList.Contains(idFor))
            return;
        if (schema == null || schema.Ref != null)
        {
            if (schemaRegistry.Definitions.ContainsKey(idFor) == false) return;
            schema = schemaRegistry.Definitions[idFor];
        }

        _updatedSchemeList.Add(idFor);
        SetSchema(schema, modelMetadata);
        if (schema.Type == "array")//Array Schema
        {
            var metaData = _metadataProvider.GetMetadataForType(modelMetadata.ModelType.GenericTypeArguments[0]);
            UpdateSchema(schemaRegistry, metaData);
        }
        else//object schema
        {
            if (schema.Properties == null)
            {
                return;
            }
            foreach (var properties in modelMetadata.Properties)
            {
                if (!(properties is DefaultModelMetadata defaultModelMetadata))
                {
                    continue;
                }
                var name = ToLowerCamelCase(defaultModelMetadata.Name);
                if (schema.Properties.ContainsKey(name) == false)
                {
                    //when this doesn't match the json object naming.
                    return;
                }
                var subSchema = schema.Properties[name];
                SetSchema(subSchema, defaultModelMetadata);

                UpdateSchema(schemaRegistry, defaultModelMetadata, subSchema);
            }
        }
    }

    private static string ToLowerCamelCase(string inputString)
    {
        if (inputString == null) return null;
        if (inputString == string.Empty) return string.Empty;
        if (char.IsLower(inputString[0])) return inputString;
        return inputString.Substring(0, 1).ToLower() + inputString.Substring(1);
    }
}
...