Practical configuration & DI in ASP.NET Core 2.0

This article is an extension to the .NET Core Series. Go have a look at the articles of this series, and run through the previous topics if not done already!

In this article, we’ll look at some real life code to see how configuration and DI are used in .NET Core 2.0 code.

[1] DI - Service Registration in .NET Core

.NET Core comes with in-built dependency injection (DI). Though practically optional, conventionally it is expected to build the whole application based on DI. That means, different modules and layers to not depend on each other directly, rather connect via some abstraction (good ol’ Dependency Inversion Principle). Talking of code you register all your dependency to the .NET Core IoC container at application startup and get them injected in the client code when required.

The Startup.cs class has a ConfigureServices method where all the services are registered. This method is called by the Main() method and it passes the IServiceCollection to the method, which is used to register services. A service can be registered with 3 different type of scopes or lifetimes.

  • Choice of service lifetimes
    • Transient: Creted each time they are requested
    • Scoped: Created once per http request
    • Singleton: Created once per lifetime of application

Following code shows the standard ways of registering services in .NET Core 2.0

//Startup.cs
public void ConfigureServices(IServiceCollection services)
{
    //with framework provided extension methods
    services.AddDbContext<MyDbContext>(opt => opt.UseInMemoryDatabase("MyDbName"));
    services.AddMvc(); //inject all services related to MVC
    //simple scoped service registration
    services.AddScoped<IRepository, Repository>();
    services.AddTransient<ISomeService, SomeService>();
    services.AddSingleton<IConfigBuilder, FileConfigBuilder>();
    //any type that needs injection/to be injected
    services.AddScoped<JustAnotherEntity>();
    //instance registration example, sp is IServiceProvider
    services.AddTransient<MyService>(sp => new MyService(
        "Some string argument",
        sp.GetService<ISomeService>()));
}

[2] Dependency Injection in .NET Core

The standard and ideal way to get a service instance (any dependency type that has been registered in above method) injected in a class is to use Constructor Injection. In simple words, add the dependencies as parameter to the constructor of the class, the DI framework will inject the actual instances as arguments at runtime.

Note: The DI framework can only inject constructor dependencies when the class is instantiated through DI, if a new instance is created directly with new or Reflection, DI will play no role in the process!

Following code shows simple constructor injection in ASP.NET Core controller.

public class TestController : Controller
{
    ISomeService _someService;

    //constructor injection
    public TestController(ISomeService service)
    {
        _someService = service;
    }

    public IActionResult Get()
    {
        //use injected service
        var result = _someService.SomeMethod();
        return new OkObjectResult(result);
    }
}

[3] Injecting service without constructor injection

Constructor injection is the ideal way to inject dependency in a class as it is conventional, very descriptive and readable. Also it makes the intention very clear that the code will not function if those dependencies are not provided.

But there can be cases, where one might need to get a service instance by explicit injection. This can be achieved by asking for an instance directly from IServiceProvider. This IServiceProvider itself can be injected through dependency injection. See code below

public class TestController : Controller
{
    IServiceProvider _serviceProvider;

    //constructor injection of IServiceProvider
    public TestController(IServiceProvider sp)
    {
        _serviceProvider = sp;
    }

    public IActionResult Get()
    {
        //get service instance from IServiceProvider
        ISomeService service = _serviceProvider.GetService(typeof(ISomeService));
        var result = service.SomeMethod();
        return new OkObjectResult(result);
    }
}

As mentioned above, the ideal way to define dependency is the constructor. But there can be some cases when one might want to use the IServiceProvider

  1. To create service instances conditionally at runtime
  2. To minimize the parameters of constructor. Only IServiceProvider can be injected in constructor, then later the actual services can be instantiated as and when necessary.

Service instance injection inside the ConfigureServices method

In some occasions, it might be required to get a service instance injected inside the ConfigureServices method itself! The way to do that is again using IServiceProvider. The IServiceCollection available to the method has an extension to create an IServiceProvider instance. See below

