How to get IOptions in ConfigureServices method? - c#

I have asp.net core application. I want to use IOptions pattern to inject values from appsettings.json. So I have a class SecurityHeaderOptions, and also have target class SecurityHeadersBuilder whose constructor takes IOptions<SecurityHeaderOptions> as parameter.
I know that .net core can implicitly create instance of SecurityHeadersBuilder by injecting IOptions<SecurityHeaderOptions> after registering both with container.
However i want to explicitly create instance of SecurityHeadersBuilder, call one of its method and then register the instance with the container.
public sealed class SecurityHeaderOptions
{
public string FrameOption { get; set; }
public string XssProtection { get; set; }
}
public class SecurityHeadersBuilder
{
private readonly SecurityHeaderOptions _options = null;
public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
{
_options = options.Value;
}
public SecurityHeadersBuilder AddDefaultPolicy()
{
AddFrameOptions();
AddConetntSecurityPolicy();
return this;
}
}
ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));
services.AddScoped<SecurityHeadersBuilder>(provider =>
new SecurityHeadersBuilder(?????).AddDefaultPolicy())
}
Questions
1> If i am explicitly passing options into constructor, do i need to register SecurityHeaderOptions with the container using service.Configure method?
2> Configuration.GetSection("SecurityHeaderOptions") can't return instance of IOptions<SecurityHeaderOptions> , instead it returns IConfigurationSection?
3>Either way, how do I retrieve and pass SecurityHeaderOptions into SecurityHeadersBuilder's constructor?

Using .NET Core 2 and not having a provider available (or caring to add it) in ConfigureServices I opted to go with something like this (using OP code as example):
public void ConfigureServices(IServiceCollection services)
{
// secOpts available for use in ConfigureServices
var secOpts = Configuration
.GetSection("SecurityHeaderOptions")
.Get<SecurityHeaderOptions>();
...
}

This is how I register options and inject into SecurityHeadersBuilder
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));
services.AddScoped<SecurityHeadersBuilder>(provider =>
{
var option = provider.GetService<IOptions<SecurityHeaderOptions>>();
return new SecurityHeadersBuilder(option)
.AddDefaultPolicy();
});
}

Docs specifically say:
Don't use IOptions<TOptions> or IOptionsMonitor<TOptions> in Startup.ConfigureServices. An inconsistent options state may exist due to the ordering of service registrations.
So you'll have to access the configuration some other way from Startup.ConfigureServices, e.g. Quinton's answer

Firstly you need to add a second constructor to SecurityHeadersBuilder, that takes a plain SecurityHeaderOptions:
public class SecurityHeadersBuilder
{
private readonly SecurityHeaderOptions _options;
public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
{
_options = options.Value;
}
public SecurityHeadersBuilder(SecurityHeaderOptions options)
{
_options = options;
}
public SecurityHeadersBuilder AddDefaultPolicy()
{
AddFrameOptions();
AddContentSecurityPolicy();
return this;
}
}
Then the answer entirely depends on whether or not you need to use those options outside of your Startup class.
If not, you can simply use the Microsoft.Extensions.Configuration.ConfigurationBinder.Get<T>() extension method:
services.AddScoped<SecurityHeadersBuilder>(provider =>
{
var options = Configuration.GetSection("SecurityHeaderOptions")
.Get<SecurityHeaderOptions>();
return new SecurityHeadersBuilder(options)
.AddDefaultPolicy();
});
(you can then delete the SecurityHeadersBuilder constructor that takes IOptions<SecurityHeaderOptions>).
If you will need to use these options elsewhere, then you can combine the above approach with the Microsoft.Extensions.DependencyInjection.OptionsConfigurationServiceCollectionExtensions.Configure() extension method:
var optionsSection = Configuration.GetSection("SecurityHeaderOptions");
services.Configure<SecurityHeaderOptions>(optionsSection);
services.AddScoped<SecurityHeadersBuilder>(provider =>
{
var options = optionsSection.Get<SecurityHeaderOptions>();
return new SecurityHeadersBuilder(options)
.AddDefaultPolicy();
});

