如何在.NET Core 中将 appseting.json 部分加载到 Dictionary?

我熟悉在.NET Core startup.cs 中将 appsetings.json 部分加载到强类型对象中。例如:

public class CustomSection
{
public int A {get;set;}
public int B {get;set;}
}


//In Startup.cs
services.Configure<CustomSection>(Configuration.GetSection("CustomSection"));


//Inject an IOptions instance
public HomeController(IOptions<CustomSection> options)
{
var settings = options.Value;
}

我有一个 appsetings.json 部分,其键/值对的数量和名称随着时间的推移而变化。因此,在类中硬编码属性名是不切实际的,因为新的键/值对需要在类中进行代码更改。一些键/值对的小示例:

"MobileConfigInfo": {
"appointment-confirmed": "We've booked your appointment. See you soon!",
"appointments-book": "New Appointment",
"appointments-null": "We could not locate any upcoming appointments for you.",
"availability-null": "Sorry, there are no available times on this date. Please try another."
}

有没有办法将这些数据加载到 MobileConfigInfo Dictionary 对象中,然后使用 IOptions 模式将 MobileConfigInfo 注入到控制器中?

90476 次浏览

You can use Configuration.Bind(settings); in startup.cs class

And your settings class will be like

public class AppSettings
{
public Dictionary<string, string> MobileConfigInfo
{
get;
set;
}
}

Hope it helps!

For others who want to convert it to a Dictionary,

sample section inside appsettings.json

"MailSettings": {
"Server": "http://mail.mydomain.com"
"Port": "25",
"From": "info@mydomain.com"
}

Following code should be put inside the Startup file > ConfigureServices method:

public static Dictionary<string, object> MailSettings { get; private set; }


public void ConfigureServices(IServiceCollection services)
{
//ConfigureServices code......


MailSettings = Configuration.GetSection("MailSettings").GetChildren()
.ToDictionary(x => x.Key, x => x.Value);
}

Now you can access the dictionary from anywhere like:

string mailServer = Startup.MailSettings["Server"];

One downside is that all values will be retrieved as strings, if you try any other type the value will be null.

Go with this structure format:

"MobileConfigInfo": {
"Values": {
"appointment-confirmed": "We've booked your appointment. See you soon!",
"appointments-book": "New Appointment",
"appointments-null": "We could not locate any upcoming appointments for you.",
"availability-null": "Sorry, there are no available times on this date. Please try another."
}
}

Make your setting class look like this:

public class CustomSection
{
public Dictionary<string, string> Values {get;set;}
}

then do this

services.Configure<CustomSection>((settings) =>
{
Configuration.GetSection("MobileConfigInfo").Bind(settings);
});

For simple (perhaps microservice) applications you can just add it it as a singleton Dictionary<string, string> and then inject it wherever you need it:

var mobileConfig = Configuration.GetSection("MobileConfigInfo")
.GetChildren().ToDictionary(x => x.Key, x => x.Value);


services.AddSingleton(mobileConfig);

And the usage:

public class MyDependantClass
{
private readonly Dictionary<string, string> _mobileConfig;


public MyDependantClass(Dictionary<string, string> mobileConfig)
{
_mobileConfig = mobileConfig;
}


// Use your mobile config here
}

As an example of more complex binding in ASP.Net Core 2.1; I found using the ConfigurationBuilder .Get<T>() method far easier to work with, as per the documention.

ASP.NET Core 1.1 and higher can use Get, which works with entire sections. Get can be more convenient than using Bind.

I bound the configuration in my Startup method.

private Config Config { get; }


public Startup(IConfiguration Configuration)
{
Config = Configuration.Get<Config>();
}

This binds the appsettings file:

{
"ConnectionStrings": {
"Accounts": "Server=localhost;Database=Accounts;Trusted_Connection=True;",
"test": "Server=localhost;Database=test;Trusted_Connection=True;",
"Client": "Server=localhost;Database={DYNAMICALLY_BOUND_CONTEXT};Trusted_Connection=True;",
"Support": "Server=localhost;Database=Support;Trusted_Connection=True;"
},
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"Plugins": {
"SMS": {
"RouteMobile": {
"Scheme": "https",
"Host": "remote.host",
"Port": 84567,
"Path": "/bulksms",
"Username": "username",
"Password": "password",
"Source": "CompanyName",
"DeliveryReporting": true,
"MessageType": "Unicode"
}
},
"SMTP": {
"GenericSmtp": {
"Scheme": "https",
"Host": "mail.host",
"Port": 25,
"EnableSsl": true,
"Username": "smtpuser@mail.host",
"Password": "password",
"DefaultSender": "noreply@companyname.co.uk"
}
}
}
}

Into this configuration structure:

[DataContract]
public class Config
{
[DataMember]
public Dictionary<string, string> ConnectionStrings { get; set; }
[DataMember]
public PluginCollection Plugins { get; set; }
}


[DataContract]
public class PluginCollection
{
[DataMember]
public Dictionary<string, SmsConfiguration> Sms { get; set; }
[DataMember]
public Dictionary<string, EmailConfiguration> Smtp { get; set; }
}


[DataContract]
public class SmsConfiguration
{
[DataMember]
public string Scheme { get; set; }
[DataMember]
public string Host { get; set; }
[DataMember]
public int Port { get; set; }
[DataMember]
public string Path { get; set; }
[DataMember]
public string Username { get; set; }
[DataMember]
public string Password { get; set; }
[DataMember]
public string Source { get; set; }
[DataMember]
public bool DeliveryReporting { get; set; }
[DataMember]
public string Encoding { get; set; }
}


