C# 使用IConfiguration绑定DateTime时使用DateTimeStyles.RoundtripKind

C# 使用IConfiguration绑定DateTime时使用DateTimeStyles.RoundtripKind,c#,datetime,asp.net-core,C#,Datetime,Asp.net Core,TL;DR:我希望绑定DateTime值的结果与IConfiguration中的DateTime.Parse(stringValue,null,DateTimeStyles.RoundtripKind)相同,但默认情况下与DateTime.Parse(stringValue)相同 我正在使用IConfiguration的绑定功能从配置中提取DateTime值 假设我有一个JSON配置,如下所示: { testValue: "2020-03-08T14:37:46.9793888Z" }

TL;DR:我希望绑定
DateTime
值的结果与
IConfiguration
中的
DateTime.Parse(stringValue,null,DateTimeStyles.RoundtripKind)
相同,但默认情况下与
DateTime.Parse(stringValue)
相同


我正在使用
IConfiguration
的绑定功能从配置中提取
DateTime

假设我有一个JSON配置,如下所示:

{
    testValue: "2020-03-08T14:37:46.9793888Z"
}
我可以通过以下几种方法之一将值提取为
DateTime
。以下是我想到的前两个:

// Directly from IConfiguration
var dateTimeValue = configuration.GetValue<DateTime>("testValue");
我正在编写关注
DateTime
Kind
属性的代码。普通解析忽略“Z”时间,并生成任何带有时区指示的内容
Local
。使用
RoundtripKind
样式进行这样的调用可以解决以下问题:

// Good, uses Kind if available
var dateTimeValue = DateTime.Parse(stringValue, null, DateTimeStyles.RoundtripKind);

有没有办法强制配置绑定器使用此调用而不是默认调用?

我有一个想法:我可以用包装器类型替换选项类中的
DateTime
字段,然后对其执行解析。我们更改字段的类型,如下所示:

private class TestOptions
{
    //public DateTime TestValue { get; set; }
    public RoundtripDateTime TestValue { get; set; }
}
我们组成了一个包装类型(我把它做成了一个
struct
,但是
类也可以工作),包括与
DateTime
之间的隐式转换(这样至少一些代码不需要更改)和很好的
Parse()
ToString()
方法。我们包括一个
[TypeConverter]
属性,以便配置绑定器检测转换器类

    [TypeConverter(typeof(RoundtripDateTimeTypeConverter))]
    public struct RoundtripDateTime
    {
        public RoundtripDateTime(DateTime value)
        {
            Value = value;
        }

        public DateTime Value { get; set; }

        public static implicit operator DateTime(RoundtripDateTime roundTripDateTime) => roundTripDateTime.Value;
        public static implicit operator RoundtripDateTime(DateTime dateTime) => new RoundtripDateTime(dateTime);

        public override string ToString() => Value.ToString("o");
        public static RoundtripDateTime Parse(string s) => DateTime.Parse(s, null, DateTimeStyles.RoundtripKind);
        public static RoundtripDateTime Parse(string s, CultureInfo culture) => DateTime.Parse(s, culture, DateTimeStyles.RoundtripKind);
    }
最后,我们包括一个新的
TypeConverter
实现,它将处理包装器的实例。(我作弊并查找源代码到
DateTimeConverter
,以了解它已经在做什么,但这实际上要简单得多,因为它总是在文化无关模式下运行。[编辑:对于从
string
的转换,添加了文化来帮助
Parse()
处理不是ISO8601格式的日期时间。它仍然非常简单。])

这一切可能需要一些调整,但我想我有一些我可以处理的东西

如果你有更好的想法,请随意添加其他答案

private class TestOptions
{
    //public DateTime TestValue { get; set; }
    public RoundtripDateTime TestValue { get; set; }
}
    [TypeConverter(typeof(RoundtripDateTimeTypeConverter))]
    public struct RoundtripDateTime
    {
        public RoundtripDateTime(DateTime value)
        {
            Value = value;
        }

        public DateTime Value { get; set; }

        public static implicit operator DateTime(RoundtripDateTime roundTripDateTime) => roundTripDateTime.Value;
        public static implicit operator RoundtripDateTime(DateTime dateTime) => new RoundtripDateTime(dateTime);

        public override string ToString() => Value.ToString("o");
        public static RoundtripDateTime Parse(string s) => DateTime.Parse(s, null, DateTimeStyles.RoundtripKind);
        public static RoundtripDateTime Parse(string s, CultureInfo culture) => DateTime.Parse(s, culture, DateTimeStyles.RoundtripKind);
    }
    public class RoundtripDateTimeTypeConverter : 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 stringValue)
            {
                var text = stringValue.Trim();

                if (text == string.Empty)
                {
                    return (RoundtripDateTime)DateTime.MinValue;
                }
                else
                {
                    try
                    {
                        return RoundtripDateTime.Parse(text, culture ?? CultureInfo.InvariantCulture);
                    }
                    catch (FormatException e)
                    {
                        throw new FormatException($"'{value}' is not a valid value for {nameof(RoundtripDateTime)}.", e);
                    }
                }
            }
            else
            {
                return base.ConvertFrom(context, culture, value);
            }
        }

        public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
        {
            if (destinationType == typeof(string) && value is RoundtripDateTime roundtripDateTimeValue)
            {
                return (roundtripDateTimeValue.Value == DateTime.MinValue)
                    ? string.Empty
                    : roundtripDateTimeValue.ToString();
            }
            else
            {
                return base.ConvertTo(context, culture, value, destinationType);
            }
        }
    }