.NET Things: Assembly Scanning

James Louie
Pragmatic Programming
3 min readFeb 28, 2019

In this installment of .NET Things, we’ll dive into a common dependency injection pattern known as Assembly Scanning, which is used throughout ASP.NET systems. Assembly Scanning is a pattern that describes the automatic registration of one or more dependencies within your application.

Assembly scanning is very powerful when you need to bulk register your dependencies without having to explicitly register each instance. By following some convention, your code will be automatically connected with your application without any developer intervention. This can improve consistency in your application and improve developer productivity.

Assembly Scanning can be organized into three components:

  • Dependency — The thing we’re trying to inject
  • Locator — How we determine what to inject
  • Scanner — The algorithm used to locate the dependencies

These definitions were meant to be left to be very generic, as this pattern can be applied in a variety of ways. We’ll explore some common built-in scanners that Microsoft has implemented for us, and then go into how we can use this pattern for ourselves.

ASP.NET Usage Examples

WebApi Controllers

ASP.NET uses the assembly scanner pattern to register your WebApi controllers with MVC. It does this by locating all the classes in your assembly for classes that end in “Controller”.

Here is the code snippet taken from AspNetCore repository for the locator component:

Original Source, Github

Entity Framework Mappings

ASP.NET uses assembly scanning to automatically register your entity framework mapping classes. If you’re familiar with the interface, the mapping interface extends IEntityTypeConfiguration<>. The scanning implementation takes the assembly and tries to determine if a class implements this interface, and then registers it to the appropriate mapping.

The implementation is a bit cryptic, but what it amounts to is that it grabs the types from the assembly, checks if they implement the interface, and creates an instance of the map if it does.

Original Source, Github

As a result of calling ModelBuilder.ApplyConfigurationsFromAssembly, all future maps will now be automatically included with the DbContext.

Implementing Your Own

We’ll go through how to create your own assembly scanning pattern for a service design pattern as an example.

Step 1: Determining the Locator Pattern

First thing we need to do determine is how to decide which classes we want to include. In the previous examples, we used class names and specific interface implementations. In this example we’ll go with my personal favorite — Attributes. Attributes are great because they allow developers to explicitly mark the class, as well as allows you to associate metadata that can be used as part of the configuration.

In this example we give the attribute two options: to explicitly link an interface or to specify it’s lifetime. The properties alone don’t do anything, but combined with the scanning component we can provide configuration information.

Step 2: Implement the Dependency

This is the easy part. Now that we have the locator we just have to make sure our dependency follows that pattern. In Step 1 we created an attribute to determine which classes to inject.

Here we tag our implementation class with the Inject attribute, as well as assign it a lifetime scope of Singleton.

Step 3: Scan the Assembly

The last step is to scan the assembly for classes that have our custom attribute and include the into the dependency injection container. I created a convenience extension method to IServiceCollection to accomplish this.

Using the generic parameter T, we get the assembly from the type and return a list of classes that use the custom InjectAttribute. Next we iterate over those types and pull out our metadata. From here it’s however you want to apply your scanning algorithm. In this case we check to see if we specified a specific interface as part of the attribute metadata to register it as, or if not it registers the class for all implemented interfaces. We also include the lifetime for each of the registrations.

This step can be as simple or sophisticated as you want, but generally want to keep the other parts steps thin, as those will be repeated.

Scan Away!

Now that you’ve got the basics, you can start to identify which parts of your application this can apply to. The best scanning patterns are the least obtrusive and saves developers the manual work of registering them by hand.

--

--

James Louie
Pragmatic Programming

Developer looking to make the code a little more clean