Asp.Net Core 3.1 应用程序设置未遵循 JsonConverter。

13
在asp.net core 3.1中,使用新的System.Text.Json,我试图在appsettings部分上使用自定义JsonConverter。手动序列化/反序列化可以正常地使用转换器,但是通过Options模式从appSettings读取时则不能。以下是我的代码:
JsonConverter,为简单起见,此代码将字符串值转换为大写:
    public class UpperConverter : JsonConverter<string>
    {
        public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) =>
            reader.GetString().ToUpper();

        public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOptions options) =>
            writer.WriteStringValue(value == null ? "" : value.ToUpper());
    }

在一个字符串属性上声明转换器的appsettings类:

    public class MyOptions
    {
        public const string Name = "MyOptions";
        [JsonConverter(typeof(UpperConverter))]
        public string MyString { get; set; }
    }

Startup.cs 修改以准备一切:

       public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllersWithViews()
                .AddJsonOptions(options =>
                {
                    options.JsonSerializerOptions.Converters.Add(new UpperConverter());
                });

            services.Configure<MyOptions>(Configuration.GetSection(MyOptions.Name));
        }

当我将IOptions<MyOptions>注入到HomeController中时,它读取小写值。如果我手动执行JsonSerializer.Deserialize<MyOptions>("{\"MyString\":\"lowercase_manual\"}"),我会得到一个大写字符串,即使我删除了Startup中对JsonSerializerOptions的声明。
有人知道如何让appsettings/options模式尊重JsonConverter吗?我需要在其他地方声明JsonSerializerOptions吗?谢谢。

2
它们不是必须的,也不需要。JsonConverter选项适用于ASP.NET请求和响应解析。Microsoft.Extensions.Configuration是一个单独的包,用于所有类型的项目,包括控制台项目。appsettings.json不是特殊文件,只是使用其中一个可用配置提供程序加载设置的约定名称。JSON提供程序使用特定格式加载设置,文档中有描述。所有设置,无论来源(json、ini、数据库、环境),都存储为键/值对。 - Panagiotis Kanavos
为什么要使用JsonConverter?你不能用它来构造对象,这些对象是从所有提供者加载的键/值对构造出来的。如果你想将加载的值转换为大写字母 - 为什么?如果大小写有关系,你不应该保留大小写,或者确保存储了正确的大小写吗?如果没有关系,你可以使用不区分大小写的比较或不区分大小写的字典来处理这些值。 - Panagiotis Kanavos
不,我不需要大写字母,这只是为了回答这个问题而在转换器中做的一个简单的事情。你关于扩展预期使用的第一条评论很有帮助,还有提到创建自己的设置提供程序的评论。 - edhenn
为什么?除非您有一个不遵循“section:subsection:key:value”格式的外部文件,否则您可能不需要它。但是每个自定义提供程序都应该将设置转换为该格式。 - Panagiotis Kanavos
1
我猜我在这里寻找的具体答案是,JsonConfigurationProvider从不调用JsonSerializer.Deserialize,因为正如你所指出的那样,它不需要,它只是用于创建键值对。相反,它调用JsonDocument.Parse来完成这项工作。感谢您的帮助。 - edhenn
显示剩余2条评论
1个回答

10
重要的是要了解选项模式实现为两个独立步骤:从配置源中读取数据,然后将该数据绑定到强类型对象。
读取步骤由各种配置提供程序实现,其中只有一个是JSON。您可能希望JSON提供程序尊重您的JsonConverter,但此步骤仅将其配置数据最小转换为下一步可以接受的通用格式。
因此,绑定步骤似乎是关心JsonConverter的地方。但是,这一步骤故意完全不知道任何特定的配置提供程序,因为它仅从提供程序中以通用格式接收数据,而对此毫不关心。因此,它不会关心JSON特定的转换器。
但是,它将关心更通用的转换器来处理其通用数据,并且幸运的是,.NET已经内置了此基础架构:type converters。这些自.NET诞生以来就存在,虽然它们很老,但它们非常简单、实用,确实非常适合这种特定情况。
这里不包含实现类型转换器的完整示例,但主要步骤是派生自 TypeConverter,重写适当的方法,并使用 TypeConverterAttribute 装饰要转换的类,指向您的 TypeConverter 实现。然后一切都应该正常工作™。
你提供的例子有一个警告,那就是你不是真正尝试着去转换什么,而是试图转化一个字符串,显然,由于配置提供程序的源值是一个字符串,而选项类上的目标类型也是一个字符串,所以TypeConverter不会被调用。 相反,你可以创建一个新类来包装一个字符串,以强制将其转换为大写:
public class UppercaseString
{
    public string Value { get; }

    public UppercaseString(string str)
    {
        Value = str.ToUpper();
    }

    public static implicit operator string(UppercaseString upper)
        => upper.Value;
}

然后将您的选项类更改为使用该包装器:

public class MyOptions
{
    public const string Name = "MyOptions";

    public UppercaseString MyString { get; set; }
}

最后,实现一个TypeConverter,将string转换为UppercaseString。
请注意implicit operator string的定义-这允许您在任何期望标准string的地方使用UppercaseString,以便您不必更改引用MyOptions.MyString的代码为MyOptions.MyString.Value。

价值包装的想法非常棒。非常感谢你。 - Victor Machado

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接