Swashbuckle/Swagger错误将路由参数纳入请求体问题解决
解决方案
方案一:自定义Schema过滤器,移除请求体中带[FromRoute]的属性
通过实现ISchemaFilter,在Swashbuckle生成Schema时自动移除请求体中标记为路由来源的属性,同时保留模型绑定所需的属性定义:
public class RemoveRoutePropertiesFromBodySchemaFilter : ISchemaFilter { public void Apply(OpenApiSchema schema, SchemaFilterContext context) { if (context.Type == null) return; // 筛选出所有带[FromRoute]特性的属性 var routeProperties = context.Type.GetProperties() .Where(p => p.GetCustomAttributes(typeof(FromRouteAttribute), inherit: true).Any()); foreach (var prop in routeProperties) { // 转换为Swagger默认的驼峰命名格式 var swaggerPropName = char.ToLowerInvariant(prop.Name[0]) + prop.Name.Substring(1); // 从Schema中移除该属性 if (schema.Properties.ContainsKey(swaggerPropName)) { schema.Properties.Remove(swaggerPropName); // 同步移除必填项标记(如果存在) if (schema.Required.Contains(swaggerPropName)) { schema.Required.Remove(swaggerPropName); } } } } }
在Swashbuckle配置中注册该过滤器:
services.AddSwaggerGen(c => { c.SchemaFilter<RemoveRoutePropertiesFromBodySchemaFilter>(); // 其他Swagger配置... });
方案二:自定义操作过滤器,修正参数位置
如果需要进一步调整参数的显示位置(比如确保CourseId只作为路径参数存在,而非查询参数),可以搭配IOperationFilter修正参数的位置分类:
public class CorrectRouteParameterLocationFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { if (context.ApiDescription.ParameterDescriptions == null) return; foreach (var paramDesc in context.ApiDescription.ParameterDescriptions) { var swaggerParam = operation.Parameters.FirstOrDefault(p => p.Name.Equals(paramDesc.Name, StringComparison.OrdinalIgnoreCase)); if (swaggerParam == null) continue; // 标记路由来源的参数为Path类型 if (paramDesc.Source == BindingSource.Path) { swaggerParam.In = ParameterLocation.Path; swaggerParam.Schema.ReadOnly = true; } // 移除请求体模型中带[FromRoute]属性对应的查询参数 else if (paramDesc.ParameterType.GetProperties().Any(p => p.GetCustomAttributes(typeof(FromRouteAttribute), true).Any())) { var routeProps = paramDesc.ParameterType.GetProperties() .Where(p => p.GetCustomAttributes(typeof(FromRouteAttribute), true).Any()); foreach (var prop in routeProps) { var queryParam = operation.Parameters.FirstOrDefault(p => p.Name.Equals(prop.Name, StringComparison.OrdinalIgnoreCase)); if (queryParam != null) { operation.Parameters.Remove(queryParam); } } } } } }
注册该操作过滤器:
services.AddSwaggerGen(c => { c.SchemaFilter<RemoveRoutePropertiesFromBodySchemaFilter>(); c.OperationFilter<CorrectRouteParameterLocationFilter>(); // 其他Swagger配置... });
方案三:针对[FromComposite]绑定器的特殊处理
如果使用自定义[FromComposite]绑定器导致Swashbuckle误将请求体转为查询参数,可以通过操作过滤器强制将该参数标记为请求体,并清理路由属性:
public class CompositeBindingSourceOperationFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { var compositeParams = context.MethodInfo.GetParameters() .Where(p => p.GetCustomAttributes(typeof(FromCompositeAttribute), inherit: true).Any()); foreach (var param in compositeParams) { // 移除自动生成的查询参数 var queryParamsToRemove = operation.Parameters.Where(p => p.In == ParameterLocation.Query).ToList(); foreach (var qp in queryParamsToRemove) { operation.Parameters.Remove(qp); } // 生成请求体Schema并移除路由属性 var schema = context.SchemaGenerator.GenerateSchema(param.ParameterType, context.SchemaRepository); var routeProps = param.ParameterType.GetProperties() .Where(p => p.GetCustomAttributes(typeof(FromRouteAttribute), true).Any()); foreach (var prop in routeProps) { var swaggerPropName = char.ToLowerInvariant(prop.Name[0]) + prop.Name.Substring(1); if (schema.Properties.ContainsKey(swaggerPropName)) { schema.Properties.Remove(swaggerPropName); if (schema.Required.Contains(swaggerPropName)) { schema.Required.Remove(swaggerPropName); } } } // 添加请求体定义 operation.RequestBody = new OpenApiRequestBody { Content = new Dictionary<string, OpenApiMediaType> { ["application/json"] = new OpenApiMediaType { Schema = schema } } }; } } }
注册后,Swashbuckle会正确识别[FromComposite]标记的参数为请求体,同时自动移除其中的路由属性字段。
内容的提问来源于stack exchange,提问作者Neil
相关产品推荐
相关产品推荐

