Bolt.IocScanner – DotNet Core Dependency Injection with Convention and Attribute

Dependency injection is a common practice for any application development now a days. Thats why dotnetcore comes with inbuilt dependency injection provider. Which makes the whole development experience much better for any applications event if you build a console or serverless lambda/function application.

And here how we generally use it in a typical mvc applicaton.

Your initial startup.cs file is as below:

    // file: ~/Startup.cs
    public void ConfigureServices(IServiceCollection srvices)
    {
        services.AddMvc();
    }

Then you define your controller say which allow you to manage books:

    // file: ~/Controllers/BooksController.cs
    public class BooksController : Controller
    {
        private readonly IBooksService _booksService;

        public BooksController(IBooksService booksService)
        {
            _booksService = booksService;
        }

        ...
    }

Your books service class might be as below:

    // file: ~/Services/BooksService.cs
    public class BooksService : IBooksService
    {
        private readonly IBooksRepository _repo;

        public BooksService(IBooksRepository repo)
        {
            _repo = repo;
        }
        ....
    }

Your books repository might be as below:

    // file: ~/Repositories/BooksRepository.cs
    public class BooksRepository : IBooksRepository
    {
        publi BooksRepository(IDatabase db)
        {
            ....
        }
    }

Now you run your application it will fail. Because you didn't register all these classes in your IOC. So now you have to go back ot Startup file and update as below:

    // file: ~/Startup.cs
    public void ConfigureServices(IServiceCollection srvices)
    {
        services.AddScoped<IDatabase.SqlDatabase>();
        services.AddTransient<IBooksProxy,BooksProxy>();
        services.AddTRansient<IBooksService,BooksService>();

        services.AddMvc();
    }

This just gives you an idea, but in real life this is a circle. Everytime you add a new feature or refactory you have to come to startup file and update code to register any new classes you added. This is a pain. At the same time its a code smell also as your startup file changed almost for every class you add in your application that needs to be injected.

<!–more Keep on reading!–>

So in ideal world I like my classes automatically bind against the interface so I don't need to write those registration codes in startup file. In other word I don't need to touch the Startup file during the development of the application. At the same time I like to get flexibility to control what the class instance lifecycle should be if required. So introducing Bolt.IocScanner which allow me to do the way I like to use IOC container during development. You need to add nuget package Bolt.IocScanner in your application and add one line in your startup class. After that whenever you add new class that imeplement an interface will be registered automatically. You never need to touch startup file.

Your startup file should be as below:

    // file: ~/Startup.cs
    public void ConfigureServices(IServiceCollection srvices)
    {
        // Just add following line to scan and register automatically
        services.Scan<Startup>();

        services.AddMvc();
    }

Okay I am convniced. But what about…

Can I scan mutiple assemblies?

Yes you can. Here is an example:

    public void ConfigureServices(IServiceCollection services)
    {
        services.Scan(new []{
            typeof(BookStore.Web.Startup).Assembly,
            typeof(BookStore.Infrastructure.ISerializer).Assembly
        });
    }

How can I bind a class to self without using an inteface?

If a class doesn't implement any interface then the lib doesn't bind it automatically. You have to put AutoBind attribute to bind a class without interface. Then the library bind that class to self with lifecycle instructed in the attribute. See example below:

    [AutoBind]
    public class BooksProxy
    {
        public Task Get(string id)
        {
            // your code here ...
        }
    }

    public class BooksController : Controller
    {
        private readonly BooksProxy _proxy;

        public BooksController(BooksProxy proxy)
        {
            _proxy = proxy;
        }
    }

How I can control the lifecycle of the bindings?

By default all bindings use transient lifecycle. If you want different lifecycle then you need decorate your class with an attribute as below and can mention what lifecycle you like the class to bind.

    [AutoBind(LifeCycle.Scoped)]
    public class BooksProxy
    {

    }

    // or if you like to register as singleton

    [AutoBind(LifeCycle.Singleton)]
    public class ConnectionPool
    {

    }

How I can exclude some classes from automatic binding?

Theres four ways you can do this.

You can decorate the class that you want to exclude using an attribute

    [SkipAutoBind]
    public class ThisClassNotBind
    {

    }

you can add the class type as exclude items of scan options as below

    public void ConfigureServices(IServiceCollection services)
    {
        services.Scan(new IocScannerOptions
        {
            TypesToExclude = new[] { typeof(ThisClassNotBind) }
        });
    }

you can tell in option that only bind when an "AutoBind" attribute provided. In that case any class in assemblies doesn't have "AutoBind" attribute will not be registered in ioc.

    public void ConfigureServices(IServiceCollection services)
    {
        services.Scan(new IocScannerOptions
        {
            SkipWhenAutoBindMissing = true
        });
    }

you can even pass your own logic to exclude any types using a func as below

    public void ConfigureServices(IServiceCollection services)
    {
        services.Scan(new IocScannerOptions()
            .Exclude(t => t.Name.Equals("Startup")));
    }

Does it bind to all implemented interfaces?

Yes if a class implement more than one interfaces the lib bind the class against all interfaces. In case of Singleton and Scoped lifecycle all interface bind against same instance. For example:

    [AutoBind(LifeCycle.Singleton)]
    public class Implementation : IProvideName, IProvidePrice
    {
    }

Now if you request for IProvideName and IProvidePrice both case the service provider will return same instance of Implementation throughout the whole life of the application.

Same applies for Scoped lifecycle but in that case the behaviour is true for same scope. For new scope a new instance of Implementation will be provided by service provider.

How can I ask to register using TryAdd instead

Sometimes you want to change the registration of your implementation when you setup Ioc in your test projects. In that case sometimes its easy to achieve if we use TryAdd instead of Add. So yes you can tell to use TryAdd instead of Add when register your implementation as below:

    [AutoBind(UseTryAdd = true)]
    public class BooksProxy
    {

    }

I reckon I did cover most of the things the library supports. Enjoy using Bolt.IocScanner.

Happy Coding ๐Ÿ™‚

3 thoughts on “Bolt.IocScanner – DotNet Core Dependency Injection with Convention and Attribute

Leave a comment