如何在 ASP.NET Core 6 中使用 DateOnly/TimeOnly 查询参数?

How can I use DateOnly/TimeOnly query parameters in ASP.NET Core 6?

As of .NET 6 in ASP.NET API,如果你想获取DateOnly(或TimeOnly)作为查询参数,你需要单独指定所有它是字段,而不是像 DateTime.

那样只提供字符串(TimeOnly 的“2021-09-14”或“10:54:53”)

如果它们是正文的一部分,我可以通过添加自定义 JSON 转换器 (AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(...))) 来解决这个问题,但它不适用于查询参数。

我知道可以使用模型绑定器解决这个问题,但我不想为每个包含 DateOnly/TimeOnly 的模型创建一个模型绑定器。有没有办法在整个应用程序范围内修复此应用程序?

演示:

假设您有一个跟随动作:

[HttpGet] public void Foo([FromQuery] DateOnly date, [FromQuery] TimeOnly time, [FromQuery] DateTime dateTime)

这是它在 Swagger 中的表示方式:

我希望它表示为三个字符串字段:一个用于 DateOnly,一个用于 TimeOnly,一个用于 DateTime(这个已经存在)。

PS:这不是 Swagger 问题,是 ASP.NET 问题。如果我尝试手动传递 ?date=2021-09-14,ASP.NET 将无法理解。

我这边也遇到了你的问题,好像构造函数本身不支持无参数模式。如以下代码:

public DateOnly(int year, int month, int day)
        {
            throw null;
        }

        [NullableContext(1)]
        public DateOnly(int year, int month, int day, Calendar calendar)
        {
            throw null;
        }

虽然日期时间支持:

public DateTime Date
    {
        get
        {
            throw null;
        }
    }

因此,恐怕在更新DateOnlyTimeOnly之前,我们都需要用string代替,将字符串分成年月日,然后new DateOnly(int year, int month, int day).

事实证明,有两种解决方案:

我选择了 TypeConverter,一切正常!自从 .Net 团队 are not planning to add full support for DateOnly/TimeOnly in .Net 6,我决定创建一个 NuGet 来这样做:

https://www.nuget.org/packages/DateOnlyTimeOnly.AspNet (source code)

将其添加到项目并按照描述配置 Program.cs 后,问题描述中描述的动作的 Swagger 将如下所示:

它是如何工作的

首先您需要声明从 stringDateOnly 的类型转换器(以及一个从 stringTimeOnly 的类型转换器):

using System.ComponentModel;
using System.Globalization;

namespace DateOnlyTimeOnly.AspNet.Converters;

public class DateOnlyTypeConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType)
    {
        if (sourceType == typeof(string))
        {
            return true;
        }
        return base.CanConvertFrom(context, sourceType);
    }

    public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
    {
        if (value is string str)
        {
            return DateOnly.Parse(str);
        }
        return base.ConvertFrom(context, culture, value);
    }

    public override bool CanConvertTo(ITypeDescriptorContext? context, Type? destinationType)
    {
        if (destinationType == typeof(string))
        {
            return true;
        }
        return base.CanConvertTo(context, destinationType);
    }
    public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
    {
        if (destinationType == typeof(string) && value is DateOnly date)
        {
            return date.ToString("O");
        }
        return base.ConvertTo(context, culture, value, destinationType);
    }
}

DateOnly的一个是一样的,只是DateOnly换成了TimeOnly

TypeConverterAttribute需要在DateOnlyTimeOnly上添加。可以这样做:

TypeDescriptor.AddAttributes(typeof(DateOnly), new TypeConverterAttribute(typeof(DateOnlyTypeConverter)));
TypeDescriptor.AddAttributes(typeof(TimeOnly), new TypeConverterAttribute(typeof(TimeOnlyTypeConverter)));

为了让它更简洁一些,可以将此代码包装在扩展方法中:

using DateOnlyTimeOnly.AspNet.Converters;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel;

namespace Microsoft.Extensions.DependencyInjection;

public static class MvcOptionsExtensions
{
    public static MvcOptions UseDateOnlyTimeOnlyStringConverters(this MvcOptions options)
    {
        TypeDescriptor.AddAttributes(typeof(DateOnly), new TypeConverterAttribute(typeof(DateOnlyTypeConverter)));
        TypeDescriptor.AddAttributes(typeof(TimeOnly), new TypeConverterAttribute(typeof(TimeOnlyTypeConverter)));
        return options;
    }
}

用法:

builder.Services.AddControllers(options => options.UseDateOnlyTimeOnlyStringConverters())