Registering Services

Register your classes with ASP.NET Core's service container in Program.cs - as singleton, scoped or transient - and it creates them and hands them to every controller that asks in its constructor.

Journeyman · ASP.NET Core · C# · Updated

After this lesson you can

  • Register a class with AddScoped, AddSingleton or AddTransient, and ask for it in a controller's constructor
  • Choose a service lifetime (singleton, scoped or transient) for a class

Key takeaways

  • Register a service once in Program.cs, such as builder.Services.AddScoped(_ => new ProductDao(connectionString)), and any controller can ask for it in its constructor.
  • When a class's constructor only asks for other registered services, the type alone is enough: AddSingleton<UserService>().
  • An InvalidOperationException saying "Unable to resolve service for type" means a type isn't registered: add it to Program.cs.
  • AddScoped makes one object per request, the usual choice for DAOs; AddSingleton one for the whole app; AddTransient a new one every time.
  • Never put one user's data in a singleton: every other user would see it.

In Constructor Injection, you passed dependencies in yourself: new Greeter(new Clock()). In a web app, ASP.NET Core creates the controllers, so it has to know how to create what they ask for. You tell it, in Program.cs, by registering services.

Before: every controller makes its own DAO

Until now, each controller built its DAO from the configuration:

public ProductsController(IConfiguration configuration)
{
    productDao = new ProductDao(configuration.GetConnectionString("Northwind")!);
}

Every controller that needs products repeats that, and each one is wired to ProductDao and to the setting's name.

Register once, ask anywhere

Register the DAO with the service container in Program.cs, before builder.Build():

string connectionString = builder.Configuration.GetConnectionString("Northwind")!;
builder.Services.AddScoped(_ => new ProductDao(connectionString));
builder.Services.AddSingleton<UserService>();
  • AddScoped(_ => new ProductDao(connectionString)) tells the container how to make a ProductDao: with this method, which passes the connection string.
  • AddSingleton<UserService>() tells it to make a UserService with its constructor. When a class's constructor only asks for other registered services (or nothing), the type alone is enough.

Now a controller just asks:

public class ProductsController : Controller
{
    private readonly ProductDao productDao;

    public ProductsController(ProductDao productDao)
    {
        this.productDao = productDao;
    }
}

When a request comes in, ASP.NET Core sees the constructor wants a ProductDao, finds the registration, makes one, and passes it in. The controller no longer knows where the connection string comes from.

Asking for a type that isn't registered is an error when the controller is created:

InvalidOperationException: Unable to resolve service for type 'NorthwindWeb.Data.ProductDao' while attempting to activate 'NorthwindWeb.Controllers.ProductsController'.

If you see it, the service is missing from Program.cs.

Lifetimes

Each registration says how long one object lives:

Method One object per Use for
AddSingleton The whole app, shared by every request Things with no per-request data, that are costly to make or must be shared: a UserService that holds a list
AddScoped Request: every class in one request shares it DAOs and services that work with the database: the usual choice
AddTransient Every time it's asked for Small, light helpers with no state

A scoped ProductDao is created when a request needs it and thrown away when the request ends. Two controllers or services in the same request get the same one.

A singleton is shared by everyone, at the same time. Never put one user's data in a singleton: every other user would see it.

Services in views

A view can ask for a service too, with @inject:

@inject NorthwindWeb.Data.ProductDao ProductDao

<p>Northwind sells @ProductDao.Count() products.</p>

Use it sparingly: data for a page usually belongs in the model the controller passes.

The services you've already used

AddControllersWithViews() in Program.cs registers MVC's own services, and IConfiguration, which you've asked for since Controllers and Actions, was registered by WebApplication.CreateBuilder. Everything in ASP.NET Core comes from this container.

Check yourself

2 questions to try on the lesson page.

So what

Register each service once, ask for it by type, and ASP.NET Core does the creating. Controllers stay short, every dependency is visible in its constructor, and tests can hand in fakes.

Key terms

  • Dependency Injection
  • AddScoped, AddTransient, AddSingleton
  • Service Lifetime
  • @inject

Cheat sheets