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

ASP.NET Core MVC无法绑定HTML日期选择器提交的DateOnly值

问题背景

本次开发基于以下技术栈:面向.NET 6.0的ASP.NET Core MVC Web应用、Entity Framework Core 6.0.6、SQL Server Local DB,业务表单中使用HTML原生日期选择器控件。

实现目标

需要全局自动支持DateOnly类型(对应SQL Server的date列类型),无需在模型层添加任何数据注解,实现两个核心效果:

  • 从数据库读取的DateOnly类型值可在表单加载时正常显示在日期选择器中(该功能已实现 ✔);
  • 日期选择器提交的数值可被目标控制器Action正确接收绑定(当前功能异常 ❌)。

最终需要解决控制器Action中Student.DateOfBirth(DateOnly?类型)提交后取值为null、无法正常赋值的问题。

已执行操作
  • 模型定义:在多个模型类中添加DateOnly类型属性,例如Student实体定义如下:
namespace WebApp1.Models;

public class Student
{
    public Guid StudentId { get; set; }
    public string? FirstName { get; set; }
    public string? LastName { get; set; }
    public DateOnly? DateOfBirth { get; set; }
}
  • EF Core配置
    • 编写Student实体的IEntityTypeConfiguration<Student>配置,设置主键、字段长度、必填约束,添加测试种子数据;
    • 编写ValueConverter<DateOnly,DateTime>类型转换器,实现DateOnly转DateTime时映射为当日最小时间、DateTime转DateOnly时直接提取日期部分;
    • 完成基础配置:在appsettings.json中配置可用的数据库连接字符串,在Program.cs中注册ApplicationContext数据上下文,配置SqlServer数据库连接。
  • JSON序列化配置:编写DateOnly类型的自定义JsonConverterDateOnlyJsonConverter,固定使用yyyy-MM-dd格式、CultureInfo.InvariantCulture实现DateOnly的序列化与反序列化,并在Program.cs的MVC服务配置中,将该转换器添加到JsonSerializerOptions的全局转换器集合中。
  • 自定义Tag Helper:编写日期选择器专用Tag Helper,固定渲染为type="date"的input标签,自动生成对应id、name属性,绑定的DateOnly值按yyyy-MM-dd格式渲染到value属性。
  • 视图编写:编写学生信息编辑表单视图,使用自定义<date-picker>标签辅助器绑定DateOfBirth属性,表单采用POST方式提交至Student控制器的UpsertStudent Action。
  • EF Core全局约定配置:在DbContext的ConfigureConventions方法中,全局为所有DateOnly类型属性配置上述DateOnly类型转换器,映射数据库列类型为date。
当前现象
  • 数据库读取的日期值可正常在页面加载时显示在日期选择器中,符合预期;
  • 表单提交时,HttpContext.Request.Form中可正常获取到日期选择器提交的更新值,前端数据传输正常;
  • 控制器Action中接收的Student实体的DateOfBirth属性值为null,模型绑定失败;
  • 此时ModelState.IsValid返回true,无模型校验错误。
解决方案

问题根因:.NET 6 内置的MVC模型绑定器默认不支持DateOnly/DateOnly?类型的表单值绑定,之前配置的JsonConverter仅对请求体为JSON格式的接口生效,对application/x-www-form-urlencoded类型的常规表单提交无效。
通过全局注册自定义DateOnly模型绑定器和绑定提供器即可实现无数据注解的自动绑定,步骤如下:

  1. 编写自定义DateOnly模型绑定器,兼容可空类型,固定解析HTML日期控件提交的yyyy-MM-dd格式值:
using System.Globalization;
using Microsoft.AspNetCore.Mvc.ModelBinding;

public class DateOnlyModelBinder : IModelBinder
{
    private const string TargetDateFormat = "yyyy-MM-dd";
    
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        var modelName = bindingContext.ModelName;
        var valueResult = bindingContext.ValueProvider.GetValue(modelName);
        
        if (valueResult == ValueProviderResult.None)
            return Task.CompletedTask;

        bindingContext.ModelState.SetModelValue(modelName, valueResult);
        var rawDateStr = valueResult.FirstValue;

        // 处理空值,兼容可空DateOnly?类型
        if (string.IsNullOrWhiteSpace(rawDateStr))
        {
            bindingContext.Result = ModelBindingResult.Success(null);
            return Task.CompletedTask;
        }

        // 按固定格式解析提交值
        if (DateOnly.TryParseExact(rawDateStr, TargetDateFormat, CultureInfo.InvariantCulture, 
                DateTimeStyles.None, out var parsedDate))
        {
            bindingContext.Result = ModelBindingResult.Success(parsedDate);
            return Task.CompletedTask;
        }

        // 解析失败时写入模型校验错误
        bindingContext.ModelState.TryAddModelError(modelName, 
            $"日期格式需为{TargetDateFormat}");
        return Task.CompletedTask;
    }
}
  1. 编写模型绑定提供器,全局匹配DateOnly和DateOnly?类型的属性,自动应用上述绑定器:
using Microsoft.AspNetCore.Mvc.ModelBinding;

public class DateOnlyModelBinderProvider : IModelBinderProvider
{
    public IModelBinder? GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        var propertyType = context.Metadata.ModelType;
        // 同时匹配非空、可空DateOnly类型
        if (propertyType == typeof(DateOnly) || propertyType == typeof(DateOnly?))
        {
            return new DateOnlyModelBinder();
        }
        return null;
    }
}
  1. 在Program.cs的MVC服务配置中,将该绑定提供器插入到内置绑定提供器集合的最前端,实现全局生效:
builder.Services.AddControllersWithViews(options =>
{
    // 插入到集合首位,优先处理DateOnly类型绑定
    options.ModelBinderProviders.Insert(0, new DateOnlyModelBinderProvider());
})
.AddJsonOptions(options =>
{
    // 原有DateOnlyJsonConverter配置保留即可,覆盖JSON请求/响应场景
    options.JsonSerializerOptions.Converters.Add(new DateOnlyJsonConverter());
});

配置完成后无需修改任何模型、视图代码,表单提交的DateOnly/DateOnly?类型属性会被自动正确绑定,和已实现的数据库读写、前端显示逻辑完全兼容。

注意:逐个属性添加[DataType(DataType.Date)]注解的方案需要修改所有模型定义,不符合全局配置的需求,不推荐使用。


内容的提问来源于stack exchange,提问作者UchiTesting

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 23:33:20