Regarding your questions:
1. Yes, you need to register the options, but I believe you are doing it the wrong way (at least by your example). You should register as this:
services.Configure<SecurityHeaderOptions>(Configuration.GetSection("SecurityHeaderOptions"));
2. I believe that the correct registration I refer above returns what you are expecting.
3. Just registering it and placing it on the SecurityHeaderBuilder constructor is enough. You do not need (neither does the default .NET Core IOC container allows) to pass constructor parameters when registering it. For that you would need to use other IOC's such as Autofac.
But you need to register SecurityHeadersBuilder in order to use it within other classes.
Just use an interface for that.
public interface ISecurityHeadersBuilder
{
SecurityHeadersBuilder AddDefaultPolicy();
}
public class SecurityHeadersBuilder : ISecurityHeadersBuilder
{
private readonly SecurityHeaderOptions _options = null;
public SecurityHeadersBuilder(IOptions<SecurityHeaderOptions> options)
{
_options = options.Value;
}
public SecurityHeadersBuilder AddDefaultPolicy()
{
AddFrameOptions();
AddContentSecurityPolicy();
return this;
}
}
Then, just register it in startup.cs as this
services.AddScoped<ISecurityHeadersBuilder, SecurityHeadersBuilder>();

You could do something like this
public static class IConfigurationExtensions
{
public static TypedConfiguration<SecurityHeaderOptions> GetSecurityHeaderOptions(this IConfiguration configuration)
{
return new TypedConfiguration<SecurityHeaderOptions>(configuration.GetSection("SecurityHeaderOptions"));
}
}
public class TypedConfiguration<T> where T : class
{
public TypedConfiguration(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public T Value => Configuration.Get<T>();
public void InitializeOptions(IServiceCollection services)
{
services.Configure<T>(Configuration);
}
}
Now from single place you've created object that has both IConfiguration, typed SecurityHeaderOptions and helper method for registering IOptions injection for that class.
Use it like this
public void ConfigureServices(IServiceCollection services)
{
var wrappedOptions = Configuration.GetSecurityHeaderOptions();
wrappedOptions.InitializeOptions(services);
var options = Options.Create(wrappedOptions.Value);
services.AddScoped<SecurityHeadersBuilder>(provider =>
new SecurityHeadersBuilder(options).AddDefaultPolicy());
}

Related

Configure IOptions<T> with ServiceCollection when T is Type

I need to register an IOptions<T> with a .NET Core ServiceCollection.
Normally the method would be something like this:
var configSection = configuration.GetSection("sectionName");
serviceCollection.Configure<KnownClassName>(configSection);
This registers a strongly typed IOptions<KnownClassName> with the container.
I need to register an IOptions<UnknownName>. I have the Type of the class. I can't seem to find a method that will allow me to register the Type and the configuration section.
This is what am attempting to do:
interface ILoggingProvider
{
Type GetSettingType();
string ProviderName {get;}
}
IList<ILoggingProvider> loggingProviders = GetLoggingProviders();
foreach(var provider in loggingProvider)
{
var providerSection = configuration.GetSection(providerSection);
var providerSettingType = provider.GetSettingType();
// can't find an overload or other method to do the same thing as
// serviceCollection.Configure<LoggerSettings>(providerSection);
serviceCollection.Configure(providerSection, providerSettingType);
}
I don't think that this is possible; checking the source of IServiceCollection.Configure<TOptions> shows that the entire options/configuration functionality is built around generic types.
Also, as far as I know, there is no non-generic IOptions interface, so there is not really a justification for providing a non-generic configuration registration method.
However, this implies that your ILoggingProvider instances have to make use of IOptions<UnknownName> themselves, else they would not be able to retrieve the configuration from the DI container. In this case, you may either expose the options class of the particular providers, or add an (extension-)method ILoggingProvider.RegisterOptions(IServiceCollection, IConfiguration) which does this registration itself.
In Startup.cs, you would then simply call
provider.RegisterOptions(serviceCollection, providerSection);
for each provider.
This of course requires write access to the source code of the ILoggingProvider implementations.
You could use services.ConfigureOptions, if it is possible to return type that implements IConfigureOptions<T> from your ILoggingProvider.GetSettingType()
public class CustomConfiguration
{
public string Data { get; set; }
}
public class CustomConfigurationOptions : IConfigureOptions<CustomConfiguration>
{
private readonly IConfiguration configuration;
public CustomConfigurationOptions(IConfiguration configuration)
{
this.configuration = configuration;
}
public void Configure(CustomConfiguration options)
{
var optionsFromConfig = configuration
.GetSection(nameof(CustomConfiguration))
.Get<CustomConfiguration>();
options.Data = optionsFromConfig.Data;
}
}
public class LoggingProvider
{
public Type Type { get; set; }
}
public class Startup
{
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();
var loggingProviders = new List<LoggingProvider>()
{
new LoggingProvider()
{
Type = typeof(CustomConfigurationOptions)
}
};
foreach (var loggingProvider in loggingProviders)
{
services.ConfigureOptions(loggingProvider.Type);
}
}
...
}

