如何让Swashbuckle处理查询字符串中的DateOnly/TimeOnly与JSON一致
解决Swashbuckle中DateOnly查询参数被拆分的问题
要解决GET接口中DateOnly参数被拆分为Year/Month/Day多个查询参数的问题,需要同时处理ASP.NET Core的查询参数绑定和Swagger文档的生成逻辑两部分:
一、让ASP.NET Core支持DateOnly查询参数绑定
JSON转换器仅处理请求体(如POST的FromBody),查询参数需要单独配置绑定规则,这里提供两种可选方案:
方案1:使用自定义TypeConverter
这种方式更简洁,无需编写ModelBinder:
public class DateOnlyTypeConverter : TypeConverter { public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) { return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); } public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value) { if (value is string strValue && DateOnly.TryParse(strValue, culture ?? CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)) { return date; } return base.ConvertFrom(context, culture, value); } }
在Program.cs中注册TypeConverter:
TypeDescriptor.AddAttributes(typeof(DateOnly), new TypeConverterAttribute(typeof(DateOnlyTypeConverter)));
方案2:使用ModelBinder
如果需要更精细的绑定控制,可自定义ModelBinder:
// 自定义ModelBinder public class DateOnlyQueryBinder : IModelBinder { public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext == null) throw new ArgumentNullException(nameof(bindingContext)); var modelName = bindingContext.ModelName; var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); if (valueProviderResult == ValueProviderResult.None) return Task.CompletedTask; bindingContext.ModelState.SetModelValue(modelName, valueProviderResult); var value = valueProviderResult.FirstValue; if (string.IsNullOrEmpty(value)) return Task.CompletedTask; if (DateOnly.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date)) { bindingContext.Result = ModelBindingResult.Success(date); } else { bindingContext.ModelState.TryAddModelError(modelName, "无效日期格式,请使用yyyy-MM-dd"); } return Task.CompletedTask; } } // 注册ModelBinderProvider public class DateOnlyQueryBinderProvider : IModelBinderProvider { public IModelBinder? GetBinder(ModelBinderProviderContext context) { if (context == null) throw new ArgumentNullException(nameof(context)); if (context.Metadata.ModelType == typeof(DateOnly)) return new BinderTypeModelBinder(typeof(DateOnlyQueryBinder)); return null; } }
在Program.cs中添加ModelBinderProvider:
builder.Services.AddControllers(options => { options.ModelBinderProviders.Insert(0, new DateOnlyQueryBinderProvider()); }) .AddJsonOptions(options => { options.JsonSerializerOptions.Converters.Add(new JsonStringDateTimeOnlyConverter()); });
二、修正Swagger文档生成逻辑
添加两个Swagger Filter,分别修正Schema定义和Operation参数:
1. SchemaFilter:将DateOnly标记为字符串类型
public class DateOnlySchemaFilter : ISchemaFilter { public void Apply(OpenApiSchema schema, SchemaFilterContext context) { if (context.Type == typeof(DateOnly)) { schema.Type = "string"; schema.Format = "date"; schema.Properties.Clear(); schema.Required.Clear(); } } }
2. OperationFilter:移除拆分参数,添加单个DateOnly查询参数
public class DateOnlyOperationFilter : IOperationFilter { public void Apply(OpenApiOperation operation, OperationFilterContext context) { // 筛选出所有DateOnly类型的查询参数 var dateOnlyParams = context.ApiDescription.ParameterDescriptions .Where(p => p.ModelMetadata.ModelType == typeof(DateOnly) && p.Source == BindingSource.Query) .ToList(); if (!dateOnlyParams.Any()) return; // 移除自动生成的Year/Month/Day等拆分参数 var splitParams = operation.Parameters .Where(p => dateOnlyParams.Any(d => p.Name.StartsWith(d.Name + "."))) .ToList(); foreach (var param in splitParams) { operation.Parameters.Remove(param); } // 添加单个DateOnly查询参数 foreach (var param in dateOnlyParams) { operation.Parameters.Add(new OpenApiParameter { Name = param.Name, In = ParameterLocation.Query, Required = param.IsRequired, Schema = new OpenApiSchema { Type = "string", Format = "date" }, Description = "日期格式:yyyy-MM-dd" }); } } }
3. 注册Swagger Filter
在Program.cs的Swagger配置中添加:
builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Test API", Version = "v1" }); c.SchemaFilter<DateOnlySchemaFilter>(); c.OperationFilter<DateOnlyOperationFilter>(); });
完成以上配置后,GET接口的DateOnly参数会显示为单个字符串查询参数,同时ASP.NET Core也能正确解析yyyy-MM-dd格式的日期字符串为DateOnly类型。
内容的提问来源于stack exchange,提问作者luc.chante
相关产品推荐
相关产品推荐