//using Microsoft.Extensions.DependencyInjection;
//Startup.ConfigureServices()
var someService = services.BuildServiceProvider()
    .GetService<ISomeService>();

Note:

  1. Before using this, the ISomeService and it’s dependecy graph must be registered
  2. For the Configure method, any service that has been registered, can be directly injected as method parameters

[4] Dependency Injection in Filters

Let’s say I have an ActionFilter like this, which simply adds a custom header to the response

public class MyHeaderFilterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add("MyHeader", "HeaderValue");
    }
}

This can be used as an attribute in controller or action method

[MyHeaderFilter]
public ActionResult Get()
{
    return new OkObjectResult(new { Id = 123, Name = "Hero" });
}

Now, if I have a constructor dependency in the filter, it CANNOT be used simply as an attribute. The dependency also cannot be injected.

public class MyHeaderFilterAttribute : ActionFilterAttribute
{
    ISomeService _service;

    public MyHeaderFilterAttribute(ISomeService someService)
    {
        _service = someService;
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        _service.DoStuff();
        context.HttpContext.Response.Headers.Add("MyHeader", "HeaderValue");
    }
}

Dependency injection to Filter in ASP.NET is little tricky. As filters cannot be used directly as attributes anymore, we need to get some work-arounds. The good news is the framework provides us some work-arounds. There are basically few ways to handle this.

Using ServiceFilter

For this,

  • register the filter with container in Startup
  • use ServiceFilter attribute with type of desired filter that needs service injection
//In Startup.ConfigureServices()
services.AddScoped<MyHeaderFilterAttribute>();

//In controller
[ServiceFilter(typeof(MyHeaderFilterAttribute))]
public ActionResult GetWithStatus()
{
    return new OkObjectResult(new { Id = 123, Name = "Hero" });
}

Using TypeFilter

For this,

  • registration of the filter is NOT REQUIRED
  • use TypeFilter attribute with type of desired filter that needs service injection
  • this doesn’t use the DI container directly, rather it internally uses frameworks’s ObjectFactory to inject the instance. More details here
  • with TypeFilter, additional constructor arguments can also be passed along with injection (e.g. Arguments = new object[] { 1, “yo” } )
[TypeFilter(typeof(MyHeaderFilterAttribute))]
public ActionResult GetWithStatus()
{
    return new OkObjectResult(new { Id = 123, Name = "Hero" });
}

With TypeFilter wrapper type

Though the above two way works, they are little cumbersome and the syntax is not clean. To just use the filter directly as attribute, without DI registration we can wrap the actual filter as a TypeFilter instance. Then it can be used directly as attribute.Note, this does not allow constructor parameters.

We’ll create a simple ExceptionFilter to log exceptions, which has got a dependency on some service.

public class LogErrorAttribute : TypeFilterAttribute
{
    public LogErrorAttribute() 
        : base(typeof(LogErrorFilterImplementation))
    {
    }

    public class LogErrorFilterImplementation : ExceptionFilterAttribute
    {
        private static ISomeService _service;

        public LogErrorFilterImplementation(ISomeService someService)
        {
            _service = someService;
        }

        public override void OnException(ExceptionContext context)
        {
            if (context.Exception != null)
            {
                //do something with injected service
                _service.DoStuff();
                //log the exception
            }
        }
    }
}

This can be used just as an attribute without any registration.

//In controller
[LogError]
public JsonResult Get()
{
    //do some processing
    return Json(new Item { Id = 123, Name = "Hero" });
}

Pretty neat I’d say :)

Also, one can implement own IFilterFactory as outlined here.

[5] Dependency injection with HttpContext

When HttpContext is built, it gets it’s own copy of IServiceProvider as RequestServices. So whoever has access to a valid HttpContext like a controller or filter, can use that to get a service instance. We’ll see an example using a controller action.

public IActionResult Get()
{
    var svc = (ISomeService)Request.HttpContext.RequestServices
        .GetService(typeof(ISomeService));
    svc.DoStuff(); //use the service as usual

    return new OkObjectResult("Everyone gets a service provider!");
}

