I am new to ASP.NET Core and need some direction. I am trying to figure out a simple scenario with using app settings in ASP.NET Core while also using Simple Injector.
I first set up my Strongly-Type configuration settings as explained here by Rick Strahl. This works great.
public class MySettings
{
public string ApplicationName { get; set; }
}
appsetting.json
{
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
},
"MySettings": {
"ApplicationName": "Test Service"
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMvc();
// Add Options
services.AddOptions();
// Add our Config object so it can be injected
services.Configure<MySettings>(Configuration.GetSection("MySettings"));
services.AddSingleton(GetTestBroker(container));
//Simple Injector
//services.AddSingleton<IControllerActivator>(new SimpleInjectorControllerActivator(container));
//services.AddSingleton<IViewComponentActivator>(new SimpleInjectorViewComponentActivator(container));
}
In our other projects were are using Simple Injector for DI. After adding the Simple Injector package and configuring it per the instructions I see that my IOptions configuration breaks.
What I'd like to know is what would be best practice for implementing a configuration in ASP.NET Core and using another DI library like Simple Injector together?
The ASP.NET Core integration guide for Simple Injector states the following:
.NET Core contains a new configuration model based on an
IOptions<T>
abstraction. We advise against injectingIOptions<T>
dependencies into your application components. Instead let components depend directly on configuration objects and register those objects as instances (usingRegisterInstance
). This ensures that configuration values are read during application start up and it allows verifying them at that point in time, allowing the application to fail fast.Letting application components depend on
IOptions<T>
has some unfortunate downsides. First of all, it causes application code to take an unnecessary dependency on a framework abstraction. This is a violation of the Dependency Inversion Principle, which prescribes the use of application-tailored abstractions. Injecting anIOptions<T>
into an application component makes such component more difficult to test, while providing no additional benefits for that component. Application components should instead depend directly on the configuration values they require.Second,
IOptions<T>
configuration values are read lazily. Although the configuration file might be read upon application start up, the required configuration object is only created whenIOptions<T>.Value
is called for the first time. When deserialization fails, because of application misconfiguration for instance, such error will only be appear after the call toIOptions<T>.Value
. This can cause misconfigurations to stay undetected for much longer than required. By reading—and verifying—configuration values at application start up, this problem can be prevented. Configuration values can be injected as singletons into the component that requires them.To make things worse, in case you forget to configure a particular section (by omitting a call to
services.Configure<T>
) or when you make a typo while retrieving the configuration section (e.g. by supplying the wrong name toConfiguration.GetSection(name)
), the configuration system will simply supply the application with a default and empty object instead of throwing an exception! This may make sense when building framework or third-party components, but not so much for application development, as it easily leads to fragile applications.Because you want to verify the configuration at start-up, it makes no sense to delay reading it, and that makes injecting
IOptions<T>
into your application components sub optimal, to say the least. Depending onIOptions<T>
might still be useful when bootstrapping the application, but not as a dependency anywhere else in your application. TheIOptions<T>
architecture is designed for the framework and its components, and makes most sense in that particular context—not in the context of line-of-business applications.Once you have a correctly read and verified configuration object, registration of the component that requires the configuration object is as simple as this:
MyMailSettings mailSettings = config.GetSection("Root:SectionName").Get<MyMailSettings>(); // Verify mailSettings here (if required) container.Register<IMessageSender>( () => new MailMessageSender(mailSettings));