Access to Configuration object from Startup class - c#

I would like to access to the Active Directory from my company in many controllers from my ASP.NET vNext project, and I inserted the domain name into my config.json file, so I can access it from the Configuration class. I find it heavy to always instantiate a new Configuration object at every time I want to access to my config.json, is there any way through the IConfiguration API to access to the Configuration class initialized into the Startup class ?

An example of how you can do this:
Let's assume you have a config.json like below:
{
"SomeSetting1": "some value here",
"SomeSetting2": "some value here",
"SomeSetting3": "some value here",
"ActiveDirectory": {
"DomainName": "DOMAIN-NAME-HERE"
}
}
Create a POCO type having your option information:
public class ActiveDirectoryOptions
{
public string DomainName { get; set; }
}
In Startup.cs, when configuring services:
services.Configure<ActiveDirectoryOptions>(optionsSetup =>
{
//get from config.json file
optionsSetup.DomainName = configuration.Get("ActiveDirectory:DomainName");
});
In all controllers which want to get this config setting, do something like...Here the options is injected by the DI system:
public class HomeController : Controller
{
private readonly IOptions<ActiveDirectoryOptions> _activeDirectoryOptions;
public HomeController(IOptions<ActiveDirectoryOptions> activeDirectoryOptions)
{
_activeDirectoryOptions = activeDirectoryOptions;
}
public IActionResult Index()
{
string domainName = _activeDirectoryOptions.Options.DomainName;
........
}
}
Responding to the comment:
Dependency Injection was one of my option, but assume that you inject many repository inside your controller and a UserManager object because you want some user management, your constructor will be very busy. And all the time you want to use your controller, an IOptions object will be instanciate, but what if you just want to use this object in one method of your controller ?
There are couple of options that I can think of:
From within the action, you can do
var options = HttpContext.RequestServices.GetRequiredService<IOptions<ActiveDirectoryOptions>>().Options;
You can have a parameter to the action which is decorated with FromServicesAttribute. This attribute will cause the parameter value to be retrieved from the DI.
Example:
public IActionResult Index([FromServices] IOptions<ActiveDirectoryOptions> options)
I prefer #2 over #1 as in case of unit testing it gives you information on all dependent pieces.

Related

How can I use different modules based on application settings?

