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?
Related
So I have a simple configuration class PubsubSettings:
public class PubSubSettings
{
public string ProjectId { get; set; }
public string TopicId { get; set; }
public int PartnerId { get; set; }
public string SubscriptionId { get; set; }
}
I have previously only had one of these configured in my appsettings.json but now I want to be able to handle an arbitrary number of them.
I have another class, PubSub, that I usually inject an IOptions<PubSubSettings> into. And this, in turn, gets injected into my Worker class.
services.Configure<PubSubSettings>(configuration.GetSection(nameof(PubSubSettings)));
...
services.AddHostedService<Worker>();
So, what I want to do now, is add a new Worker as a hosted service for each entry in my AppSettings PubSubSettings section and inject the relevant IOptions<PubSubSettings> into each of these (along with the standard ILogger).
So in essence, I'd like this config block:
"PubsubSettings": [
{
"ProjectId": "project1",
"TopicId": "topic",
"PartnerId": 1,
"SubscriptionId": "sub1"
},
{
"ProjectId": "project2",
"TopicId": "topic2",
"PartnerId": 2,
"SubscriptionId": "sub2"
}
]
To end up with two hosted services being created, one with the first set of options and the other with the second.
I've seen a few questions looking for similar things but nothing I could find quite lined up with this so I'm a bit stumped. Any ideas?
The solution is Dotnet 5.
So from what I've been able to find, there's no way to do this out-of-the box.
However, This can be done manually using a combination of ActivatorUtilities and Configuration.Bind().
private void CreateWorkers(IServiceCollection services, IConfigurationRoot configuration)
{
List<PubSubSettings> pubsubSettings = new();
configuration.Bind(nameof(PubSubSettings), pubsubSettings);
foreach (PubSubSettings setting in pubsubSettings)
{
services.AddSingleton<IHostedService>(s => ActivatorUtilities.CreateInstance<Worker>(s, ActivatorUtilities.CreateInstance<PubSub.PubSub>(s, setting)));
}
}
Essentially, you can use Bind to get the configuration objects from the JSON. Then you can manually construct the Worker for the call to AddHostedService using CreateInstance.
Two calls are needed in this case, one to generate the PubSub for the worker (in which we pass the setting parameter) and the other to generate the Worker itself.
ActivatorUtilities essentially injects everything you need for the object except the parameters you've provided.
We need to use .AddSingleton<IHostedService> because of the way that the framework checks for dupes with AddHostedService().
Maybe you could try creating a class only for the object and let the PubSubSettings class only for the array:
public class PubSubSettings
{
public PubSubObject[] PubSubs { get; set; }
}
public class PubSubObject
{
public string ProjectId { get; set; }
public string TopicId { get; set; }
public int PartnerId { get; set; }
public string SubscriptionId { get; set; }
}
Then in the startup class you should use Bind to get the current value of the array to create a Worker for each PubSub:
PubSubSettings settings = new PubSubSettings();
Configuration.GetSection(nameof(PubSubSettings)).Bind(settings);
...
foreach(PubSubObject item in settings.PubSubs)
{
services.AddHostedService<Worker>();
}
Then in the PubSub class you need to search the PartnerId inside the Array.
Or you could follow the approach described in the section Named options support using IConfigureNamedOptions in the Microsoft docs: Options pattern in ASP.NET Core
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:
I have a .NET Core API. I set the following in Startup.cs to handle serialization:
services.AddControllers().AddNewtonsoftJson(options =>
{
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
});
I have the following endpoint to update an object:
[HttpPut, Route( "car" )]
public ActionResult<Car> UpdateCar( [FromBody] Car car )
Using the following model class:
public class Car
{
public List<Owner> PreviousOwners { get; set; }
public List<string> Details { get; set; }
public Car()
{
PreviousOwners = new List<Owner>();
Details = new List<string> { "Used" };
}
}
When I PUT to this endpoint, I want to ensure that the "Used" value is in the list, at the minimum. This is why I modify the default constructor as such.
However, when I PUT to this endpoint, without defining any further "Details" values (say, I add another PreviousOwner) then I get the following at my breakpoint set at when the endpoint is hit (before any further code is processed):
{
"PreviousOwners": [ *Populates Correctly* ],
"Details": [
"Used",
"Used"
],
}
Why is an additional "Used" being added here? Is an empty default constructor absolutely required?
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?
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);