Notes: Few things to watch out for, specially when converting older projects to .NET Core.

Before we move on to Configurations, let’s quickly talk about some (decided) limitations of the current dependency injection framework in .NET Core.

  1. It doesn’t support named instances like some other popular DI frameworks like Unity, StructureMap etc.
  2. It doesn’t support setter or property injection like Unity, Ninject etc.

From GitHub, Microsoft does not have a plan to support named instances. There are some workarounds though. This Stack Overflow QA shows some viable alternatives.

The idea is to cleverly use an extension method of IServiceCollection that allows registering a function of type Func<IServiceProvider, TService>, to actually register a factory function of type Func<string, TService>. Now that factory can be injected by the DI framework.

//using Microsoft.Extensions.DependencyInjection;
//Startup.ConfigureServices()
services.AddTransient(servicrProvider =>
{
    Func<string, ISomeService> accesor = key =>
    {
        switch (key)
        {
            case "My":
                return servicrProvider.GetService<MyService>();
            case "Other":
                return servicrProvider.GetService<OtherService>();
            default:
                throw new KeyNotFoundException();
        }
    };
    return accesor;
});
//And the default standard instance
services.AddTransient<ISomeService, MyService>();

//To use a named instance
//A controller constructor as example
public TestController(Func<string, ISomeService> serviceAccessor)
{
    ISomeService svc = serviceAccessor("Other");
}

[6] Basic Configuration setup in .NET Core 2.0

There is a significant change from classic .NET to .NET Core on how configuration and settings are handled. There are no preset app.config or web.config files to dump the config values (well, there is appsettings.json but that is practically optional). It comes with lot of flexibility and options on how the config settings can be managed. See the Configuration & settings section of Porting ASP.NET MVC applications to ASP.NET Core 2.0 for a basic understanding of how it works in (ASP).NET Core.

Here are some key points

  • Like older .NET, still the configuration is key-value based
  • Unlike older .NET, there is no app.config and there is no predefined config sections like appSettings. Configuration settings in .NET Core is much more flexible, and the settings can be structured in any form.
  • Source of configuration can be any XML, JSON, INI file or in-memory, command line args or environment variables
  • The configuration sources (e.g. appsettings.json) can be updated without restarting the application
  • Configuration source is defined in Program.cs, and registered with DI in Startup. Then the configuration values can be used across the application

In the earlier releases of .NET Core, any file or other sources of configuration needed to be explicitly mentioned in Program.cs. Example below

//Program.cs
public static IWebHost BuildWebHost()
{
    return new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .ConfigureAppConfiguration((builderContext, config) =>
        {
            IHostingEnvironment env = builderContext.HostingEnvironment;
            //Adding all configuration sources
            config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true);
            config.AddEnvironmentVariables();
            if (args != null)
            {
                config.AddCommandLine(args);
            }
        })
        .UseStartup<Startup>()
        .Build();
}

Starting with .NET Core 2.0, the inclusion of main appsettings.json, environment specific appsettings.{env-name}.json and other standard configuration sources are implicit. The CreateDefaultBuilder() method takes care of that. See the code on GitHub.

//Program.cs
public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args) //adds appsettings.json, appsettings.env.json
        .UseStartup<Startup>() // Invokes ConfigureServices & Configure on Startup
        .Build();

A sample default appsettings.json from ASP.NET Core 2.0 project (with connection strings)

{
  "Logging": {
    "IncludeScopes": false,
    "Debug": {
      "LogLevel": {
        "Default": "Warning"
      }
    },
    "Console": {
      "LogLevel": {
        "Default": "Warning"
      }
    }
  },
  "ConnectionStrings": {
    "PrimaryDB": "SomeDBConnectionString",
    "SecondaryDB": "AnotherDBConnectionString"
  }
}

Reading simple values from appsettings file with IConfiguration

