Search code examples
c#asp.net-coresimple-injector

How do I use ASP.NET Core App Settings with Simple Injector


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?


Solution

  • 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 injecting IOptions<T> dependencies into your application components. Instead let components depend directly on configuration objects and register those objects as instances (using RegisterInstance). 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 an IOptions<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 when IOptions<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 to IOptions<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 to Configuration.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 on IOptions<T> might still be useful when bootstrapping the application, but not as a dependency anywhere else in your application. The IOptions<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));