利用接口中的静态虚拟成员实现自定义配置节
C# 11引入了一项新特性——接口中的静态虚拟成员。该特性的主要动机是支持通用数学算法。提到数学可能会让一些人忽略这个特性,但实际上它在其他场景中也很有用。
例如,我利用这个特性清理了注册和使用自定义配置节类型的方式。
自定义配置节
作为回顾,让我们看看自定义配置节。假设您想在appSettings.json中配置API客户端。您可以将配置节映射到类型。例如,以下是我某个项目中的appSettings.json文件。
{"Logging": {"LogLevel": {"Default": "Information","Microsoft.AspNetCore": "Warning"}},"AllowedHosts": "*","OpenAI": {"ApiKey": "Set this in User Secrets","OrganizationId": "{Set this to your org id}","Model": "gpt-4","EmbeddingModel": "text-embedding-3-large"}
}
与其通过IConfiguration API逐个读取"OpenAI"设置,我更倾向于将其映射到一个类型。
public class OpenAIOptions {public string? ApiKey { get; init; }public string? OrganizationId { get; init; }public string Model { get; init; } = "gpt-3.5-turbo";public string EmbeddingModel { get; init; } = "text-embedding-ada-002";
}
在Program.cs中,我可以配置这个映射。
builder.Configuration.Configure<OpenAIOptions>(builder.Configuration.GetSection("OpenAI"));
配置完成后,我可以将IOptions
using Microsoft.Extensions.Options;public class OpenAIClient(IOptions<OpenAIOptions> options) {string? ApiKey => options.Value.ApiKey;string? Model => options.Value.Model;// ...
}
有时,由于某种原因无法注入IOptions
Configuration.GetSection("OpenAI").Get<OpenAIOptions>()
静态虚拟接口来清理
这一切都很好,但当您有多个配置类时,会有些重复。我希望构建一个更基于约定的方法。这就是静态虚拟成员接口派上用场的地方。
首先,为所有配置节定义一个接口。
public interface IConfigOptions
{static abstract string SectionName { get; }
}
注意,有一个名为SectionName的静态抽象字符串属性。这是静态虚拟成员。任何实现此接口的类型都必须实现静态SectionName属性。
现在,我将在配置类中实现该接口。
public class OpenAIOptions : IConfigOptions {public static string SectionName => "OpenAI";public string? ApiKey { get; init; }public string? OrganizationId { get; init; }public string Model { get; init; } = "gpt-3.5-turbo";public string EmbeddingModel { get; init; } = "text-embedding-ada-002";
}
有了这个,我可以实现一个扩展方法来在注册配置节类型时访问SectionName。
public static class OptionsExtensions {public static IHostApplicationBuilder Configure<TOptions>(this IHostApplicationBuilder builder)where TOptions : class, IConfigOptions{var section = builder.Configuration.GetSection(TOptions.SectionName);builder.Services.Configure<TOptions>(section);return builder;}public static TOptions? GetConfigurationSection<TOptions>(this IHostApplicationBuilder builder)where TOptions : class, IConfigOptions{return builder.Configuration.GetSection(TOptions.SectionName).Get<TOptions>();}
}
现在,使用这个方法,我可以这样注册配置节:
builder.Configure<OpenAIOptions>();
当您有多个配置节需要配置时,注册代码看起来简洁明了。
例如,在一个项目中,我有这样的部分:
builder.Configure<OpenAIOptions>().Configure<GitHubOptions>().Configure<GoogleOptions>().Configure<WeatherOptions>()
结论
敏锐的读者会注意到,我不需要在这里使用静态虚拟成员。我本可以通过使用反射从类型名称中提取配置节名称来构建基于约定的方法。确实如此,但代码不如这种方法紧凑。此外,有时您可能希望类型名称与节名称不同。
更多精彩内容 请关注我的个人公众号 公众号(办公AI智能小助手)
对网络安全、黑客技术感兴趣的朋友可以关注我的安全公众号(网络安全技术点滴分享)
公众号二维码
公众号二维码