Using dependency injection while configuring services
August 9, 2016ASP.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.