用实栗谈一谈Swagger中的SchemaFilter

1. ISchemaFilter 的作用

ISchemaFilter 是一个接口,主要用于拦截并修改自动生成的 Swagger Schema(在 OpenAPI 3.0 中通常指 OpenApiSchema)的定义。它能够帮助我们在以下场景中使用:

  1. 为某些属性附加额外的注释或元数据
    • 在自动生成的 Schema 中可能会缺少一些与业务相关的描述、或需要为某些属性配置额外的信息(如格式化规则、示例值、只读/只写标记等)。
  2. 动态地隐藏或修改属性
    • 某些属性不希望被公开给第三方使用,或者需要根据特定条件隐藏属性。
  3. 对一些特殊类型进行处理
    • 比如针对 DateTime、自定义类型等,注入更多描述(format、example、pattern 等)。
  4. 对一些复杂类型做深度定制
    • 假设你有一个包含子类或泛型集合的复杂对象,需要对子类型也进行特别处理时,就可以在此扩展。

简而言之,ISchemaFilter 能让我们在生成 Swagger 文档时,对每一个类型的 Schema 进行细粒度的定制

2. 举个栗子

接下来,我们结合实际的使用场景举一个小栗子:

在一个前后端分离的项目中,后端的ID是用雪花算法生成的long类型,长度比较长,可能会超过了前端的Number类型的最大长度,有可能会导致前端丢失精度的问题。解决方案也很简单,只要Json序列化的时候,将long和string进行转换,前端用string类型,后端还是long类型。

  • 第一步: 写一个JsonConverter,这里就不赘述了,反正就是Json序列化的时候,Read的时候返回long,Write的时候写成string(如果不用Swagger的话,这里就结束了~);
  • 第二步: 定义一个SnowflakeIdAttribute, 用来标注那些是雪花ID。当然了,这里也可以顺带做一点验证;
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)]  
public class SnowflakeIdAttribute : ValidationAttribute  
{  
    protected override ValidationResult? IsValid(object? value, ValidationContext? validationContext)  
    {        
	    var displayName = validationContext?.DisplayName ?? "该字段";  
        var memberName = validationContext?.MemberName ?? displayName;  
        return value switch  
        {  
            null or long or IEnumerable<long> => ValidationResult.Success,  
            string strValue => long.TryParse(strValue, out _)  
                ? ValidationResult.Success  
                : new ValidationResult($"{displayName} 必须是可转换为 long 类型的字符串。", [memberName]),  
            IEnumerable<string> strArray => strArray.Any(str => !long.TryParse(str, out _))  
                ? new ValidationResult($"{displayName} 中的所有字符串必须是可转换为 long 类型。", [memberName])  
                : ValidationResult.Success,  
            _ => new ValidationResult($"{displayName} 必须是 string 类型或 long 类型。", [memberName])  
        };    
    }
}
  • 第三步: 定义一个SnowflakeIdSchemaFilter, 集成ISchemaFilter, 实现Swagger中string和long的转换;
public class SnowflakeIdSchemaFilter : ISchemaFilter  
{  
    public void Apply(OpenApiSchema schema, SchemaFilterContext context)  
    {        
	    foreach (var property in context.Type.GetProperties())  
        {            
	        var attrs = property.GetCustomAttributes(typeof(SnowflakeIdAttribute), true);  
            if (attrs.Any())  
            {                
	            // Convert property name to camel case  
                var propertyName = Utils.ToCamelCase(property.Name);  
                if (schema.Properties.ContainsKey(propertyName))  
                {                    
	                var propSchema = schema.Properties[propertyName];  
  
                    if (IsEnumerableOfLong(property.PropertyType))  
                    {                        
	                    // If the property is IEnumerable<long>, change the item type to IENumerable<string>  
                        propSchema.Type = "array";  
                        propSchema.Items = new OpenApiSchema  
                        {  
                            Type = "string",  
                            Example = new OpenApiString("snowflakeId")  
                        };                    }                    
                    else if (property.PropertyType == typeof(long) || property.PropertyType == typeof(long?))  
                    {                        
	                    // For single long type properties  
                        propSchema.Type = "string";  
                        propSchema.Example = new OpenApiString("snowflakeId");  
                    }                
                }            
            }        
        }    
    }
    
    private static bool IsEnumerableOfLong(Type type)  
    {        
	    if (type.IsGenericType && typeof(IEnumerable<>).IsAssignableFrom(type.GetGenericTypeDefinition()))  
        {            
	        var itemType = type.GetGenericArguments()[0];  
            return itemType == typeof(long) || itemType == typeof(long?);  
        }  
        return false;  
    }
}
  • 第四步: 注册一下这个过滤器:
builder.Services.AddSwaggerGen(options =>  
{
	options.SchemaFilter<SnowflakeIdSchemaFilter>();
}
  • 第五步: 验证一下
public class DemoDto  
{  
    /// <summary>  
    /// 雪花ID  
    /// </summary>    
    [Required]  
    [SnowflakeId]
    [JsonConverter(typeof(SnowflakeIdConverter))]
    public long? Id { get; set; }  
  
    /// <summary>  
    /// 雪花ID列表  
    /// </summary>  
    [SnowflakeId]
    [JsonConverter(typeof(SnowflakeIdsConverter))]
    public IEnumerable<long> Ids { get; set; } = [];   
}
testApi.MapPost("/test",  
    [SwaggerOperation(Summary = "测试例子")] (DemoDto dto) =>  
    $"from body: IdStr: {dto.Id}, IdStrList: {string.Join(',', dto.Ids ?? [])}");

3. 小结

  • ISchemaFilter 接口主要用于在生成 Swagger 文档时,自定义或修改自动生成的 Schema(即每个模型/实体的定义)。
  • 可以应用在多种场景中:为属性添加描述、示例,隐藏属性,为特定类型添加或修改自定义信息等。
  • 只需要在自己的项目中实现 ISchemaFilter 接口,并在 AddSwaggerGen 时通过 c.SchemaFilter<T>() 进行注册即可。

通过灵活地使用 ISchemaFilter,我们可以使 Swagger 文档更加清晰、准确地反映业务需求,为前后端或第三方对接带来更好的体验。

posted on   God写注释没有代码  阅读(45)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示