Reputation: 1768
Consider following appsettings.json
:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"NumberOfRetries": 5,
"Option1": "abc",
"Option2": "def"
}
In order to read NumberOfRetries
following class can be used successfully:
public class AppSettings
{
public int NumberOfRetries { get; set; }
public string Option1 { get; set; }
public string Option2 { get; set; }
}
with following code in Startup.cs:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddOptions();
services.Configure<AppSettings>(Configuration);
}
Now, let's say key name is Number.Of.Retries
instead of NumberOfRetries
- with periods in the middle.
How can the AppSetings
class (or the approach itself) be modified to support that? Can't exactly put periods in property name.
Upvotes: 8
Views: 3636
Reputation: 365
A new "ConfigurationKeyNameAttribute" was added in .NET 6 to solve this problem: https://github.com/dotnet/runtime/issues/36010
Upvotes: 4
Reputation: 1768
OK, I figured it out.
Ideally I would like to just use JsonPropertyName
like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
namespace WebAPICore
{
public class AppSettings
{
[JsonPropertyName("Number.Of.Retries")]
public int NumberOfRetries { get; set; }
public string Option1 { get; set; }
public string Option2 { get; set; }
}
}
but it doesn't work. Why? Don't they use JSON parser for this?
So the solution I ended up with looks like this:
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddOptions();
services.Configure<AppSettings>(Configuration); // This maps every key that matches existing property name
services.PostConfigure<AppSettings>(appSettings =>// This maps keys where names don't match existing property names
{
appSettings.NumberOfRetries = Configuration.GetValue<int>("Number.Of.Retries");
});
}
Upvotes: 3
Reputation: 2989
I see your point, i did a quick look up, there is the ability to provide custom logic on how your Options are configured. I did a quick prototype...
void Main()
{
string json = @"{
""Logging"": {
""LogLevel"": {
""Default"": ""Information"",
""Microsoft"": ""Warning"",
""Microsoft.Hosting.Lifetime"": ""Information""
}
},
""AllowedHosts"": ""*"",
""Number.Of.Retries"": 5
}";
using (var doc = System.Text.Json.JsonDocument.Parse(json, new JsonDocumentOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip } ))
{
using(var stream = new MemoryStream())
{
using (var writer = new Utf8JsonWriter(stream))
{
doc.WriteTo(writer);
writer.Flush();
}
stream.Position = 0;
// Usable code here
IConfigurationRoot configuration = new ConfigurationBuilder().AddJsonStream(stream).Build();
var services = new ServiceCollection();
services.AddOptions<AppSettings>();
// There is an option to configure it manually here, if it does not fit the convention
services.Configure<AppSettings>((options) =>
{
options.NumberOfRetries = configuration.GetValue<int>("Number.Of.Retries");
});
var container = services.BuildServiceProvider();
using (var scope = container.CreateScope())
{
var appSettings = scope.ServiceProvider.GetRequiredService<IOptions<AppSettings>>();
Console.WriteLine(appSettings.Value.NumberOfRetries);
}
}
}
}
public class AppSettings
{
public int NumberOfRetries { get; set; }
}
If you have a specific pattern of settings, you can create a custom settings binder for your own "convention", i have provided a very basic sample, which handles the '.' in the settings.
void Main()
{
string json = @"{
""Logging"": {
""LogLevel"": {
""Default"": ""Information"",
""Microsoft"": ""Warning"",
""Microsoft.Hosting.Lifetime"": ""Information""
}
},
""AllowedHosts"": ""*"",
""Number.Of.Retries"": 5
}";
using (var doc = System.Text.Json.JsonDocument.Parse(json, new JsonDocumentOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip } ))
{
using(var stream = new MemoryStream())
{
using (var writer = new Utf8JsonWriter(stream))
{
doc.WriteTo(writer);
writer.Flush();
}
stream.Position = 0;
// Usable code here
IConfigurationRoot configuration = new ConfigurationBuilder().AddJsonStream(stream).Build();
var services = new ServiceCollection();
services.AddOptions<AppSettings>();
services.AddSingleton<IConfiguration>(configuration);
services.ConfigureOptions<CustomConfigureOptions>();
var container = services.BuildServiceProvider();
using (var scope = container.CreateScope())
{
var appSettings = scope.ServiceProvider.GetRequiredService<IOptions<AppSettings>>();
Console.WriteLine(appSettings);
}
}
}
}
public class AppSettings
{
public int NumberOfRetries { get; set; }
}
public class CustomConfigureOptions : IConfigureOptions<AppSettings>
{
private readonly IConfiguration configuration;
public CustomConfigureOptions(IConfiguration configuration)
{
this.configuration = configuration;
}
public void Configure(AppSettings options)
{
foreach(var pair in configuration.AsEnumerable())
{
foreach(var property in typeof(AppSettings).GetProperties())
{
if (property.Name.Equals(pair.Key.Replace(".", "")))
{
property.SetValue(options, configuration.GetValue(property.PropertyType, pair.Key));
}
}
}
}
}
Upvotes: 1