ASP.NET Core - Dependency Injection (DI)

ASP.NET Core is designed from the ground up to support Dependency Injection (DI). It uses a built-in Inversion of Control (IoC) container to automatically inject objects of dependency classes into constructors or methods.

Built-in IoC Container

ASP.NET Core provides a simple IoC container out-of-the-box. While it doesn't offer as many features as third-party IoC containers (such as auto-registration, scanning, interceptors, or decorators), it is efficient for most scenarios. If more advanced features are required, you can replace the built-in container with a third-party solution.

The built-in IoC container is implemented through the IServiceProvider interface, which supports constructor injection by default. The types (classes) managed by the IoC container are called services.

Types of Services in ASP.NET Core

There are two main types of services in ASP.NET Core:

  1. Framework Services: These are provided by the ASP.NET Core framework, such as IApplicationBuilderIHostingEnvironment, and ILoggerFactory.

  2. Application Services: These are custom services (types or classes) that you, as the developer, create for your application.

Before the IoC container can inject our application services, we need to register them with the container.


Registering Application Services

Let’s consider a simple example of an ILog interface and its implementation, MyConsoleLogger. We'll see how to register it with the IoC container and use it in an ASP.NET Core application.

    public interface ILog
    {
        void Info(string message);
    }

    class MyConsoleLogger : ILog
    {
        public void Info(string message)
        {
            Console.WriteLine(message);
        }
    }

To register this service, use the ConfigureServices method in the Startup class. This method accepts an IServiceCollection parameter where services are registered.

Register Service Example:

    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.Add(new ServiceDescriptor(typeof(ILog), new MyConsoleLogger()));
        }
    }

In the example above:

  • The Add method of IServiceCollection registers a service with the IoC container.

  • ServiceDescriptor specifies the service type (ILog) and its implementation (MyConsoleLogger).

  • By default, this registers the service as a singleton, meaning the same instance will be shared throughout the application's lifetime.

Once registered, ASP.NET Core will automatically inject an instance of MyConsoleLogger wherever ILog is requested in the constructor.


Understanding Service Lifetime

The IoC container manages the lifetime of registered services. It automatically disposes of a service based on the specified lifetime:

  1. Singleton: A single instance is created and shared across the entire application's lifetime.

  2. Transient: A new instance is created each time a service is requested.

  3. Scoped: A new instance is created per request, and that instance is reused during the same request.

Registering Services with Different Lifetimes:

    public void ConfigureServices(IServiceCollection services)
    {
        services.Add(new ServiceDescriptor(typeof(ILog), new MyConsoleLogger())); // Singleton
        services.Add(new ServiceDescriptor(typeof(ILog), typeof(MyConsoleLogger), ServiceLifetime.Transient)); // Transient
        services.Add(new ServiceDescriptor(typeof(ILog), typeof(MyConsoleLogger), ServiceLifetime.Scoped)); // Scoped
    }

Alternatively, you can use the provided extension methods for a cleaner registration:

Using Extension Methods:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton<ILog, MyConsoleLogger>(); // Singleton
        services.AddTransient<ILog, MyConsoleLogger>();  // Transient
        services.AddScoped<ILog, MyConsoleLogger>();     // Scoped
    }

These extension methods provide a more straightforward approach for registering services with the desired lifetime.


Constructor Injection

Once a service is registered, the IoC container automatically performs constructor injection by providing the service type as a parameter in the constructor.

For example, let’s use the ILog service in an MVC controller:

    public class HomeController : Controller
    {
        private readonly ILog _log;

        // Constructor injection
        public HomeController(ILog log)
        {
            _log = log;
        }

        public IActionResult Index()
        {
            _log.Info("Executing /home/index");
            return View();
        }
    }

In this example, the IoC container automatically passes an instance of MyConsoleLogger to the HomeController constructor. The container will manage the instance based on the service lifetime (singleton, transient, or scoped).


Action Method Injection

Sometimes you may need the service only in a specific action method rather than throughout the controller. For this, you can use the [FromServices] attribute to inject the service directly into the action method.

Action Method Injection Example:

    public class HomeController : Controller
    {
        public IActionResult Index([FromServices] ILog log)
        {
            log.Info("Index method executing");
            return View();
        }
    }

In this case, the IoC container will inject the ILog service only when the Index method is called.


Property Injection

The built-in IoC container does not support property injection. However, third-party IoC containers may offer this feature. For property injection, you would need to integrate a third-party container like Autofac.


Getting Services Manually

While it’s recommended to use constructor injection, sometimes you may want to manually access services from the IoC container. You can use the HttpContext.RequestServices property to get services manually.

Example: Manual Service Resolution:

    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            var services = this.HttpContext.RequestServices;
            var log = (ILog)services.GetService(typeof(ILog));

            log.Info("Index method executing");
            return View();
        }
    }

Although possible, this approach is discouraged because it can make the code harder to test and maintain. It's generally better to use constructor injection.


Conclusion

ASP.NET Core’s Dependency Injection system helps to manage service lifetimes and dependencies cleanly. The IoC container handles the creation and injection of services, reducing the need for tightly coupled code and making your application more maintainable and testable.

  • Use constructor injection whenever possible to let the IoC container automatically handle service dependencies.

  • Take advantage of the built-in IoC container for registering and managing your application services.

  • Be mindful of service lifetimes (singleton, transient, and scoped) to manage the lifespan of your services correctly.

Leave a Reply

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


Talk to us?

Post your blog

F.A.Q

Frequently Asked Questions