domaindrivendev / Swashbuckle.AspNetCore

Swagger tools for documenting API's built on ASP.NET Core
MIT License
5.24k stars 1.31k forks source link

[Bug]: In the `Apply` method of the `IOperationFilter` interface, all schemas in `operation.RequestBody.Content` are either `null` or have a `Count` of `0`. #3017

Open MuQuanyu opened 2 months ago

MuQuanyu commented 2 months ago

Describe the bug

In the Apply method of the IOperationFilter interface, all schemas in operation.RequestBody.Content are either null or have a Count of 0.I've tested a lot of things like public string testiges noreparams ([FromBody] Person person) { return $"{person.Id},{person.Name},{person.Gender},{person.Age}"; Such an interface has also changed the entity class type of many request bodies, but it is still in the filter and cannot get those parameters.

Expected behavior

Since I want to filter out some of the attributes in the request body that are generated on swagger, to implement this also annotate, then I have to take advantage of your filter! This is to implement the Apply method in the IOperationFilter interface. But I can't get the Schema of the request body, which makes me unable to implement this feature, I can only use reflection mechanism to enforce the implementation.

Actual behavior

What actually happens is that you don't get it. And I also enforced the use of reflection to fulfill my needs. The code snippet is attached.

Steps to reproduce

No response

Exception(s) (if any)

No response

Swashbuckle.AspNetCore version

6.7.0

.NET Version

net6.0

Anything else?

using Microsoft.OpenApi.Models; using Swashbuckle.AspNetCore.SwaggerGen; using System.Reflection; using Microsoft.AspNetCore.Mvc;

namespace WebApplication2.Attributes.Filter { public class IgnoreParamsOperationFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { var ignoreParamsAttribute = context.MethodInfo .GetCustomAttributes(true) .OfType() .FirstOrDefault();

        if (ignoreParamsAttribute != null && operation.RequestBody != null)
        {
            foreach (var content in operation.RequestBody.Content)
            {
                var schema = content.Value.Schema;

                // 动态生成 Schema,如果它为空或未初始化
                if (schema == null || schema.Properties.Count == 0)
                {
                    var parameterType = context.MethodInfo.GetParameters()
                        .FirstOrDefault(p => p.GetCustomAttribute<FromBodyAttribute>() != null)?.ParameterType;

                    if (parameterType != null)
                    {
                        schema = GenerateSchemaFromType(parameterType, ignoreParamsAttribute.ParamsToIgnore);
                        content.Value.Schema = schema;
                    }
                }
            }
        }
    }

    private OpenApiSchema GenerateSchemaFromType(Type type, string[] paramsToIgnore)
    {
        var schema = new OpenApiSchema
        {
            Type = "object",
            Properties = new Dictionary<string, OpenApiSchema>()
        };

        // 使用反射获取所有公共属性并生成 Schema
        var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);

        foreach (var prop in properties)
        {
            if (!paramsToIgnore.Contains(prop.Name))
            {
                var propSchema = new OpenApiSchema
                {
                    Type = GetOpenApiType(prop.PropertyType),
                    Description = GetPropertyDescription(prop),
                    Format = GetOpenApiFormat(prop.PropertyType)
                };

                schema.Properties.Add(Char.ToLowerInvariant(prop.Name[0]) + prop.Name.Substring(1), propSchema);
            }
        }

        return schema;
    }

    private string GetOpenApiType(Type type)
    {
        if (type == typeof(int) || type == typeof(long))
            return "integer";
        if (type == typeof(float) || type == typeof(double) || type == typeof(decimal))
            return "number";
        if (type == typeof(bool))
            return "boolean";
        if (type == typeof(string))
            return "string";
        if (type.IsArray || (type.IsGenericType && typeof(IEnumerable<>).IsAssignableFrom(type.GetGenericTypeDefinition())))
            return "array";

        return "object"; // 默认类型为 object
    }

    private string GetOpenApiFormat(Type type)
    {
        if (type == typeof(int))
            return "int32";
        if (type == typeof(long))
            return "int64";
        if (type == typeof(float))
            return "float";
        if (type == typeof(double) || type == typeof(decimal))
            return "double";
        if (type == typeof(DateTime))
            return "date-time";

        return null; // 默认没有 format
    }

    private string GetPropertyDescription(PropertyInfo prop)
    {
        // 可以在此扩展,使用注解或其他方式为属性生成描述
        return prop.Name;
    }
}

}

martincostello commented 2 months ago

It may be the case that you're seeing the intermediate state when the document is in the process of being built-up and that the information isn't available yet.

What do you see if you inspect the operations as part of a document filter?

FYI there's information here about when each type of filter runs: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/issues/2993#issuecomment-2245802023

jgarciadelanoceda commented 1 month ago

Seeing the filter that you have created it's very similar to what the SwagerIgnoreAttribute makes under the hood, so you could take a look there.

If it's not the same I need a repro to look at it