问题描述
我使用 IOptions 模式-configuration-objects" rel="noreferrer">在官方文档中.
I am using the IOptions
pattern as described in the official documentation.
当我从 appsetting.json
读取值时,这可以正常工作,但是如何更新值并将更改保存回 appsetting.json
?
This works fine when I am reading values from appsetting.json
, but how do I update values and save changes back to appsetting.json
?
就我而言,我有一些可以从用户界面编辑的字段(由应用程序中的管理员用户).因此,我正在寻找通过选项访问器更新这些值的理想方法.
In my case, I have a few fields that can be edited from the user interface (by admin user in application). Hence I am looking for the ideal approach to update these values via the option accessor.
推荐答案
在撰写此答案时,似乎 Microsoft.Extensions.Options
包没有提供具有功能的组件将配置值写回 appsettings.json
.
At the time of writing this answer it seemed that there is no component provided by the Microsoft.Extensions.Options
package that has functionality to write configuration values back to appsettings.json
.
在我的一个 ASP.NET Core
项目中,我想让用户更改一些应用程序设置 - 这些设置值应该存储在 appsettings.json
中,更准确地说是在一个可选的 appsettings.custom.json
文件中,如果存在,它会被添加到配置中.
In one of my ASP.NET Core
projects I wanted to enable the user to change some application settings - and those setting values should be stored in appsettings.json
, more precisly in an optional appsettings.custom.json
file, that gets added to the configuration if present.
像这样……
public Startup(IHostingEnvironment env)
{
IConfigurationBuilder builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile("appsettings.custom.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables();
this.Configuration = builder.Build();
}
我声明了 IWritableOptions<T>
接口,它扩展了 IOptions<T>
;所以只要我想读写设置,我就可以用 IWritableOptions<T>
替换 IOptions<T>
.
I declared the IWritableOptions<T>
interface that extends IOptions<T>
; so I can just replace IOptions<T>
by IWritableOptions<T>
whenever I want to read and write settings.
public interface IWritableOptions<out T> : IOptions<T> where T : class, new()
{
void Update(Action<T> applyChanges);
}
另外,我想出了 IOptionsWriter
,它是一个旨在由 IWritableOptions<T>
用于更新配置部分的组件.这是我对前面提到的接口的实现...
Also, I came up with IOptionsWriter
, which is a component that is intended to be used by IWritableOptions<T>
to update a configuration section. This is my implementation for the beforementioned interfaces...
class OptionsWriter : IOptionsWriter
{
private readonly IHostingEnvironment environment;
private readonly IConfigurationRoot configuration;
private readonly string file;
public OptionsWriter(
IHostingEnvironment environment,
IConfigurationRoot configuration,
string file)
{
this.environment = environment;
this.configuration = configuration;
this.file = file;
}
public void UpdateOptions(Action<JObject> callback, bool reload = true)
{
IFileProvider fileProvider = this.environment.ContentRootFileProvider;
IFileInfo fi = fileProvider.GetFileInfo(this.file);
JObject config = fileProvider.ReadJsonFileAsObject(fi);
callback(config);
using (var stream = File.OpenWrite(fi.PhysicalPath))
{
stream.SetLength(0);
config.WriteTo(stream);
}
this.configuration.Reload();
}
}
由于作者不了解文件结构,我决定将部分作为 JObject
对象处理.访问器尝试找到请求的部分并将其反序列化为 T
的实例,使用当前值(如果未找到),或者只是创建 T
的新实例,如果当前值为 null
.然后将此持有者对象传递给调用者,调用者将对其应用更改.然后更改的对象将转换回 JToken
实例,该实例将替换该部分...
Since the writer is not aware about the file structure, I decided to handle sections as JObject
objects. The accessor tries to find the requested section and deserializes it to an instance of T
, uses the current value (if not found), or just creates a new instance of T
, if the current value is null
. This holder object is than passed to the caller, who will apply the changes to it. Than the changed object gets converted back to a JToken
instance that is going to replace the section...
class WritableOptions<T> : IWritableOptions<T> where T : class, new()
{
private readonly string sectionName;
private readonly IOptionsWriter writer;
private readonly IOptionsMonitor<T> options;
public WritableOptions(
string sectionName,
IOptionsWriter writer,
IOptionsMonitor<T> options)
{
this.sectionName = sectionName;
this.writer = writer;
this.options = options;
}
public T Value => this.options.CurrentValue;
public void Update(Action<T> applyChanges)
{
this.writer.UpdateOptions(opt =>
{
JToken section;
T sectionObject = opt.TryGetValue(this.sectionName, out section) ?
JsonConvert.DeserializeObject<T>(section.ToString()) :
this.options.CurrentValue ?? new T();
applyChanges(sectionObject);
string json = JsonConvert.SerializeObject(sectionObject);
opt[this.sectionName] = JObject.Parse(json);
});
}
}
最后,我为 IServicesCollection
实现了一个扩展方法,允许我轻松配置可写选项访问器...
Finally, I implemented an extension method for IServicesCollection
allowing me to easily configure a writable options accessor...
static class ServicesCollectionExtensions
{
public static void ConfigureWritable<T>(
this IServiceCollection services,
IConfigurationRoot configuration,
string sectionName,
string file) where T : class, new()
{
services.Configure<T>(configuration.GetSection(sectionName));
services.AddTransient<IWritableOptions<T>>(provider =>
{
var environment = provider.GetService<IHostingEnvironment>();
var options = provider.GetService<IOptionsMonitor<T>>();
IOptionsWriter writer = new OptionsWriter(environment, configuration, file);
return new WritableOptions<T>(sectionName, writer, options);
});
}
}
可以在ConfigureServices
中使用类似...
Which can be used in ConfigureServices
like...
services.ConfigureWritable<CustomizableOptions>(this.Configuration,
"MySection", "appsettings.custom.json");
在我的 Controller
类中,我只需要一个 IWritableOptions<CustomizableOptions>
实例,它与 IOptions<T>
具有相同的特性,但也允许更改和存储配置值.
In my Controller
class I can just demand an IWritableOptions<CustomizableOptions>
instance, that has the same characteristics as IOptions<T>
, but also allows to change and store configuration values.
private IWritableOptions<CustomizableOptions> options;
...
this.options.Update((opt) => {
opt.SampleOption = "...";
});
这篇关于如何将值更新到 appsetting.json?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!