Dependency Injection

ASP.NET Core includes that makes configured services available throughout an app. Services are added to the DI container with WebApplicationBuilder.Services.

ASP.NET Core provides a built-in service container, IServiceProvider, in which any can be registered.

Program.cs
using Microsoft.EntityFrameworkCore;
using RazorPagesMovie.Data;

var builder = WebApplication.CreateBuilder(args);
var connectionStr = builder.Configuration.GetConnectionString("DefaultConnection");

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddControllersWithViews();

builder.Services.AddDbContext<ApplicationDbContext>(options =>
   options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<IdentityUser>(options => 
   options.SignIn.RequireConfirmedAccount = true)
   .AddEntityFrameworkStores<ApplicationDbContext>();

var app = builder.Build();
  • Services are typically resolved from DI using constructor injection. The DI framework provides an instance of this service at runtime.

Injection of the service into the constructor of the class where it's used. The framework takes on the responsibility of creating an instance of the dependency and disposing of it when it's no longer needed.

public class IndexModel : PageModel {
    private readonly RazorPagesMovieContext _context;
    private readonly ILogger<IndexModel> _logger;

    public IndexModel(RazorPagesMovieContext context, ILogger<IndexModel> logger) {
        _context = context;
        _logger = logger;
    }

    public IList<Movie> Movie { get;set; }

    public async Task OnGetAsync() {
        _logger.LogInformation("IndexModel OnGetAsync.");
        Movie = await _context.Movie.ToListAsync();
    }
}

In dependency injection terminology, a service:

  • Is typically an object that provides a service to other objects, such as the IMyDependency service.

  • Is not related to a web service, although the service may use a web service.

IServiceCollection - register services

IServiceProvider - resolve service instances

Service Lifetimes

  • Transient objects are always different.

  • Scoped objects are the same for a given request but differ across each new request.

  • Singleton objects are the same for every request.

  • The container calls Dispose() for the IDisposable types it creates. Services resolved from the container should never be disposed by the developer. If a type or factory is registered as a singleton, the container disposes the singleton automatically.

public class Service1 : IDisposable {
    private bool _disposed;

    public void Write(string message) {
        Console.WriteLine($"Service1: {message}");
    }

    public void Dispose() {
        if (_disposed)
            return;

        Console.WriteLine("Service1.Dispose");
        _disposed = true;
    }
}

public class Service2 : IDisposable {
    private bool _disposed;

    public void Write(string message) {
        Console.WriteLine($"Service2: {message}");
    }

    public void Dispose() {
        if (_disposed)
            return;

        Console.WriteLine("Service2.Dispose");
        _disposed = true;
    }
}

public interface IService3 {
    public void Write(string message);
}

public class Service3 : IService3, IDisposable {
    private bool _disposed;
    public string MyKey { get; }

    public Service3(string myKey) {
        MyKey = myKey;
    }

    public void Write(string message) {
        Console.WriteLine($"Service3: {message}, MyKey = {MyKey}");
    }

    public void Dispose() {
        if (_disposed)
            return;

        Console.WriteLine("Service3.Dispose");
        _disposed = true;
    }
}
  • The service instances that aren't created by the service container, The framework doesn't dispose these services automatically. The developer is responsible for disposing the services.

  • High-level modules should not depend on low-level modules.

  • Abstraction should not depend upon details

  • Modules and Details should depend upon abstraction.

Benefits of Dependency Injection

  • promotes loose coupling of components

  • promotes logical abstraction of components

  • supports unit testing

How to find dependency?

  1. Locate 'new' keyword usage

  2. is the object a dependency?

  3. apply dependency inversion

  4. register the service

  5. rinse and repeat

Understanding Service Lifetime

Built-in IoC container manages the lifetime of a registered service type. It automatically disposes a service instance based on the specified lifetime.

The built-in IoC container supports three kinds of lifetimes:

  1. Singleton: IoC container will create and share a single instance of a service throughout the application's lifetime. or, once for the lifetime of the application.

  2. Transient: The IoC container will create a new instance of the specified service type every time you ask for it. or, each time they are requested.

  3. Scoped: IoC container will create an instance of the specified service type once per request and will be shared in a single request. or, once per request

Registering Services
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
}
Extension Methods
public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<ILog, MyConsoleLogger>();
    services.AddSingleton(typeof(ILog), typeof(MyConsoleLogger));

    services.AddTransient<ILog, MyConsoleLogger>();
    services.AddTransient(typeof(ILog), typeof(MyConsoleLogger));

    services.AddScoped<ILog, MyConsoleLogger>();
    services.AddScoped(typeof(ILog), typeof(MyConsoleLogger));
}

Constructor Injection

Once we register a service, the IoC container automatically performs constructor injection if a service type is included as a parameter in a constructor.

Constructor Injection
public class HomeController : Controller
{
    ILog _log;
    public HomeController(ILog log)
    {
        _log = log;
    }
    public IActionResult Index()
    {
        _log.info("Executing /home/index");
        return View();
    }
}

Action Method Injection

Sometimes we may only need dependency service type in a single action method. For this, use [FromServices] attribute with the service type parameter in the method.

Action Method Injection
using Microsoft.AspNetCore.Mvc;

public class HomeController : Controller
{
    public HomeController()
    {
        //some code
    }

    public IActionResult Index([FromServices] ILog log)
    {
        log.info("Index method executing");

        return View();
    }
}

Get Services Manually

It is not required to include dependency services in the constructor. We can access dependent services configured with built-in IoC container manually using RequestServices property of HttpContext

public class HomeController : Controller
{
    public HomeController()
    {
    }
    public IActionResult Index()
    {
        var services = this.HttpContext.RequestServices;
        var log = (ILog)services.GetService(typeof(ILog));
            
        log.info("Index method executing");
    
        return View();
    }
}

Last updated