Using dependency injection while configuring services

August 9, 2016

ASP.NET Core applications are configured using the Configure and optionally, the ConfigureServices methods of the startup class (typically Startup). ConfigureServices is used to set up the dependency injection container that ships with ASP.NET Core. The method signature looks like this:

public void ConfigureServices(IServiceCollection services);

That IServiceCollection instance is the developer’s surface area for configuring dependencies.

Mvc can be configured like this:

public class Startup
{
  // ...
  public void ConfigureServices(IServiceCollection services)
  {
    // ...
    services.AddMvc();
    // ...
  }
  // ...
}

The extension methods offered by Mvc also allow you do do more than just apply the default configuration. There is also a method like this:

public static IMvcBuilder AddMvc(this IServiceCollection services, Action<MvcOptions> setupAction);

You can pass an anonymous function for the setupAction parameter, so you can do things like services.AddMvc(options => /* do stuff with MvcOptions */). In my case, I wanted to add a default authorization filter globally, using the MvcOptions.Filters property.

public void ConfigureServices(IServiceCollection services)
{
  // ...
  services.AddMvc(options => {
    options.Filters.Add(new AuthorizeFilter(/* uh....how do I get the default policy??? */));
  });
  // ...
}

As you might be able to guess from the above, AuthorizeFilter requires a policy in the constructor. Unfortunately, there wasn’t a straightforward way to get the configured default authorization policy. I’ve seen this problem before with other things that need configuring. Sometimes you just need to know about other stuff that’s been configured without resorting to brittle hacks - but you can’t get direct access to the DI container in the context of the ConfigureServices method because the container hasn’t been built yet! What we really need is a way to declare a deferred configuration that would get the benefit of injected dependencies.

Fortunately, ASP.NET Core does include such a mechanism. It’s part of the Options “sub-framework”. What you have to do is implement IConfigureOptions<TOptions> for whatever TOptions you need to configure, and then add that to the container. When the Options framework invokes all of its configurations, it will resolve classes of type IConfigureOptions<> and then invoke the Configure method on them. In my case, the solution was to write the following class and wire up a singleton to the DI container:

class MvcConfiguration : IConfigureOptions<MvcOptions>
{
  private readonly IOptions<AuthorizationOptions> _authOptions;
  public MvcConfiguration(IOptions<AuthorizationOptions> authOptions)
  {
    _authOptions = authOptions;
  }

  public void Configure(MvcOptions options)
  {
    options.Filters.Add(new AuthorizeFilter(_authOptions.Value.DefaultPolicy));
  }
}

Conveniently, the Options framework will just apply this configuration along with whatever else you have configured elsewhere, so you can do more complex things (that require injected dependencies) in your IConfigureOptions class and still do simpler configuration for Mvc up in ConfigureServices. The configurations will stack.

My ConfigureServices method now looks like this:

public void ConfigureServices(IServiceCollection services)
{
  // ...
  services.AddMvc(); // configures the defaults, including default AuthorizationOptions
  services.AddSingleton<IConfigureOptions<MvcOptions>, MvcConfiguration>(); // my additional configuration
  // ...
}

Thanks to Derek Gray (@tuespetre in the aspnetcore slack) for discussion that led me here.