.NET Core HealthCheck - Add HealthCheck with dependency injection and parameters

I have different classes which inherit of a base class. The base class implements the interface IHealthCheck. Each class has a constructor which need a logger and parameters according to the class.
For example :
public ConnectionHealthCheck(ILogger logger, string address)
: base(logger)
{
Address = address;
}
I have a appSettings.json which allows me to configure several diagnostics to do in my Health Check service.
I get the list of diagnostics in my App.xaml.cs and i'm trying to add them in the HealthCheck list.
The problem is that I cannot do a dependency injection with parameters next to it and I don't know what is the best solution to do it...
Here is some parts of my code.
The OnStartup method :
protected override void OnStartup(StartupEventArgs e)
{
var a = Assembly.GetExecutingAssembly();
using var stream = a.GetManifestResourceStream("appsettings.json");
Configuration = new ConfigurationBuilder()
.AddJsonStream(stream)
.Build();
var host = new HostBuilder()
.ConfigureHostConfiguration(c => c.AddConfiguration(Configuration))
.ConfigureServices(ConfigureServices)
.ConfigureLogging(ConfigureLogging)
.Build();
[...] }
The configureService Method :
private void ConfigureServices(IServiceCollection serviceCollection)
{
// create and add the healthCheck for each diag in the appSettings file
List<DiagnosticConfigItem> diagnostics = Configuration.GetSection("AppSettings:Diagnostics").Get<List<DiagnosticConfigItem>>();
diagnostics.ForEach(x => CreateHealthCheck(serviceCollection, x));
[...] }
And the method CreateHealthCheck where is the problem :
private void CreateHealthCheck(IServiceCollection serviceCollection, DiagnosticConfigItem configItem)
{
EnumDiagType type;
try
{
type = (EnumDiagType)Enum.Parse(typeof(EnumDiagType), configItem.Type, true);
}
catch (Exception)
{
throw new Exception("Diagnostic type not supported");
}
switch (type)
{
case EnumDiagType.Connection:
serviceCollection.AddHealthChecks().AddCheck(nameof(ConnectionHealthCheck), new ConnectionHealthCheck(???, configItem.Value));
break;
case EnumDiagType.Other:
[...] }
As you can see, I cannot create the instance of the ConnectionHealthCheck class because I cannot reach the ILogger object...
So how can I do it ? I think about different solutions but I don't have the answer or the way to do it
Build the HealthCheck service not in the App.xaml.cs but after ? (In a view model for exemple where I have access to the serviceCollection and the logger)
Find a way to get the logger to use it in the CreateHealthCheck method ?
Do something like that but I don't know when I can pass the parameters
serviceCollection.AddHealthChecks().AddCheck<ConnectionHealthCheck>(nameof(ConnectionHealthCheck));
You can use HealthCheckRegistration to register your class (it should implement IHealthCheck), it has constructors accepting delegate Func<IServiceProvider,IHealthCheck> which allows you to use IServiceProvider to resolve required parameters to create an instance of your healthcheck class. Something like this:
public static class ConnectionHealthCheckBuilderExtensions
{
const string DefaultName = "example_health_check";
public static IHealthChecksBuilder AddConnectionHealthCheck(
this IHealthChecksBuilder builder,
string name,
DiagnosticConfigItem configItem,
HealthStatus? failureStatus = default,
IEnumerable<string> tags = default)
{
return builder.Add(new HealthCheckRegistration(
name ?? DefaultName,
sp => new ConnectionHealthCheck(sp.GetRequiredService<ISomeService>(), configItem.Value),
failureStatus,
tags));
}
}
See this part of docs for more details.
The .NET Core in-built DI can inject the components on the Constructor level.
So use the following way, which I use in my ASP.NET Core Projects.
public class Startup
{
public Startup(IWebHostEnvironment environment, IConfiguration configuration, ILoggerFactory loggerFactory)
{
Environment = environment;
Configuration = configuration;
LoggerFactory = loggerFactory;
}
public IConfiguration Configuration { get; }
public ILoggerFactory LoggerFactory { get; }
public IWebHostEnvironment Environment { get; }
private void ConfigureServices(IServiceCollection serviceCollection)
{
List<DiagnosticConfigItem> diagnostics = Configuration.GetSection("AppSettings:Diagnostics").Get<List<DiagnosticConfigItem>>();
diagnostics.ForEach(x => CreateHealthCheck(serviceCollection, x, LoggerFactory));
}
private void CreateHealthCheck(IServiceCollection serviceCollection, DiagnosticConfigItem configItem)
{
// Create a ILogger<T> based on your Type by
loggerFactory.CreateLogger<MessagingServices>())
}
}
This might be crude, but hope this helps.