The IConfiguration is auto-registered with the DI and can be injected in any class. This IConfiguration has a string based indexer that allow reading values with JSON-style keys. Sample code below.

using Microsoft.Extensions.Configuration;

public class TestController : Controller
{
    IConfiguration _configuration;

    public TestController(IConfiguration configuration,)
    {
        _configuration = configuration;
    }

    public IActionResult Get()
    {
        //get config value with IConfiguration
        var logLevel = _configuration["Logging:Debug:LogLevel:Default"];
        //from array with index
        var firstServerName = _configuration["Servers:0:Name"];
        //casting with (optional) default value
        var country = _configuration.GetValue<string>("Address:Country", "India");

        return new OkObjectResult($"Log level: {logLevel}");
    }
}

Reading connection strings

Practically connections strings can be kept in any configuration file and can be read exactly the same way ano other configuration value is read. But, following the conventions, if ConnectionStrings is kept at top level of appsetiings.json, there is a handy method to read them.

var primaryConnStr = Configuration.GetConnectionString("PrimaryDB");
//which is simply a short-hand for
var secondaryConnStr = Configuration.GetSection("ConnectionStrings")["SecondaryDB"];

[7] Adding additional configuration source

Apart from the default appsettings.json, any other source of configuration can also be added. This does not override the initial config, rather appends the new values. Following code shows example of adding an xml file as an additional configuration source.

//using Microsoft.Extensions.Configuration;
//Program.cs
public static IWebHost BuildWebHost(string[] args)
{
    WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((builderContext, config) =>
        {
            config.AddXmlFile("AppData/AppConfigs.xml", 
                optional: true, reloadOnChange: true);
        })
        .UseStartup<Startup>()
        .Build();
}

Sample AppConfigs.xml configuration XML. Here, the name and structure of the XML file doesn’t really matter. Once added, the values can be read the same way as shown above.

<?xml version="1.0" encoding="utf-8" ?>
<AppConfigs>
  <EnableGuestAccess>true</EnableGuestAccess>
  <CustomLogHeader>My Cool Log Header</CustomLogHeader>
  <DeleteLogsAfterDays>45</DeleteLogsAfterDays>
</AppConfigs>

[8] Strongly typed configuration

Another useful cool feature of .NET Core is, a complete configuration file or any part of it can be easily casted to simple POCO object, and then can be passed around as strongly-typed configuration. Rather than injecting the IConfigurationRoot or IConfiguration with all the settings, it is better to have related settings bound to a meaningful POCO and inject where it makes sense.

For this, create a class that is compatible with the section of configuration file that needs to be casted. Following is a class that is used to store the configuration values from the XML shown above.

public class AppConfigs
{
    public bool EnableGuestAccess { get; set; }
    public string CustomLogHeader { get; set; }
    public int DeleteLogsAfterDays { get; set; }
}

Read a whole configuration file or a section of it (Json/XML) into a compatible object instance, and register with the DI container. Here, it is important that the structure of the configuration section matches the type of the object. Following code inside the ConfigureServices() method shows how a configuration is casted to an object and registered with the DI container.

Note: When this services.Configure() method is used, it’ll look in all configuration files available in the reverse order of how they were added. So, the source that was added last will be checked first and so on. It’ll take the first match, complete file or by section name, checking the properties recursively.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        //Read compatible config file (AppConfigs.xml) into AppConfigs instance
        //Here compatible means a matching structure with Type AppConfigs
        services.Configure<AppConfigs>(Configuration);
        //OR read only a section of config file into a compatible onject
        services.Configure<LogSetup>(Configuration.GetSection("Logging"));
        //Here it'll read from appsettings.json as that has matching section "Logging"
    }
}

Also note, the strongly-typed configuration object registered like this, behaves more like a singleton registration.

[9] IOptions injection for configuration instance

The configuration registered in above code can be injected with an IOptions wrapper of the created instance type e.g. IOptions<AppConfigs> for our code sample

public class TestController : Controller
{
    AppConfigs _appConfigs;