[DataContract]
public class EmailConfiguration
{
[DataMember]
public string Scheme { get; set; }
[DataMember]
public string Host { get; set; }
[DataMember]
public int Port { get; set; }
[DataMember]
public string Path { get; set; }
[DataMember]
public string Username { get; set; }
[DataMember]
public string Password { get; set; }
[DataMember]
public string DefaultSender { get; set; }
[DataMember]
public bool EnableSsl { get; set; }
}

I use the way below:

appsettings.json:

  "services": {
"user-service": "http://user-service:5000/",
"app-service": "http://app-service:5000/"
}

startup.cs:

  services.Configure<Dictionary<string, string>>(Configuration.GetSection("services"));

Usage:

private readonly Dictionary<string, string> _services;
public YourConstructor(IOptions<Dictionary<string, string>> servicesAccessor)
{
_services = servicesAccessor.Value;
}

I believe you can use the following code:

var config =  Configuration.GetSection("MobileConfigInfo").Get<Dictionary<string, string>>();

By far the simplest method would be to define your configuration class to inherit from the Dictionary type you want to support.

public class MobileConfigInfo:Dictionary<string, string>{
}

Then your startup and dependency injection support would be exactly the same as for any other configuration type.

The only thing that worked for me (ASP.NET Core 3.0) was to add the following to the ConfigureServices method of Startup.cs:

services.Configure<Dictionary<string, string>>(dict => Configuration
.GetSection("MySectionName")
.GetChildren()
.ToList()
.ForEach(c => dict[c.Key] = c.Value));

In .NET Core 3.1 you can do something like the following...

appsettings.json:

{
"myConfig": {
"foo": "bar",
"myMappings": {
"key1": "value1",
"key2": "value2"
}
}
}

A configuration model

MyConfig.cs

public class MyConfig
{
public string Foo { get; set; }
public Dictionary<string, string> MyMappings { get; set; }
}

Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyConfig>(configuration.GetSection("myConfig"));

Class using the options:

public class OptionsUsingClass
{
public OptionsUsingClass(IOptions<MyConfig> myConfigOptions)
{
// Be wary of nulls in real code.
var myConfig = myConfigOptions.Value;


// Examples with the above data.
myConfig.Foo.Should().Be("bar");


myConfig.MyMappings["key1"].Should().Be("value1");
myConfig.MyMappings["key2"].Should().Be("value2");
}

This was how I used appsettings.json dictionary mappings.

You can do it on the fly:

appsettings.json:
{
"MobileConfigInfo": {
"a": "x",
"b": "y",
"c": "z"
}
}

Somewhere in code: (don't forget to add IConfiguration dependency to the class constructor)

var yourDictionary = _configuration.GetSection("MobileConfigInfo")
.Get<IDictionary<string, string>>();

I have a generic solution for setting dictionary type properties, such as the dictionary of HTML attributes that are retrieved from options.

Default dictionary values ​​can be set in options. If the same key exists in the section, then the value in the dictionary is overwritten, otherwise a key-value pair is inserted.

The dictionary is of type IDictionary<string, object> and the read values ​​are not parsed, they are set as type string.

using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;


namespace Microsoft.Extensions.DependencyInjection
{
public static class JJurOptionsExtensions
{
/// <summary>
/// Binding configuration of the property of type IDictionary {string, object}
/// </summary>
/// <typeparam name="TOptions">
/// The type of class that contains the property to be set
/// </typeparam>
/// <param name="services">
/// IoC container
/// </param>
/// <param name="section">
/// Section containing key-value pairs for the dictionary to be set
/// </param>
/// <returns>
/// IServiceCollection
/// </returns>
/// <param name="property">
/// Delegate of the property to be set
/// </param>
public static IServiceCollection ConfigureDictionary<TOptions>(
this IServiceCollection services,
IConfigurationSection section,
Func<TOptions, IDictionary<string, object>> property)
where TOptions : class
{
var values = section        // List of sub-sections
.GetChildren()
.ToList();


services.Configure<TOptions>(options =>
{
var dict = property(options);
values.ForEach(v =>
{
// If there is not key, then insert it.
// If there is, override the value.


dict[v.Key] = v.Value;
});
});


return services;
}
}
}

An example of use:

        services.Configure<JJurCoreLibs.HtmlSortMnu.SortMenuOptions>(
options => configuration.GetSection("SortMenuOptions").Bind(options)
)
.ConfigureDictionary<JJurCoreLibs.HtmlSortMnu.SortMenuOptions>(
configuration.GetSection("SortMenuOptions:DropDownBbtnHtmlAttributes"),
o => o.DropDownBbtnHtmlAttributes);


The SortMenuOptions class contains a property named DropDownBtnHtmlAttribute of type Dictionary<string, object>.

using System.Collections.Generic;


namespace JJurCoreLibs.HtmlSortMnu
{
/// <summary>
/// Options of the Bootstrap dropdown creating service for sorting items
/// </summary>
public class SortMenuOptions
{
...
public DropDownBbtnHtmlAttributes DropDownBbtnHtmlAttributes { get; } = new DropDownBbtnHtmlAttributes {
{ "role", "button" },
{ "data-toggle", "dropdown" },
{ "aria-expanded", false },
{ "aria-haspopup", true }
};
}


public class DropDownBbtnHtmlAttributes : Dictionary<string, object> { }
}