Value from HTML date picker is not interpreted in ASP.NET Core MVC controller
Context
- ASP.NET Core MVC web application targeting .NET 6.0
 - Entity Framework Core 6.0.6
 - SQL Server Local DB
 - The forms contain HTML date picker.
 
Goal
- Being able to smoothly work with the new 
DateOnlytype. SQL Server has a correspondingdatetype. - I don't want to use any annotation at model level. Data shall be treated automatically/globally like common types.
 - Value from DB should be displayed accordingly in the date picker upon loading forms. ✔
 - Value from date picker should be retrieved correctly by the targeted action. ❌
 
So ultimately what I want is the Student.DateOfBirth of type DateOnly to be properly populated when it reaches the action in the controller instead on being null.
Many thanks to anyone being able to help.
Actions performed
1. Add DateOnly properties in a few models
For instance:
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; }
}
2. Setup EF
2.1. Write Entity Configs
For the Student entity
namespace WebApp1.Persistence.EntityConfigs;
public class StudentConfig : IEntityTypeConfiguration<Student>
{
    public void Configure(EntityTypeBuilder<Student> builder)
    {
        builder.HasKey(s => s.StudentId);
        builder
            .Property(s => s.FirstName)
            .HasMaxLength(50)
            .IsRequired();
        builder
            .Property(s => s.LastName)
            .HasMaxLength(50)
            .IsRequired();
        // Seeding
        builder.HasData(
            new Student { StudentId = Guid.NewGuid(), DateOfBirth = DateOnly.FromDateTime(DateTime.Today), FirstName = "Uchi", LastName = "Testing" },
            new Student { StudentId = Guid.NewGuid(), DateOfBirth = DateOnly.FromDateTime(DateTime.Today), FirstName = "Armin", LastName = "VanSisharp" },
            new Student { StudentId = Guid.NewGuid(), DateOfBirth = DateOnly.FromDateTime(DateTime.Today), FirstName = "Jack", LastName = "O'Voltrayed" }
            );
    }
}
2.2. Write a ValueConverter<DateOnly,DateTime>
namespace WebApp1.Persistence.Conventions;
public class DateOnlyConverter : ValueConverter<DateOnly, DateTime>
{
    public DateOnlyConverter() : base(
        d => d.ToDateTime(TimeOnly.MinValue),
        d => DateOnly.FromDateTime(d)
        )
    { }
}
2.3. Add usual config
Setup of the connection string in appsettings.json which works fine.
In program.cs:
builder.Services.AddTransient<ApplicationContext>();
builder.Services.AddDbContext<ApplicationContext>(
    options => options
    .UseSqlServer(builder.Configuration.GetConnectionString("Dev")
));
3. Write DateOnlyJsonConverter
namespace WebApplication1.Converters;
public class DateOnlyJsonConverter : JsonConverter<DateOnly>
{
    public override DateOnly Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        => DateOnly.ParseExact(reader.GetString()!, "yyyy-MM-dd", CultureInfo.InvariantCulture);
    public override void Write(Utf8JsonWriter writer, DateOnly value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture));
    }
}
4. Add the JSON converter in JsonOptions
In Program.cs:
builder.Services.AddControllersWithViews()
    .AddJsonOptions(options =>
        options.JsonSerializerOptions
            .Converters.Add(new DateOnlyJsonConverter())
);
5. Wrote a Tag Helper for date picker
public class DatePickerTagHelper : TagHelper
{
    protected string dateFormat = "yyyy-MM-dd";
    public ModelExpression AspFor { get; set; }
    public DateOnly? Date { get; set; }
    public override void Process(TagHelperContext context, TagHelperOutput output)
    {
        base.Process(context, output);
        output.TagName = "input";
        if (!string.IsNullOrEmpty(AspFor.Name))
        {
            output.Attributes.Add("id", AspFor.Name);
            output.Attributes.Add("name", AspFor.Name);
        }
        output.Attributes.Add("type", "date");
        if (Date.HasValue)
            output.Attributes
                .SetAttribute("value", GetDateToString());
        //output.Attributes.Add("value", "2022-01-01");
        output.TagMode = TagMode.SelfClosing;
    }
    protected string GetDateToString() => (Date.HasValue)
        ? Date.Value.ToString(dateFormat)
        : DateOnly.FromDateTime(DateTime.Today).ToString(dateFormat);
}
6. Wrote a form view
@using WebApp1.Models;
@using WebApplication1.Helpers;
@model Student
<h2>Edit @(Model.StudentId == Guid.Empty ? "New Student":$"Student {@Model.FirstName} {@Model.LastName}")</h2>
<form asp-action="UpsertStudent" asp-controller="Student" method="post">
    <input asp-for="StudentId" type="hidden" />
    <label asp-for="FirstName"></label>
    <input asp-for="FirstName" />
    <label asp-for="LastName"></label>
    <input asp-for="LastName" />
    <label asp-for="DateOfBirth"></label>
    <date-picker date=@Model.DateOfBirth asp-for="DateOfBirth"/>
    <button type="reset">Reset</button>
    <submit-button text="Submit Me"/>
</form>
7. [P.S.] Applied newly made convention to EF
I forgot that one.
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    base.ConfigureConventions(configurationBuilder);
    configurationBuilder
        .Properties<DateOnly>()
        .HaveConversion<DateOnlyConverter>()
        .HaveColumnType(SqlDbType.Date.ToString());
}
Behaviour
- Data is properly retrieved from DB and displayed upon form/page load. The date picker is set to the same date as persisted in the DB ✔
 
- Data is transferred accordingly in the 
HttpContext.Requests.Formwhich contains any updated value from the form ✔ 
- Date is set to 
nullin the action ❌ 
ModelState.IsValidreturnstrue❓
Documentation
- Many articles and posts here and there I do not even remember all of'em
 - Official docs
 




