You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 15:33:13