How to use dependency injectionen and argument in the same time?

I have a class
public TimeSeriesBusinessComponent(IContextRepository contextRepository) { ...
I pass the dependency injection through my class ServiceExtension, which I call from my Startup.cs.
Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddRepositories(Configurations);
services.AddBusinessComponents();
ServiceExtension:
public static void AddRepositories(this IServiceCollection serviceCollection, Configurations con)
{
serviceCollection.AddScoped<IContextRepository>(serviceProvider =>
{
string createContextFunctionName = con.Get(CREATE_CONTEXT_FUNCTION_NAME);
return new ContextRepository(createContextFunctionName);
});
}
public static void AddBusinessComponents(this IServiceCollection serviceCollection)
{
serviceCollection.AddScoped<ITimeSeriesBusinessComponent, TimeSeriesBusinessComponent>();
}
Later I use my Class TimeSeriesBusinessComponent like that:
public class TimeSeriesController : Controller
{
private ITimeSeriesBusinessComponent BusinessComponent { get; }
public TimeSeriesController(
ITimeSeriesBusinessComponent businessComponent
)
{
BusinessComponent = businessComponent;
}
But now I need to transfer a argument from my ServicExtension to my TimeSeriesBusinessComponent too, like ..
public TimeSeriesBusinessComponent(IContextRepository contextRepository,. string value) { ...
The string value I get from my configurations (EnvirenmentVariables).
How can I use dependency injection and normal argument in the same time?
So, since your string value comes from Environment variable, you should use IOptions.
You create your MyOptions class (that will hold a value you need)
You load a value from your environment (or from wherever) in your Startup.cs
You register your options with DI container
You inject it into target class just like any other service
for example:
services.Configure<MyOptions>(options =>
{
options.MyString = Environment.GetEnvironmentVariable("MY_STRING_KEY");
});
and then you use it like:
public TimeSeriesBusinessComponent(IContextRepository contextRepository, IOptions<MyOptions> myOptions) {
_myOptionsOptions = options.MyString;
}
docs: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-2.2

Using Factory Pattern with ASP.NET Core Dependency Injection

I need the ASP.Net Core dependency injection to pass some parameters to the constructor of my GlobalRepository class which implements the ICardPaymentRepository interface.
The parameters are for configuration and come from the config file and the database, and I don't want my class to go and reference the database and config itself.
I think the factory pattern is the best way to do this but I can't figure out the best way to use a factory class which itself has dependencies on config and database.
My startup looks like this currently:
public class Startup
{
public IConfiguration _configuration { get; }
public IHostingEnvironment _environment { get; }
public Startup(IConfiguration configuration, IHostingEnvironment environment)
{
_configuration = configuration;
_environment = environment;
}
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<IDbRepository, DbRepository>();
var connection = _configuration.GetConnectionString("DbConnection");
services.Configure<ConnectionStrings>(_configuration.GetSection("ConnectionStrings"));
services.AddDbContext<DbContext>(options => options.UseSqlServer(connection));
services.AddScoped<ICardPaymentRepository, GlobalRepository>();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IRFDbRepository rFDbRepository)
{
...
}
}
The GlobalRepository constructor looks like this:
public GlobalRepository(string mode, string apiKey)
{
}
How do I now pass the mode from configuration and the apiKey from the DbRepository into the constructor from Startup?
Use the factory delegate overload when registering the repository
//...
string mode = "get value from config";
services.AddScoped<ICardPaymentRepository, GlobalRepository>(sp => {
IDbRepository repo = sp.GetRequiredService<IDbRepository>();
string apiKey = repo.GetApiKeyMethodHere();
return new GlobalRepository(mode, apiKey);
});
//...
Alternative using ActivatorUtilities.CreateInstance
//...
string mode = "get value from config";
services.AddScoped<ICardPaymentRepository>(sp => {
IDbRepository repo = sp.GetRequiredService<IDbRepository>();
string apiKey = repo.GetApiKeyMethodHere();
return ActivatorUtilities.CreateInstance<GlobalRepository>(sp, mode, apiKey);
});
//...
You might want to also check these links...
https://github.com/Microsoft/AspNetCoreInjection.TypedFactories
https://espressocoder.com/2018/10/08/injecting-a-factory-service-in-asp-net-core/
With regard to the last link the code is basically:
public class Factory<T> : IFactory<T>
{
private readonly Func<T> _initFunc;
public Factory(Func<T> initFunc)
{
_initFunc = initFunc;
}
public T Create()
{
return _initFunc();
}
}
public static class ServiceCollectionExtensions
{
public static void AddFactory<TService, TImplementation>(this IServiceCollection services)
where TService : class
where TImplementation : class, TService
{
services.AddTransient<TService, TImplementation>();
services.AddSingleton<Func<TService>>(x => () => x.GetService<TService>());
services.AddSingleton<IFactory<TService>, Factory<TService>>();
}
}
I think castle windsor's typed factories dispose of all they created when they themselves are disposed (which may not be always the best idea), with these links you would probably have to consider if you are still expecting that behaviour. When I reconsidered why I wanted a factory I ended up just creating a simple factory wrapping new, such as:
public class DefaultFooFactory: IFooFactory{
public IFoo create(){return new DefaultFoo();}
}
I'll show the minimal example for the factory that resolves ITalk implementation by a string key. The solution can be easily extended to a generic factory with any key and entity type.
For the sake of example let's define the interface ITalk and two implementations Cat and Dog:
public interface ITalk
{
string Talk();
}
public class Cat : ITalk
{
public string Talk() => "Meow!";
}
public class Dog : ITalk
{
public string Talk() => "Woof!";
}
Now define the TalkFactoryOptions and TalkFactory:
public class TalkFactoryOptions
{
public IDictionary<string, Type> Types { get; } = new Dictionary<string, Type>();
public void Register<T>(string name) where T : ITalk
{
Types.Add(name, typeof(T));
}
}
public class TalkFactory
{
private readonly IServiceProvider _provider;
private readonly IDictionary<string, Type> _types;
public TalkFactory(IServiceProvider provider, IOptions<TalkFactoryOptions> options)
{
_provider = provider;
_types = options.Value.Types;
}
public ITalk Resolve(string name)
{
if (_types.TryGetValue(name, out var type))
{
return (ITalk)_provider.GetRequiredService(type);
}
throw new ArgumentOutOfRangeException(nameof(name));
}
}
Add extension method for simple implementations registration:
public static class FactoryDiExtensions
{
public static IServiceCollection RegisterTransientSpeaker<TImplementation>(this IServiceCollection services, string name)
where TImplementation : class, ITalk
{
services.TryAddTransient<TalkFactory>();
services.TryAddTransient<TImplementation>();
services.Configure<TalkFactoryOptions>(options => options.Register<TImplementation>(name));
return services;
}
}
And register the Cat and Dog implementations:
services
.RegisterTransientSpeaker<Cat>("cat")
.RegisterTransientSpeaker<Dog>("dog");
Now you can inject the TalkFactory and resolve the implementation by the name:
var speaker = _factory.Resolve("cat");
var speech = speaker.Talk();
The trick here is Configure<TOptions(). This method is additive, which means you can call it multiple times to configure the same instance of TalkFactoryOptions.
As I said this example can be converted into a generic factory and add the ability to register factory delegate instead of a concrete type. But the code will be too long for SO.
I've been running up against the same issue and solved this by registering a set of open generics for IFactory<TService>, IFactory<T, TService>, IFactory<T1, T2, TService> etc. A single call on startup to add this facility then allows any IFactory<...> to be injected / resolved, which will instantiate an instance of TService for a given set of argument types, provided a constuctor exists whose last parameters match the T* types of the factory generic. Source code, NuGet package and explanatory blog article below:
https://github.com/jmg48/useful
https://www.nuget.org/packages/Ariadne.Extensions.ServiceCollection/
https://jon-glass.medium.com/abstract-factory-support-for-microsoft-net-dependency-injection-3c3834894c19
An alternative to the other answers. Follow the options pattern.
First introduce a strong type for your configuration;
public class RespositoryOptions {
public string Mode { get; set; }
public string ApiKey { get; set; }
}
public GlobalRepository(IOptions<RespositoryOptions> options) {
// use options.Value;
}
You could still use a service factory method to unwrap the IOptions<RespositoryOptions> if you prefer. But then you lose the ability to verify that your service dependencies have all been met.
Then you can seed your options from configuration;
public void ConfigureServices(IServiceCollection services) {
...
services.Configure<RespositoryOptions>(_configuration.GetSection(name));
...
}
And write another service to update that options instance from other services, like a database;
public class ConfigureRespositoryOptions : IConfigureOptions<RespositoryOptions> {
private readonly IDbRepository repo;
public ConfigureRespositoryOptions(IDbRepository repo) {
this.repo = repo;
}
public void Configure(RespositoryOptions config) {
string apiKey = repo.GetApiKeyMethodHere();
}
}

ASP.NET 5 (vNext) - Getting a Configuration Setting

I'm writing a basic app to learn ASP.NET 5. One area I find very confusing is configuration. Prior to ASP.NET 5, I could do the following:
var settingValue = ConfigurationManager.AppSettings["SomeKey"];
I would have lines of code like that sprinkled throughout my code. Now, in the vNext world, I have a config.json file that looks like this:
config.json
{
"AppSettings": {
"SomeKey":"SomeValue"
}
}
Then in Startup.cs, I have the following:
Startup.cs
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment environment)
{
Configuration = new Configuration()
.AddJsonFile("config.json");
}
From there, I'm totally stumped. I have MyClass.cs in /src/Website/Code/Models/MyClass.cs.
MyClass.cs
public class MyClass
{
public string DoSomething()
{
var result = string.Empty;
var keyValue = string.Empty; // TODO: What do I do here? How do I get the value of "AppSettings:SomeKey"?
return result;
}
}
How do I get the value of "AppSettings:SomeKey"?
ASP.NET 5 makes heavy use of Dependency Injection, so if you are also using Dependency Injection then this is very simple. If you examine the sample MVC6 project, you can see how this works:
First, there's a class AppSettings defined in Properties, which is a strongly-typed version of the options your class supports. In the sample project, this just contains SiteTitle.
public class AppSettings
{
public string SiteTitle { get; set; }
}
Then, this class is initialised through dependency injection in ConfigureServices. Configuration here is the one you created in the constructor of the Startup class.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<AppSettings>(Configuration.GetSubKey("AppSettings"));
// ...
}
Then, assuming your class is instantiated by the dependency injection container, you can simply ask for an IOptions and you'll get one. For example, in a controller you could have the following:
public class HomeController
{
private string _title;
public HomeController(IOptions<AppSettings> settings)
{
_title = settings.Options.SiteTitle;
}
}
I use ASP.NET 5 dependency injection, like so.
config.json:
{
"random": "Hello World!"
}
startup.cs:
public class Startup
{
public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
{
var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
.AddJsonFile("config.json");
Configuration = builder.Build();
}
public IConfiguration Configuration { get; set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddSingleton<IConfiguration>(sp => { return Configuration; });
}
public void Configure(IApplicationBuilder app)
{
app.UseMvc(routes =>
{
routes.MapRoute(name: "default", template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Controller:
public class HomeController : Controller
{
IConfiguration config;
public HomeController(IConfiguration config)
{
this.config = config;
}
public IActionResult Index()
{
var template = "<marquee>{0}</marquee>";
var content = string.Format(template, config.Get("random"));
return Content(content, "text/html");
}
}
I highly recommend using the OptionsModel instead of reading the configuration directly. It allows strong typed model binding to configuration.
Here is an example: GitHub.com/aspnet/Options/test/Microsoft.Extensions.Options.Test/OptionsTest.cs
For your particular case create a model:
class AppSettings {
public string SomeSetting {get;set;}
}
and then bind it to your configuration:
var config = // The configuration object
var options = ConfigurationBinder.Bind<AppSettings>(config);
Console.WriteLine(options.SomeSetting);
That way you don't have to worry from where the setting comes from, how it is stored or what is the structure. You simply predefine your options model and magic happens.
Use this:
var value = Configuration.Get("AppSettings:SomeKey");
Based on this blog post. The colon is similar to dot notation and is used for navigation down the hierarchy.
If you need the value in other classes, you should inject it in. ASP.NET has built in dependency injection, but if you just need one instance of MyClass you can new it up instead of setting up a DI container.
public IConfiguration Configuration { get; set; }
public Startup(IHostingEnvironment environment)
{
Configuration = new Configuration()
.AddJsonFile("config.json");
//generally here you'd set up your DI container. But for now we'll just new it up
MyClass c = new MyClass(Configuration.Get("AppSettings:SomeKey"));
}
public class MyClass
{
private readonly string Setting; //if you need to pass multiple objects, use a custom POCO (and interface) instead of a string.
public MyClass(string setting) //This is called constructor injection
{
Setting = setting;
}
public string DoSomething()
{
var result = string.Empty;
//Use setting here
return result;
}
}

Categories