How to unprefix configuration section item? - c#

To configure my services, I need to set singleton by instance like this:
IConfigurationSection settingsSection = Configuration.GetSection("AppSettings");
MySettings settings = new MySettings();
settingsSection.Bind(settings);
// something to do with the instance, so I need it here
services.Configure<MySettings>(settings);
When I inspect settingsSection items, I see its keys are all prefixed with Appsettings: (ie: AppSettings:AppId, AppSettings:AppUrl, ...).
Thus, binding is not done and my settings object isn't initialized.
Is there a way to prevent this prefix, as I already know I'm in that section?
Here is how the appsettings.json looks like:
{
"AppSettings": {
"AppId": 3540350,
"AppUrl": "http://localhost:542",
"AppEnabled": true,
...
}
}
Here is how MySettings class looks like:
public class MySettings
{
public int AppId { get; set;}
public string AppUrl { get; set;}
public bool AppEnabled { get; set;}
...
}
EDIT
I stupidly kept an old naming convention that can't be deserialized (I guess):
{
"AppSettings": {
"App.Id": 3540350,
"App.Url": "http://localhost:542",
"App.Enabled": true,
...
}
}
That question can be closed.

If you want settings to be singleton I guess you could use the extension method Get of IConfigurationSection, something like this:
var settings = configuration.GetSection("AppSettings").Get<MySettings>();
services.AddSingleton(settings);
Image of sample web project running:

Related

Get built settings options in Program.cs for .net Web api

