Dependency Injection in ASP.NET Core with Scrutor

When registering types with an IOC container, configuration can get pretty unruly depending on the size of your project.  One of the things I typically look for in a container is type scanning and conventional registration.  This way, only types that do not fit the convention or use a special scope/lifetime need to be manually registered.  Unfortunately, the built-in ASP.NET Core container does not support type scanning.

Enter Scrutor, an open source assembly scanning library for the Microsoft.Extensions.DependencyInjection framework.  With Scrutor’s fluent interface, developers can easily wire-up dependencies with a convention of their choosing.

Lets take a look at a simple example. Say we have a random number generator.  Over time, it starts getting used by a couple classes throughout our application.  Being the good developers that we are, we want to write unit tests for these classes to see how they react with different sets of random numbers.  In order to mock the RandomNumberGenerator, we create an interface.


using System;
namespace JrTech.AspNetCore.Web
{
public interface IRandomNumberGenerator
{
int Get();
}
public class RandomNumberGenerator : IRandomNumberGenerator
{
public int Get()
{
var rand = new Random();
return rand.Next();
}
}
}

view raw

Scrutor_1.cs

hosted with ❤ by GitHub

Of course, our job doesn’t end here.  We also have to register our class and interface with the IServiceCollection as shown on line 22.


using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace JrTech.AspNetCore.Web
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient<IRandomNumberGenerator, RandomNumberGenerator>();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
}

view raw

Scrutor_2.cs

hosted with ❤ by GitHub

Now we can inject the IRandomNumberGenerator interface into any of our classes and the IOC Container will create a “transient” instance of the RandomNumberGenerator class for us.  As our application matures, we notice this is a consistent pattern and our Startup class is filled with type registrations.

The other thing we notice is because our web application is handling all of the type registrations, we are required to have a direct reference to the projects and assemblies of the types we are registering.  This can lead to some undesirable effects.  For example, should our web application need to know or care about our database implementation?

There are a couple patterns that can be used to address these concerns.  As I’ve mentioned previously, type scanning and conventional registration does the trick for me.  This is where Scrutor helps us out.  Lets take a look at lines 22-28 below.


using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
namespace JrTech.AspNetCore.Web
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Scan(scan => scan
.FromExecutingAssembly()
.FromApplicationDependencies(a => a.FullName.StartsWith("JrTech"))
.AddClasses(publicOnly: true)
.AsMatchingInterface((service, filter) =>
filter.Where(implementation => implementation.Name.Equals($"I{service.Name}", StringComparison.OrdinalIgnoreCase)))
.WithTransientLifetime());
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
}

view raw

Scrutor_3.cs

hosted with ❤ by GitHub

The extension methods provided by Scrutor allows us to scan a set of assemblies of our choosing.  In the example above, we are scanning our application and all its dependencies as long as the name is prefixed by JrTech (no need to scan Microsoft.* and System.* assemblies).  We then take all public classes and if they implement an interface that meets our convention, we automatically register them into the container with Transient lifetime.

With our code in place, we only need to register types that do not meet this criteria.  Most of the time our registrations are handled automatically so, our Startup.cs class can stay nice and tidy!