    public TestController(IOptions<AppConfigs> configOptions)
    {
        //Use value of injected IOptions wrapper
        _appConfigs = configOptions.Value;
    }

    public IActionResult Get()
    {
        //use the typed config like any other object
        var logHeader = _appConfigs.CustomLogHeader;

        return new OkObjectResult($"Log header: {logHeader}");
    }
}

Instead of IOptions<T>, the IOptionsSnapshot<T> can also be used. See the last section.

[10] Injection of strongly typed configuration without IOptions wrapper

If we want to use that strongly-typed configuration object without the IOptions<> wrapper, we’d need to create an instance of that object in Startup with the required config section populated, and register that as injectable object. Following code shows an example.

Uses Configuration.Bind(obj) or Configuration.Get<T>() to populate configuration object instance.

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        //create a new instance of the object and bind to config
        var appConfigs = new AppConfigs();
        Configuration.Bind(appConfigs); //match a whole file
        //or Configuration.GetSection("AppConfigs").Bind(appConfigs);
        //or the strogly typed .Get<T>() method
        AppConfigs appConfigs1 = Configuration
            .GetSection("AppConfigs").Get<AppConfigs>();
        //Register the config object as singleton
        services.AddSingleton(appConfigs);
    }
}

Now, once that object has been registered as singleton, it can be injected with the DI framework into any class by the config object type.

public class TestController : Controller
{
    AppConfigs _appConfigs;

    //directly inject the singleton config object
    public TestController(AppConfigs appConfigs)
    {
        _appConfigs = appConfigs;
    }

    public IActionResult Get()
    {
        //use the typed config like any other object
        var logHeader = _appConfigs.CustomLogHeader;

        return new OkObjectResult($"Log header: {logHeader}");
    }
}

Runtime reload of configuration values

When working with configuration, sometimes it is required to update configuration values. Reloading of config values, when a configuration source has been updated, doesn’t work out of the box always. To be specific, strongly-typed configurations in ASP.NET Core 2.0 are not automatically reloaded.

One options is to simply restart the application, so that the Startup is run again, and it reloads the config again. But, it is not a practical solution. All users will lose their state, session, data etc., and there could be an application downtime.

One solution to this problem is to use IOptionsSnapshot. This internally uses the IOptionsMonitor based triggers to re-read configuration values from source, whenever that is updated.

To use, simply use IOptionsSnapshot<> in place of IOptions<> where a strongly-typed config is being injected. Or to inject the reloadable config object type directly, register an instance of the IOptionsSnapshot’s Value for the config object’s type. This is done with an extension method to register a service, like Func<IServiceProvider, TConfig>.

public SomeController(IConfiguration configuration, IOptions<AppConfigs> options,
    IOptionsSnapshot<AppConfigs> optionsSnapshot, AppConfigs appConfigs)
{
    //IOptions does NOT auto-update
    var c1 = options.Value.LogCount;
    //object config does NOT auto-update
    var c2 = appConfigs.LogCount;

    //simple values from IConfiguration DOES auto-update
    var c3 = configuration["log:count"];    
    //IOptionsSnapshot DOES auto-update
    var c4 = optionsSnapshot.Value.LogCount;
}

//using Microsoft.Extensions.DependencyInjection;
//Startup
//Registering IOptionsSnapshot instance as config object
services.AddScoped(isp =>  //IServiceProvider
    isp.GetService<IOptionsSnapshot<AppConfigs>>().Value);

//Now the AppConfigs injected instance auto-updates
public SomeController(AppConfigs appConfigs)
{
    //this RELOADS on source data change
    var logCount = appConfigs.LogCount;
}

Note: This is important to add the configuration sources with reloadOnChange: true (see section 7 above) option. Without this, the config values will not reload without an application restart.

Hope this helps in understanding the confiduration system and DI in ASP.NET Core 2.0 little more easily. Check back official documentation and GitHub for latest developments on all topics.

comments powered by Disqus