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 asbuilder.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
InvalidOperationExceptionsaying "Unable to resolve service for type" means a type isn't registered: add it toProgram.cs. AddScopedmakes one object per request, the usual choice for DAOs;AddSingletonone for the whole app;AddTransienta 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 aProductDao: with this method, which passes the connection string.AddSingleton<UserService>()tells it to make aUserServicewith 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