I have implemented a Custom ViewLocationExpander in a vnext project. I want to read a app setting value from the appsettings.json file in the ViewLocationExpander and hence the IOptions<> has been injected into the custom ViewLocationExpander 's constructor. However, while adding the custom ViewLocationExpander to the RazorViewEngine options an object of the ViewLocationExpander is required, which cannot be created due to the dependency.
Below is the code
public MyViewLocationExpander(IOptions<MyAppSettings> MyAppSettings)
{
var appSettings = MyAppSettings.Value;
client = appSettings.Client // client is a private field and is used in ExpandViewLocations function
}
MyAppSettings.cs is as below:
public class MyAppSettings
{
public string Client { get; set; }
}
In Startup.cs ConfigureServices method
services.Configure<RazorViewEngineOptions>(config =>
{
//config.ViewLocationExpanders.Add(new MyViewLocationExpander());
// MyViewLocationExpander cannot be created as it has a dependency on IOptions<MyAppSettings>
});
Any help on how to add the custom ViewLocationExpander to the RazorViewEngineOptions would be great.
One way to resolve services from the container is to resolve them in ExpandViewsMethod
using Microsoft.Extensions.DependencyInjection;
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
var service = context.ActionContext.HttpContext.RequestServices.GetService<IService>();
}
Related
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"]
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);
I'm creating a service that requires some config parameters and a logger. Here is the constructor for my service:
public StorageProvider(string directory, ILogger<StorageProvider> logger)
I just added the logger. I used to initalize it like this in my startup.cs:
services.AddSingleton<IStorageProvider>(
new StorageProvider(Configuration["TempStorage.Path"]));
The directory parameter comes from the config file, and the logger gets DI'ed. How do I setup my IStorageProvider?
You should do the following:
Wrap the configuration value TempStorage:Path into its own configuration class, e.g. StorageProviderSettings.
Let StorageProvider depend upon that new configuration class.
Register that configuration class as singleton into the ASP.NET configuration system.
Example:
public sealed class StorageProviderSettings
{
public readonly string TempStoragePath;
public StorageProviderSettings(string tempStoragePath)
{
if (string.IsNullOrWhiteSpace(tempStoragePath))
throw new ArgumentException(nameof(tempStoragePath));
this.TempStoragePath = tempStoragePath;
}
}
public sealed class StorageProvider : IStorageProvider
{
public StorageProvider(
StorageProviderSettings settings, ILogger<StorageProvider> logger)
{
// ...
}
}
// Registration
services.AddSingleton(new StorageProviderSettings(Configuration["TempStorage.Path"]));
services.AddSingleton<IStorageProvider, StorageProvider>();
Use the Options pattern as Tratcher suggests in a comment. Read more in the official docs on Configuration.
Basically you define a class to be hold the value you need:
public class StorageProviderOptions
{
public string TempStoragePath { get; set; }
}
Then in ConfigureServices you register the type:
services.Configure<StorageProviderOptions>();
In your code, you request IOptions<StorageProviderOptions> and set this to an instance of StorageProviderOptions:
public class SomeController
{
private readonly StorageProviderOptions _options;
public SomeController(IOptions<StorageProviderOptions> options)
{
_options = options.Value;
}
}
Finally, make sure you have an element in your configuration source that matches the TempStoragePath name. Alternately, you can register the option in ConfigureServices using code:
services.Configure<ServiceProviderOptions>(o => o.TempStoragePath = "temp");
I have created an ASP.NET Core 1.0.1 WebApi project and am trying to initialize an injected dependency with some custom options before using it in my controllers. After searching online I found a few articles (here, here and here explaining how to use IConfigureServices to do just this. Seems pretty simple! Unfortunately, I can't get it to work and I can't figure out why, I'm sure it must be a simple oversight..
I have created a simple project, and added the following classes to illustrate the most basic scenario:
public class Tester
{
public void Initialize(TestOptions options)
{
//do something with options.
}
}
public class TestConfigurator : IConfigureOptions<TestOptions>
{
private Tester _tester;
public TestConfigurator(Tester tester)
{
_tester = tester;
}
public void Configure(TestOptions options)
{
_tester.Initialize(options);
}
}
public class TestOptions
{
}
The 'Tester' class gets injected into the constructor of a Controller class:
[Route("api/[controller]")]
public class ValuesController : Controller
{
public ValuesController(Tester tester)
{
//do something with tester..
}
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
}
Finally, I have added the following configuration in ConfigureServices of the Startup class:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddOptions();
services.AddMvc();
services.AddSingleton<Tester, Tester>();
services.AddSingleton<IConfigureOptions<TestOptions>, TestConfigurator>();
}
When I run the project and call the 'api/values' call, the Tester class is created and injected into the ValuesController, but the TestConfigurator class never gets constructed and so the class never gets Initialized with the options class. What am I missing?
UPDATE
The answers below are of course all valid to this simplified example. I realize now that I oversimplified a bit, as the dependency I'm using (illustrated here as Tester) is from a 3rd party library, so I don't have the constructor to play with. Wrapping the 3rd party class in an extended class will do the trick, but if anybody has an alternative suggestion on manipulating it without modifying its constructor, then I'm still open to suggestions, thanks.
Ok, now I got it, I feel silly for all the edits.
you are using IOptions wrong, and it got me all confused.
implementing a custom IConfigurationOptions<> gives you the abilty to either configure your options from database, or to just use a different class (instead of a lambda)
what you are trying to do, is instantiate a Tester class based on those options, this is fine - but it's not the IConfigureOptions<> job.
in order to initialize your Tester class based on the TestOptions you should create a constructor on the Tester class that receives it like this
public class Tester
{
public Tester(IOptions<TestOptions> options)
{
//do something with options.
}
}
and what you are trying to do will work.
Taken from Asp.Net Core Configuration Documentation and adapted to your example
Assuming
public class TestOptions {
public string SomeOption { get; set; }
}
Options can be injected into your application using the
IOptions<TOptions> accessor service.
You could try abstracting Tester and registering that with the service collection.
public interface ITester {
//tester contract
}
public class Tester : ITester {
public Tester(IOptions<TestOptions> options) {
//do something with test options.
}
}
To setup the IOptions<TOptions> service you call the AddOptions
extension method during startup in your ConfigureServices method.
You configure options using the Configure<TOptions> extension method.
You can configure options using a delegate or by binding your options
to configuration:
public void ConfigureServices(IServiceCollection services) {
services.AddApplicationInsightsTelemetry(Configuration);
// Setup options with DI
services.AddOptions();
// Configure TestOptions using config by installing Microsoft.Extensions.Options.ConfigurationExtensions
services.Configure<TestOptions>(Configuration);
// Configure TestOptions using code
services.Configure<TestOptions>(testOptions => {
testOptions.SomeOption = "value1_from_action";
});
// Add framework services.
services.AddMvc();
// Add your services.
services.AddSingleton<ITester, Tester>();
}
And finally just refactor the controller to depend on the abstraction instead of the concretion.
[Route("api/[controller]")]
public class ValuesController : Controller {
public ValuesController(ITester tester) {
//do something with tester..
}
// GET api/values
[HttpGet]
public IEnumerable<string> Get() {
return new string[] { "value1", "value2" };
}
}
Where is TestOptions supposed to come from? Are you trying to get it mapped auto-magically from your settings file? I think you are over-thinking how this should work, unless there is some reason you have to use initialize instead of constructor injection.
All you are trying to do is make some options available to tester, right?
If so, just use the basic IOptions features - no need to go advanced with IConfigureOptions
public class Tester
{
private TestOptions _options;
public Tester(IOptions<TestOptions> options)
{
_options = options.Value;
}
}
// don't need this anymore
/* public class TestConfigurator : IConfigureOptions<TestOptions>
{
private Tester _tester;
public TestConfigurator(Tester tester)
{
_tester = tester;
}
public void Configure(TestOptions options)
{
_tester.Initialize(options);
}
}
*/
public class TestOptions
{
}
And then configure the options using one of the two methods below (depending on whether it comes from config or has to be manually constructed).
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddApplicationInsightsTelemetry(Configuration);
services.AddOptions();
services.AddMvc();
services.AddSingleton<Tester, Tester>();
// Configure TestOptions using config
services.Configure<TestOptions>(Configuration);
// Configure MyOptions using code
services.Configure<TestOptions>(testOptions =>
{
// initialize them here, e.g. testOptions.Foo = "Bar"
});
}
To access App Keys in a class library, do we need to do the following code in every class library and class where we need to access a AppKey?
public static IConfigurationRoot Configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
This is what I found in Microsoft docs, but this looks very redundant.
Startup class in a project as below
public class Startup
{
public IConfigurationRoot Configuration { get; set; }
public Startup()
{
var builder = new ConfigurationBuilder()
.AddJsonFile("appsettings.json");
Configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddEntityFramework().AddEntityFrameworkSqlServer()
.AddDbContext<DbContext>(options =>
options.UseSqlServer(Configuration["Data:MyDb:ConnectionString"]));
}
}
Then how should I inject this "IConfigurationRoot" in each class of a project. And do I have to repeat this Startup class in each class Library? Why is this not part of .NET Core Framework?
The recommended way is to use the options pattern, provided by Microsoft and used heavily in ASP.NET Core.
Basically you create a strong typed class and configure it in the Startup.cs class.
public class MySettings
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
and initialize it in the Startup class.
// load it directly from the appsettings.json "mysettings" section
services.Configure<MySettings>(Configuration.GetSection("mysettings"));
// do it manually
services.Configure<MySettings>(new MySettings
{
Value1 = "Some Value",
Value2 = Configuration["somevalue:from:appsettings"]
});
then inject these options everywhere you need it.
public class MyService : IMyService
{
private readonly MySettings settings;
public MyService(IOptions<MySettings> mysettings)
{
this.settings = mySettings.Value;
}
}
By the principle of Information Hiding in Object-Oriented Programming, most classes should not need to have access to your application configuration. Only your main application class should need to directly have access to this information. Your class libraries should expose properties and methods to alter their behavior based on whatever criteria their callers deem necessary, and your application should use its configuration to set the right properties.
For example, a DateBox shouldn't need to know how timezone information is stored in your application configuration file - all it needs to know is that it has a DateBox.TimeZone property that it can check at runtime to see what timezone it is in.