Dependency injection in ASP.NET Core with attributes

ASP.NET core comes with built-in dependency injection, which is nice! At the same time, it is possible to implement third party DI solutions like Autofac. I’m totally happy with built in DI, but when project becomes bigger, things start to get messy. Bootstrap class, which fills the DI container grows. In other words, it is ugly.

Use case

I want to write code and I really don’t care how DI will locate my service, repositories. It has to work seamlessly and has to look neat.

Solution

Use scrutor, author Kristian Hellang

1
Install-Package Scrutor

example from github

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var collection = new ServiceCollection();

collection.Scan(scan => scan
     // We start out with all types in the assembly of ITransientService
    .FromAssemblyOf<ITransientService>()
        // AddClasses starts out with all public, non-abstract types in this assembly.
        // These types are then filtered by the delegate passed to the method.
        // In this case, we filter out only the classes that are assignable to ITransientService.
        .AddClasses(classes => classes.AssignableTo<ITransientService>())
            // We then specify what type we want to register these classes as.
            // In this case, we want to register the types as all of its implemented interfaces.
            // So if a type implements 3 interfaces; A, B, C, we'd end up with three separate registrations.
            .AsImplementedInterfaces()
            // And lastly, we specify the lifetime of these registrations.
            .WithTransientLifetime()
        // Here we start again, with a new full set of classes from the assembly above.
        // This time, filtering out only the classes assignable to IScopedService.
        .AddClasses(classes => classes.AssignableTo<IScopedService>())
            // Now, we just want to register these types as a single interface, IScopedService.
            .As<IScopedService>()
            // And again, just specify the lifetime.
            .WithScopedLifetime());

This was my first try to get rid of boiler plate

1
2
3
4
5
scanServices.Scan(scan =>
    scan.FromAssemblyOf<IKernel>()
        .AddClasses(classes => classes.Where(i => i.Name.EndsWith("Service")))
        .AsImplementedInterfaces()
        .WithScopedLifetime());

Scan the assembly of IKernel location. And register all classes where name ends with Service. Some kind of naming convention for DI.

Not very nice, as it locks into naming convetion and class names start to look strange.

Final solution

Create attribute and decorate classes with attributes.

1
2
3
4
5
6
7
8
9
10
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class Service : Attribute
{
    public ServiceLifetime Lifetime;

    public Service(ServiceLifetime lifetime)
    {
        Lifetime = lifetime;
    }
}

Small adjustments in scan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void AddServiceLocator<T>(this IServiceCollection services)
{
    var scanServices = new ServiceCollection();

    scanServices.Scan(scan =>
        scan.FromAssemblyOf<T>()
            .AddClasses(classes => classes.WithAttribute<Service>(s => s.Lifetime == ServiceLifetime.Scoped))
            .AsImplementedInterfaces()
            .WithScopedLifetime()
            .AddClasses(classes => classes.WithAttribute<Service>(s => s.Lifetime == ServiceLifetime.Singleton))
            .AsImplementedInterfaces()
            .WithSingletonLifetime()
            .AddClasses(classes => classes.WithAttribute<Service>(s => s.Lifetime == ServiceLifetime.Transient))
            .AsImplementedInterfaces()
            .WithTransientLifetime()
    );

    services.TryAdd(scanServices);
}

Decorate the class and it will be picked up by scanner.

1
2
3
4
5
[Service(ServiceLifetime.Singleton)]
internal class FacebookGrantProvider : IGrantProvider
{
//***
}

Use as injection

1
2
3
4
5
6
7
8
9
internal class ExtensionGrantValidator: IExtensionGrantValidator
{
    private readonly List<IGrantProvider> _grandProviders;

    public ExtensionGrantValidator(IEnumerable<IGrantProvider> grandProviders)
    {
        _grandProviders = grandProviders.ToList();
    }
}

Code and examples in github

Install as nuget

1
Install-Package ServiceLocator

Register in startup

1
services.AddServiceLocator<Program>();

Leave a Reply

Your email address will not be published. Required fields are marked *