I am new in asp.net core and I would like to set the property value during start up in asp.net core web API.
The data is fetched from Data base and I need the values through out the application and it should be called only once.
There is two states in my project lets assume it to be A and B. In A there is one set of items are shown and in the B there are different items shown. I get a application setting data from the Database and on the basis of that data i will either show the A module or the B module through out the application life.
There are no global variables in .NET, much less C#. Storing configuration data in global, static properties is a bad idea because it ties your code with the static class that holds those properties, making it harder to write or test code.
Configuration Middleware
ASP.NET Core solves this through the Configuration middleware which can read configuration settings from multiple providers, including files, databases and even in-memory collections. The Configuration overview article shows how to use a dictionary as a configuration source :
var builder = WebApplication.CreateBuilder(args);
var Dict = new Dictionary<string, string>
{
{"MyKey", "Dictionary MyKey Value"},
{"Position:Title", "Dictionary_Title"},
{"Position:Name", "Dictionary_Name" },
{"Logging:LogLevel:Default", "Warning"}
};
builder.Host.ConfigureAppConfiguration((hostingContext, config) =>
{
config.Sources.Clear();
config.AddInMemoryCollection(Dict);
config.AddEnvironmentVariables();
if (args != null)
{
config.AddCommandLine(args);
}
});
builder.Services.AddRazorPages();
var app = builder.Build();
After that, all classes can retrieve the configuration values, no matter where they come from, through the IConfiguration interface :
public class TestModel : PageModel
{
// requires using Microsoft.Extensions.Configuration;
private readonly IConfiguration Configuration;
public TestModel(IConfiguration configuration)
{
Configuration = configuration;
}
public ContentResult OnGet()
{
var myKeyValue = Configuration["MyKey"];
var title = Configuration["Position:Title"];
var name = Configuration["Position:Name"];
var defaultLogLevel = Configuration["Logging:LogLevel:Default"];
return Content($"MyKey value: {myKeyValue} \n" +
$"Title: {title} \n" +
$"Name: {name} \n" +
$"Default Log Level: {defaultLogLevel}");
}
}
In your case you could load the settings from a database and register them as an in-memory source in your Program.cs :
var Dict=LoadDataAsDictionary();
...
config.AddInMemoryCollection(Dict);
...
Dependency Injection Middleware
Another option is to load the data as a strongly typed object and register it as a Signleton instance using the Dependency Injection middleware :
var builder = WebApplication.CreateBuilder(args);
MyCacheData cacheData=LoadStronglyTypedData();
builder.Services.AddSingleton(cachedata);
This class can be injected into pages and controllers just like other DI services or IConfiguration :
public class TestModel : PageModel
{
private readonly MyCacheData Data;
public TestModel(MyCacheData data)
{
Data = data;
}
A general solution to this problem would be to configure a singleton object to be registered in the ServiceCollection.
services.AddSingleton<ISingletonObject, SingletonObject>();
The constructor for such an object would load the necessary data, and then use constructor injection to load the singleton object into each class (e.g. controller).
public class SomeController(ISingletonObject singletonObject)
{
// singleton object will be injected with it's pre-loaded data available
}
The constructor for object SingletonObject will only be executed once, regardless of how many other constructors it is injected into by the service collection.

Accessing top-level fields in AppSettings.json from injected configuration class

Say we have such AppSettings.json
{
"Region": Europe,
"WeirdService": {
"JustField": "value"
}
}
Registering WeirdService settings in separate, singleton class (or using options pattern) is fine, just:
service.AddSingleton(configuration.GetSection("WeirdService").Get<WeirdService>();
And at this point it's fine. I don't know however how to deal cleanly with this top-level properties like Region in my example.
I know I can just inject IConfiguration and use config.GetValue<string>("Region") or just access configuration directly, but I wonder if there is some clean, better way without hardcoding this stuff in services.
Edit
I forgot to mention. Team I'm currently working with uses .NET Core 3.1 as it's current LTS release.
I think the easiest way would be to just create a class for the toplevel keys. In your case you could create something like AppConfig with the single property Region. Then you just register it without getting a config section using the Configuration object, the Configure methods asks for a Configuration interface anyway and not a ConfigurationSection.
AppConfig:
public class AppConfig
{
public string? Region { get; set; }
}
Registration:
public static IServiceCollection AddOptions(this IServiceCollection services, IConfiguration configuration)
{
return services.Configure<AppConfig>(configuration);
}
Usage:
public class ExampleConsumer
{
public ExampleConsumer(IOptions<AppConfig> appConfig) {}
}
You got two options
Don't have any top level fields
All top level fields would go one level in. Your configuration would look something like:
{
"App": {
"Region": "east-us-2",
"ShowMaintenancePrompt": false
},
// other options follow
}
The advantage of this approach is you can keep adding to "App" as your application grows, and continue to use the options pattern.
Gather top-level fields into a class, and register that with DI
For a configuration like:
{
"Region": "east-us-2"
}
Create a AppConfig class like:
internal class AppConfig
{
public string? Region { get; set; }
}
And register this class with the DI:
var toplevelConfig = new AppConfig {
Region = configuration.GetValue<string>("Region")
};
services.AddSingleton<AppConfig>(toplevelConfig);
You can now inject AppConfig anywhere you'd like.
The only minor downside to this is that you cannot use the options pattern anymore.
Avoid injecting IConfiguration directly.
Updated
Method 1 Preferred way
The preferred way to read related configuration values is using the options pattern.
For more detail about the options pattern check the link below.
https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-5.0
"WeirdService": {
"JustField": "value"
}
Create the WeiredServiceOptions class.
public class WeiredServiceOptions
{
public const string WeiredService = "WeiredService";
public string JustField { get; set; }
}
An options class:
Must be non-abstract with a public parameterless constructor.
All public read-write properties of the type are bound.
Fields are not bound. In the preceding code, WeiredService is not bound. The Position property is used so the string WeiredService doesn't need to be hardcoded in the app when binding the class to a configuration provider.
Calls ConfigurationBinder.Bind to bind the WeiredServiceOptions class to the WeiredService section.
var weiredServiceOptions = new PositionOptions();
configuration.GetSection(PositionOptions.Position).Bind(positionOptions);
An alternative approach when using the options pattern is to bind the WeiredService section and add it to the dependency injection service container. In the following code, WeiredServiceOptions is added to the service container with Configure and bound to the configuration
services.Configure<WeiredServiceOptions>(Configuration.GetSection(
WeiredServiceOptions.Position));
and then read the WeiredService Options.
private readonly WeiredServiceOptions _options;
public YourClassContructor(IOptions<WeiredServiceOptions> options)
{
_options = options
}
Console.WriteLine($"JustField: {_options.JustField}");
Method 2
service.AddSingleton(configuration.GetSection("WeirdService:JustField").value);
Method 3
service.AddSingleton(configuration["WeirdService:JustField"]);
Doing: services.AddSingleton(Configuration.GetSection("WeirdService").Get<WeirdService>());
will register WeirdService to the Ioc container without supporting Options pattern. Assuming this is what you are looking for. Here is what you could do:
Create a class with properties mathcing the top level configuration similar to the AppConfig class a couple of people have suggested in the answers
Register the AppConfig class with the Ioc as below:
services.AddSingleton(Configuration.Get<AppConfig>());
Note:
Doing Configuration.Get<AppConfig>() will bind matching properties on the AppConfig class with the corresponding values from appsettings.json
Feel free to skip properties for keys that you do not want to bind
The IConfiguration.Get<T> is an extension method defined in Microsoft.Extensions.Configuration.ConfigurationBinder just in case
You can bind section to a class as well, it allows clean usage without using much of the magic strings.
public class WeirdService{
public string JustField{ get; set;}
public string AnotherField{ get; set;}
}
In controller you can then define a field
private readonly WeirdService _weirdService = new WeirdService();
public UserController(IConfiguration configuration)
{
configuration.GetSection("WeirdService").Bind(_weirdService);
//_weirdService.JustField
//_weirdService.AnotherField
}
You can access Region field using
configuration.GetValue<string>("Region")
or other way is
public Startup(IConfiguration configuration, IWebHostEnvironment
webHostEnvironment)
{
Configuration = configuration;
environment = webHostEnvironment;
}
then you can just use
Configuration["Region"]

no need of recompile when configuration changes in ASP.NET Core

I'm a beginner in ASP.NET Core, I was reading a book which says:
In ASP.NET Core, you finally get the ability to edit a file and have the configuration of your application automatically update,
without having to recompile or restart.
and it also says that when using strong typed setting with IOptions interface:
Registers the IOptions interface in the DI container as a singleton, with the final bound POCO object in the Value property.
So here is my question, if the implementation of IOptions is singleton, which means the app will get same instance of the service all the time. If thats the case, when the configuration files change, how can the app doesn't need to recompile to reflect the latest change? ( if the IOptions is singleton, the POCO object is always the same too)
If you use IOptions<T> interface then Value property will always return the same value set during configuration. In order to get updated values every time file has been changed you should inject IOptionsMonitor<T> or IOptionsSnapshot<T> interface.
Startup.cs
services.Configure<SomeOptions>(Configuration.GetSection("ConfigSection"));
TestController.cs (with IOptions)
public class TestController : Controller
{
private readonly IOptions<SomeOptions> _options;
public TestController(IOptions<SomeOptions> options)
{
_options = options;
}
[HttpGet]
public async Task<IActionResult> GetConfig()
{
return Json(_options.Value); //returns same value every time
}
}
TestController.cs (with IOptionsMonitor)
public class TestController : Controller
{
private readonly IOptionsMonitor<SomeOptions> _options;
public TestController(IOptionsMonitor<SomeOptions> options)
{
_options = options;
}
[HttpGet]
public async Task<IActionResult> GetConfig()
{
return Json(_options.CurrentValue); //returns recalculated value
}
}

ASP.Net Core 2 Unable to resolve service for type

I'm attempting to register my own custom options. I have, in my ASP.Net project (Kimble.API), an appsettings.json file. It looks like this:
{
"NotificationHub": {
"AccountName": "my-notification-hub-name",
"ConnectionString": "my-secret-connection-string"
}
}
In my .Net Standard library (Kimble.Core), which the API project references, I have a class NotificationHubOptions:
public class NotificationHubOptions
{
public string AccountName { get; set; }
public string ConnectionString { get; set; }
}
Back to the API project.
In my Startup.cs file, in the ConfigureServices method, I register the options:
services.Configure<NotificationHubOptions>(configuration.GetSection("NotificationHub"));
I've checked, and the registration does show up in the services collection.
My controller's constructor looks like this:
public MyController(NotificationHubOptions options)
{
_notificationHubOptions = options;
}
However, when I try to call a method on the controller, I always get an exception:
System.InvalidOperationException: 'Unable to resolve service for type 'Kimble.Core.Options.NotificationHubOptions' while attempting to activate 'Kimble.API.Controllers.MyController'.'
This all worked before I moved the NotificationHubOptions class to my Core project. However, I can't see why that should matter at all.
You need to inject IOptions<TOptions>, like so:
public MyController(IOptions<NotificationHubOptions> options)
{
_notificationHubOptions = options.Value;
}
When you use Configure, you are registering a callback to configure the options instance for that type when it creates it. In this case using the configuration section to bind data to the options object.
So the options class itself is not in DI.
If you wanted that you could do this:
var options = Configuration.GetSection("NotificationHub").Get<NotificationHubOptions>();
services.AddSingleton<NotificationHubOptions>(options);

Save configuration through custom options class in asp.net core

I wrote a Configuration provider according to this (Basic sample of Entity Framework custom provider) article.
There I also override the Set method of the base class to save the configuration back to the database.
public virtual void Set(string key, string value)
{
// save key / value pair to db.
}
This also works fine if I save the configuration like this:
IConfiguration configuration = ...
configuration["Key"] = "value";
But this does not work if I want to save it through a custom options class:
public class MyOptions
{
public string Key { get; set; }
}
public class HomeController : Controller
{
private readonly MyOptions _options;
public HomeController(IOptions<MyOptions> optionsAccessor)
{
_options = optionsAccessor.Value;
}
public IActionResult Index()
{
_options.Key = "new value"; // <-- not calling the set in my provider
}
}
Has someone an idea how to solve this?
Update
I know that the configuration framework is not able to track changes I did to MyOptions and therefor it cannot update it automagically. But is there an other way to trigger to save the configuration?
If your options types are simple (with no nested classes and collections), you can achieve storing changes to IConfigurationProvider after modifying you typed options class by using MutableOptions NuGet Package (I am the author of that package):
IOptionsMutator<SimpleOptions> optionsMutator;
optionsMutator.Mutate(options => options with { Bar = 42 });
Essentially what IOptionsMutator implementation does is:
Gets IOptionMonitor.CurrentValue.
Calls your mutator function on it to get modified options instance.
Compares which properties have changed using reflection.
Writes changed properties values to IConfiguration.

Categories