So for ease of use I decided to map appsettings.json to settings classes. I have for example one section in appsettings:
"Web": {
"EnableCors": true,
"CorsOrigins": [
"http://localhost:3000",
"http://localhost:44315"
]
With a class that this should map to
public class WebSettings
{
public static string SectionName = "Web";
public bool EnableCors { get; set; }
public List<string> CorsOrigins { get; set; }
}
And in Program.cs I have added:
builder.Services.Configure<WebSettings>(
builder.Configuration.GetSection(WebSettings.SectionName));
This works great for any controller where I only have to add
IOptions webSettings
To the constructor for.
However, to the question. Is it possible to use this binding already in program.cs. So for websettings I want to, if "EnableCors" is true add the list of CorsOrigins
// Here I want to preferably use the IOption<WebSettings> object.
// Especially so I get the lost of Cors adresses to loop through and add.
if (configuration["Web:EnableCors"] == "true")
{
builder.Services.AddCors(options =>
{
options.AddPolicy(MyAllowSpecificOrigins,
b =>
{
//configuration["Web:CorsOrigin"]
b.WithOrigins("http://localhost:3000");
b.WithOrigins("http://localhost:44315");
b.AllowAnyHeader();
b.AllowAnyMethod();
b.AllowAnyOrigin();
});
});
}
Is that even possible/viable/good solution?

Dependency Injection with options pattern

I'm trying to load some settings from the appsettings file and I've got a small issue with the way lists are loaded when using the options pattern. Suppose I've got the following classes (for loading the settings):
public class Application {
public string Name { get; set; } = "";
public IEnumerable<string> Roles { get; set; } = new[] {""};
public Application ToApplicationWithoutPass() =>
new Application {
Name = Name,
Username = Username,
Roles = Roles.ToList()
};
}
public class Applications {
public IEnumerable<Application> AppList { get; set; } = new List<Application>();
}
And here is what the settings that are defined on the appsetings file look like:
"Applications": {
"AppList": [
{
"Name": "SraWebuserAdmin",
"Roles": [ "SraEntitiesWriters", "SraEntitiesReaders", "SraEntitiesLoginAccess" ]
},
...
Here are the entries from the DI setup which is done on the ConfigureServices method:
services.Configure<Applications>(options => Configuration.GetSection("Applications").Bind(options));
services.AddScoped<IApplicationAccessVerifier, ApplicationAccessVerifier>();
And, finally, here's the constructor of the ApplicationAccessVerifier class:
public ApplicationAccessVerifier(IOptionsSnapshot<Applications> applicationOptions) {
_applicationOptions = applicationOptions;
}
Now, the question: if I don't initialize the AppList property,
public class Applications {
public IEnumerable<Application> AppList { get; set; }
}
then the settings are loaded correctly.
However, if I initialized it like I've shown (making sure the filed wrapper by the property is initialized with an empty list), then the settings won't be copied to the AppList.
I find this strange since simple properties (ex.: Name on the Application class) aren't affected by the same issue.
Can anyone tell me why this happens or point me to an official documentation about it?

Can't get list of object from appsettings

I have app settings that look like this:
"Tenants": [
{
"Id": "00000000-0000-0000-0000-00000000000",
"ConnectionString": "dbstring"
},
{
"Id": "00000000-0000-0000-0000-00000000001",
"ConnectionString": "dbstring"
}
]
and an object that looks like this:
public class TenantSecrets
{
public string ConnectionString { get; set; }
public Guid Id { get; set; }
}
public class Tenants : List<TenantSecrets> { }
When I try to either configure them or bind them like so:
services.Configure<Tenants>(Configuration.GetSection("Tenants"));
var tenants = new Tenants();
Configuration.Bind("Tenants", tenants);
The list is always empty, does anyone know why or how I can debug it? I can see the list in the appsettings configuration reader when I debug it, but the object never seems to map.
ok people, i figured it out, turns out that the empty guid i was using for the Id was one 0 short of a guid, and i have now learnt that config to poco mapping fails silently, so for anyone else having issues, please check that your data fits the data types
Edit:
Good, you spotted this mistake, it happens it works perfectly. I don't want to write a useless answer so here is the version using the Options pattern.
Configure the Tenants class in the Startup.cs file (method ConfigureServices)
services.Configure<Tenants>(Configuration.GetSection("Tenants"));
Then, inject the IOptions<Tenants> service in a controller constructor.
private readonly Tenants _tenants;
public HomeController(IOptions<Tenants> tenantOptions)
{
_tenants = tenantOptions.Value;
}

How to bind IConfiguration to class having parameters in constructor

I am using standard configuration pattern for ASP.NET Core applications and I can not bind configuration to my class as it has construtor with parameters.
In appsettings.json I included desired config:
"MyServiceConfig": {
"Identity": {
"Version": "1.0",
"ComplicatedUri": {
"Scheme": "http",
"Authority": "localhost",
"Path": "SuperService"
}
}
},
My config class and it's dependencies look like that:
public class MyServiceConfig
{
public MyIdentity Identity { get; set; }
}
public class MyIdentity
{
public string IdentityName { get; set; }
public string Version { get; set; }
public MyComplicatedUri ComplicatedProperty { get; set; }
public MyIdentity(string version, MyComplicatedUri complicatedProperty)
{
Version = version;
ComplicatedProperty = complicatedProperty;
IdentityName = complicatedProperty.Path;
}
}
public class MyComplicatedUri
{
public string Scheme { get; set; }
public string Authority { get; set; }
public string Path { get; set; }
}
I have already tried code like that:
private MyServiceConfig GetMyConfig(IConfiguration configuration)
{
var config = new MyServiceConfig();
configuration.GetSection("MyServiceConfig").Bind(config);
return config;
}
It throws exception:
'Cannot create instance of type 'MyIdentity' because it is missing
a public parameterless constructor.'
That behaviour can make sense in some cases but in that particular one not so much. Mappings could be straightforward - by property names which have public setters or by constructor parameter names.
Another idea would be adding converter in AddJsonOptions in Startup class for my types - IConfiguration.Bind could infer how to construct it but I also tried that with no success.
Have you encoutered similar problems and found some reasonable solution to that?
Edit: Adding parameterless constructor will work of course, but sometimes I need to deal with some classes from external packages I'd like to use as parts of my config class so let's assume we can not modify them. I'd like to avoid adding new types for mapping only as well. Ideally I'd like to force ASP.NET Core engine to use existing constructor with parameters and by parameter name map with json properties - which currently is not working.
You should just add a default constructor in MyIdentity class.
.bind() binds the configuration into the object using the default constructor.
So, add the required default constructor in your MyIdentity class and it will be fine.
public MyIdentity(){}
Also, you can use Options.
In ConfigureServices, add the following:
services.AddOptions();
services.ConfigureOptions<MyServiceConfig>();
and then use dependency injection to initialize it.
In addition, use your own JsonConverter

Injectable ApplicationConfig service

I want a service I can inject - or in my example get with GetService - that contains settings from my appsettings.json file.
The appsettings.json fragment looks like this:
"ExternalInterfaces": [
{
"Name": "name1",
"BaseUrl": "https://www.baseurl1.svc"
},
{
"Name": "name2",
"BaseUrl": "https://www.baseurl2.svc"
}
]
To do this I have the following interfaces:
using System.Collections.Generic;
namespace Infrastructure.Settings
{
public interface IExternalInterfaceSettingsCollection
{
IReadOnlyCollection<IExternalInterfaceSettings> Settings { get; set; }
}
}
namespace Infrastructure.Settings
{
public interface IExternalInterfaceSettings
{
string Name { get; set; }
string BaseUrl { get; set; }
}
}
and the following corresponding classes:
using System.Collections.Generic;
namespace Infrastructure.Settings
{
public class ExternalInterfaceSettingsCollection : IExternalInterfaceSettingsCollection
{
public IReadOnlyCollection<IExternalInterfaceSettings> Settings { get; set; }
}
}
namespace Infrastructure.Settings
{
public class ExternalInterfaceSettings : IExternalInterfaceSettings
{
const string DefaultName = "newExternalInterface";
const string DefaultBaseUrl = "";
public string Name { get; set; } = DefaultName;
public string BaseUrl { get; set; } = DefaultBaseUrl;
}
}
And in my Startup.cs I have this (definitely gets called with no exceptions):
services.Configure<IExternalInterfaceSettingsCollection>(settings => _configuration.GetSection("ExternalInterfaces").Bind(settings));
and this is then consumed as follows:
var externalInterfaceConfiguration = app.ApplicationServices.GetService<ExternalInterfaceSettingsCollection>();
var Setting1BaseUrl = externalInterfaceConfiguration.Settings
.SingleOrDefault(s => s.Name == "name1")?.BaseUrl;
However, in the last 3 lines, externalInterfaceConfiguration is always null.
I'm clearly missing something, but I can't see what. Any clues?
You've registered IExternalInterfaceSettings, but you're attempting to retrieve ExternalInterfaceSettings. There's no such service in the collection, so the result is null (since you used GetService<T>). If you had used GetRequiredService<T> then an exception would have been thrown telling you as much.
Then, the options pattern is not meant to bind to interfaces. The whole idea is that you're binding to a POCO that represents a specific set of settings. If you want to use an interface, I suppose that's your prerogative, but it's not going to be applicable to the options configuration. In other words, you need the following instead:
services.Configure<ExternalInterfaceSettings>(Configuration.GetSection("ExternalInterfaces"));
(Note, the action overload with Bind is superfluous. You can just pass the config section directly.)
With that, you'll be able to request something like IOptions<ExternalInterfaceSettings>, but you still cannot get ExternalInterfaceSettings directly from the service collection. If you want that functionality, you'll need to add an additional service registration (which can utilize an interface, this time):
services.AddScoped<IExternalInterfaceSettings, ExternalInterfaceSettings>(p =>
p.GetRequiredService<IOptions<ExternalInterfaceSettings>>().Value);

Categories