如何全局为所有控制器接口返回的JSON追加Schema?
嘿,这个需求我太懂了——谁想挨个控制器去改接口返回格式啊!完全可以通过全局配置实现,不用碰每个控制器的代码。下面给你两种靠谱的方案,都是ASP.NET Core官方推荐的扩展方式:
方案一:自定义全局Action Filter(最推荐,灵活可控)
Action Filter是框架提供的标准扩展点,能在接口返回结果执行前拦截并修改,还能轻松拿到返回类型的元数据,完美解决你之前拿不到返回类型属性的问题。
步骤1:写自定义Filter
创建一个继承自ActionFilterAttribute的类,重写OnResultExecuting方法,在这里包装返回值并添加Schema:
public class AddResponseSchemaFilter : ActionFilterAttribute { public override void OnResultExecuting(ResultExecutingContext context) { // 只处理返回JSON的ObjectResult(大部分接口都是这种) if (context.Result is ObjectResult objectResult) { // 拿到接口声明的返回类型,比直接取Value的类型更准确(比如泛型场景) var returnType = objectResult.DeclaredType ?? objectResult.Value?.GetType(); // 生成你需要的Schema(这里写个简单示例,你可以按需求扩展) var responseSchema = GenerateCustomSchema(returnType); // 包装原返回值,改成你要的格式 var wrappedResponse = new { Data = objectResult.Value, Schema = responseSchema }; // 替换原结果,保留原状态码和内容类型 context.Result = new ObjectResult(wrappedResponse) { StatusCode = objectResult.StatusCode, ContentTypes = objectResult.ContentTypes }; } base.OnResultExecuting(context); } // 这里实现你的Schema生成逻辑,比如通过反射读取属性信息 private object GenerateCustomSchema(Type returnType) { if (returnType == null) return null; // 示例:返回属性名和对应类型的字典 return returnType.GetProperties() .ToDictionary( prop => prop.Name, prop => prop.PropertyType.Name ); } }
步骤2:全局注册Filter
在Program.cs(.NET 6+)或者Startup.cs(.NET 5及更早)里把这个Filter注册成全局的,所有接口都会自动生效:
// .NET 6+ 写法 builder.Services.AddControllers(options => { // 添加全局Filter options.Filters.Add<AddResponseSchemaFilter>(); }); // .NET 5及更早 写法 services.AddControllers(options => { options.Filters.Add<AddResponseSchemaFilter>(); });
方案二:自定义JSON Output Formatter(底层控制)
如果需要更底层的JSON输出控制,可以自定义JsonOutputFormatter,它会拦截所有JSON格式的输出,适合统一修改JSON结构的场景。
步骤1:写自定义Formatter
public class SchemaWrappedJsonFormatter : SystemTextJsonOutputFormatter { public SchemaWrappedJsonFormatter(JsonSerializerOptions options) : base(options) { } public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) { var originalValue = context.Object; // 同样拿到声明的返回类型 var returnType = context.DeclaredType ?? originalValue?.GetType(); var schema = GenerateCustomSchema(returnType); // 包装原数据 var wrappedValue = new { Data = originalValue, Schema = schema }; // 替换原输出对象,再调用原生逻辑输出 context.Object = wrappedValue; await base.WriteResponseBodyAsync(context, selectedEncoding); } private object GenerateCustomSchema(Type returnType) { // 和上面的Schema生成逻辑一致,按需扩展 if (returnType == null) return null; return returnType.GetProperties() .ToDictionary( prop => prop.Name, prop => prop.PropertyType.Name ); } }
步骤2:替换默认JSON Formatter
在配置里移除原生的JSON Formatter,换成我们自定义的:
// .NET 6+ 写法 builder.Services.AddControllers(options => { // 找到原生的JSON Formatter var originalJsonFormatter = options.OutputFormatters.OfType<SystemTextJsonOutputFormatter>().FirstOrDefault(); if (originalJsonFormatter != null) { options.OutputFormatters.Remove(originalJsonFormatter); // 添加自定义Formatter,复用原生的序列化配置 options.OutputFormatters.Add(new SchemaWrappedJsonFormatter(originalJsonFormatter.SerializerOptions)); } });
补充说明
- 为什么之前用
WriteResponseBodyAsync不行?因为直接操作这个方法时,上下文里的元数据(比如DeclaredType)可能还没初始化完全,而Action Filter和Output Formatter是框架设计好的扩展点,能可靠拿到返回类型信息。 - 如果有个别接口不需要加Schema,可以给Filter加个“排除特性”,比如自定义
SkipSchemaAttribute,然后在Filter里判断Action是否带有这个特性,是的话就跳过处理。 - Schema生成逻辑可以灵活扩展,比如你如果用了Swashbuckle(Swagger),可以直接复用它的Schema生成器来生成标准的JSON Schema,不用自己写反射逻辑。
内容的提问来源于stack exchange,提问作者Hari Krishna Gaddipati
相关产品推荐
相关产品推荐

