Introducing Scrutor - Convention based registration for Microsoft.Extensions.DependencyInjection

This is a post I should've written a long time ago. The library has already been out there since November. Just sitting in the open on GitHub and NuGet, ready to be used, but very few people know about it. This is my attempt at rectifying that.

If you already know the what and the why, skip down to the Show me the codez! section to get a feel for the API.

TL;DR: Scrutor is a small open source library that provides a fluent API to register services in your Microsoft.Extensions.DependencyInjection container based on conventions.

What's with the name?

Woah, the title of this post is quite a mouthful, eh? Your browser probably can't even render it correctly because of its length.

I could probably have written multiple posts on the naming of things in this new world of ASP.NET 5 ASP.NET Core, as several others have already done, but I'll let that slide. Naming is hard, and there will always be strong opinions, but at this point, it is what it is and things are not going to change.

I do want to point out though, that it was particularly hard to find a name for this library, because of the fact that it builds on top of Microsoft.Extensions.DependencyInjection.
Several other libraries building on top of Microsoft.Extensions.* packages have struggled with the same, like

Maybe it would be an idea if Microsoft came out with some advice on naming here, no matter how horrible, and that the community followed along so we could get some consistency? NuGet has discoverability problems enough as it is.

A natural name for this library would probably be something like

Microsoft.Extensions.DependencyInjection.Extensions.Scanning

Yeah... I don't think so. Did someone say PathTooLongException?

Actually, I think anything starting with Microsoft.* would be perceived as an official package, and although that probably would be good for adoption, it didn't feel right.

After going through a couple of options, I landed on Scrutor. It's short, memorable, and has nice googleability (yes, apparently that's a word). The word scrutor is latin and, at least according to wiktionary.org, rougly translates to

search or examine thoroughly; probe, investigate or scrutinize.

I think that's fitting for a library that scans through assemblies to register types based on conventions. It's also quite funny because

the original sense of the verb was to search through trash

I'm sure this resonates with developers, when thinking about (other people's) code.

What does it do?

The library itself is pretty simple; it provides a single extension method, Scan, along with a fluent API to IServiceCollection that allows you to register "services" based on conventions. That's it!

A lot of existing IoC containers in the .NET space has similar APIs, either built in or through extensions:

I'm sure the other, less popular containers have the same concept. It's basically a quick way to register multiple types based on a given set of conventions.

Why?

I wrote this library on a weekend, simply to "scratch my own itch". I'm used to having these APIs available to me when using other containers, and as a lazy developer, I didn't want to register all my stuff by hand. I've been using it since, to great avail.

Obviously, many people were in the same boat as me. I kept seeing issues like this and this pop up, where people were asking for these features, but Microsoft, rightfully so, didn't want to provide this out-of-the-box:

We don't have any plans to include this functionality in the default container. The default container is deliberately minimalistic. For a full featured container you can use any of the various existing containers.

(From Dan Roth, here)

So why not build it ourselves, as an extension?

Show me the codez!

To explain how it works, I think it's best to show an example:

public interface IEmailService { }  
public interface ISmsService { }  
public class MessagingService : IEmailService, ISmsService { }

public interface IPersonRepository { }  
public class PersonRepository : IPersonRepository { }

var collection = new ServiceCollection();

collection.Scan(scan => scan  
    .FromAssemblyOf<IEmailService>() // 1.
        .AddClasses(classes => classes.AssignableTo<IEmailService>()) // 2.
            .AsImplementedInterfaces() // 3.
            .WithSingletonLifetime() // 4.
        .AddClasses(classes => classes.InNamespaceOf<IPersonRepository>()) // 5.
            .As<IPersonRepository>() // 6.
            .WithScopedLifetime()); // 7.

var provider = collection.BuildServiceProvider();

var emailService = provider.GetService<IEmailService>();  
var smsService = provider.GetService<ISmsService>(); // 8.

var repository = provider.GetService<IPersonRepository>();  

The example above will

  1. Go through all types in the assembly of the IEmailService interface.
  2. Pick all concrete types, i.e. non-abstract, non-static classes that implements the IEmailService interface. In this case, that'll only be MessagingService.
  3. Register it as all its implemented interfaces, i.e. IEmailService and ISmsService.
  4. Give it a singleton lifetime. That means you always get the same instance, every time you resolve the service.
  5. Pick all concrete types in the same namespace as IPersonRepository. In this case, it's only PersonRepository.
  6. Register it as IPersonRepository.
  7. Give it a "scoped" lifetime. This means that you'll get the same instance within a specific scope. A scope is typically an HTTP request.
  8. When IEmailService and ISmsService is resolved, they will be the same MessagingService instance, since the same type is registered as both services with a singleton lifetime.

I hope most of it was somewhat self-explainatory and does what you'd expect.

There are, of course, more APIs for scanning multiple assemblies, filtering types, registering open generic types, using attributes etc.

Until I can get some decent documentation up, I think it's best to just start from the Scan method and work your way through the API using IntelliSense. Most methods should have XML-documentation on them.

Currently, the library is targeting the RC1 version of Microsoft.Extensions.DependencyInjection and supports the same target frameworks; net451, dotnet5.4 and netcore50.

Please check it out and tell me what you think. I'd love your feedback. File issues or submit pull requests on GitHub.

Thanks for reading! :)