前言
选项模式 Options 是Dotnet非常重要的一个基础概念,在应用开发过程中很多Service都关联着其 Options。
我们有个AI Agent使用 Options 来配置AI的一部分功能,原需求是只需要支持英文语言,现需求改为要支持其它共6种语言。我决定开发一个类库,使 Options 完整地得到多语言支持。
设计思路
1 具体语言的Configuration
使用OptionsLocalization:{OptionsName}:{culture}做为配置的key前缀,例如AIOptions选项的ModelId属性,在配置里的zh-CN对应的key是OptionsLocalization:AIOptions:zh-CN:ModelId
2 简化的 json 配置文件
未简化前的 AIOptions/zh-CN.json- {
- "OptionsLocalization": {
- "AIOptions": {
- "zh-CN": {
- "ModelId": "gemini2.5",
- "Prompt": "你好世界"
- }
- }
- }
- }
复制代码 期待简化后的 AIOptions/zh-CN.json- {
- "ModelId": "gemini2.5",
- "Prompt": "你好世界"
- }
复制代码 3 语言区域别名化的 Options
- // 选项绑定到别名化的配置
- // 如果是默认语言区域,则注册成别名为Options.DefaultName
- services.Configure("zh-CN", configuration.GetSection("OptionsLocalization:AIOptions:zh-CN"));
复制代码- // 使用别名获取选项
- IOptionsMonitor().Get("zh-CN");
复制代码 4 支持父语言区域回退
假设注册"en"默认语言和zh语言- services.Configure("zh", zhSection);
- services.Configure(Options.DefaultName, enSection);
复制代码 现在前端的语言区域为"zh-CN",IOptionsMonitor().Get("zh-CN")会存在以下问题:
- zh-CN不存在,要回退到zh-Hans
- zh-Hans不存在,要回退到zh
- zh下的ModelId没有配置项,要回退使用默认的en下的ModelId项
我们需要实现自定义的IOptionsFactory,把指定的语言区域别名的AIOptions构建正确。- sealed class CultureOptionsFactory<TOptions> : IOptionsFactory<TOptions>
- where TOptions : class, new()
- {
- private readonly IConfigureOptions<TOptions>[] _setups;
- private readonly IPostConfigureOptions<TOptions>[] _postConfigures;
- private readonly IValidateOptions<TOptions>[] _validations;
- public CultureOptionsFactory(IEnumerable<IConfigureOptions<TOptions>> setups, IEnumerable<IPostConfigureOptions<TOptions>> postConfigures, IEnumerable<IValidateOptions<TOptions>> validations)
- {
- _setups = setups as IConfigureOptions<TOptions>[] ?? setups.ToArray();
- _postConfigures = postConfigures as IPostConfigureOptions<TOptions>[] ?? postConfigures.ToArray();
- _validations = validations as IValidateOptions<TOptions>[] ?? validations.ToArray();
- }
- public TOptions Create(string name)
- {
- var defaultOptions = this.CreateOptions(Options.DefaultName, default);
- if (string.IsNullOrEmpty(name))
- {
- return defaultOptions;
- }
- var culture = CultureInfo.GetCultureInfo(name);
- var cultureStack = new Stack<CultureInfo>();
- cultureStack.Push(culture);
- while (culture.Parent.Name.AsSpan().Length > 0)
- {
- culture = culture.Parent;
- cultureStack.Push(culture);
- }
- var options = defaultOptions;
- while (cultureStack.TryPop(out var next))
- {
- options = this.CreateOptions(next.Name, options);
- }
- return options;
- }
- private TOptions CreateOptions(string name, TOptions? options)
- {
- if (options == null)
- {
- options = new TOptions();
- }
- foreach (var setup in _setups)
- {
- if (setup is IConfigureNamedOptions<TOptions> namedSetup)
- {
- namedSetup.Configure(name, options);
- }
- else if (name == Options.DefaultName)
- {
- setup.Configure(options);
- }
- }
- foreach (var post in _postConfigures)
- {
- post.PostConfigure(name, options);
- }
- if (_validations != null)
- {
- var failures = new List<string>();
- foreach (var validate in _validations)
- {
- var result = validate.Validate(name, options);
- if (result != null && result.Failed)
- {
- failures.AddRange(result.Failures);
- }
- }
- if (failures.Count > 0)
- {
- throw new OptionsValidationException(name, typeof(TOptions), failures);
- }
- }
- return options;
- }
- }
复制代码 工程实现
- -----------------
- | |
- JsonConfigurationSource -> | Configuration | -> IOptionsLocalizer<TOptions>
- | |
- -----------------
复制代码 项目地址:
OptionsLocalization
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |