Wiring up dependency injection
Replace hand-written service registrations with [RegisterSingleton] and AddTodoServices().
TodoRepository has been registered by hand since the first page. Immediate.Injections moves
that registration next to the class it registers, so adding a service never means remembering to
edit Program.cs.
Unlike the other packages, this one has nothing to do with handlers — it registers any class.
Register the repository
using Immediate.Injections.Shared;
namespace Todo;
[RegisterSingleton]
public sealed class TodoRepository
{
// … unchanged …
}Then replace the hand-written line with the generated method:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddTodoServices();
builder.Services.AddTodoBehaviors();
builder.Services.AddTodoHandlers();
builder.Services.AddMemoryCache();
builder.Services.AddTodoCaches();AddTodoServices() registers every class in the assembly carrying a registration attribute. TodoRepository is a singleton here because the tutorial’s in-memory list has to survive between
requests; a real repository over a DbContext would be [RegisterScoped].
There are three lifetime attributes — [RegisterSingleton], [RegisterScoped] and [RegisterTransient] — and each has three forms:
[RegisterScoped] // registers TodoRepository as itself
[RegisterScoped<ITodoRepository>] // as ITodoRepository
[RegisterScoped<ITodoRepository, TodoRepository>] // service → implementation Registering behind an interface
Extract an interface and register the class as its implementation:
using Immediate.Injections.Shared;
namespace Todo;
public interface ITodoRepository
{
SemaphoreSlim GetOperationLock(int id);
IReadOnlyList<TodoItem> GetAll();
TodoItem? GetById(int id);
TodoItem Add(string title);
TodoItem? Complete(int id);
}
[RegisterSingleton<ITodoRepository>]
public sealed class TodoRepository : ITodoRepository
{
// … unchanged …
}Handlers then depend on ITodoRepository:
[Handler]
public sealed partial class GetTodosQuery(ITodoRepository repository)
{
// … unchanged …
}[RegisterSingleton<ITodoRepository>] registers the interface only —
resolving TodoRepository directly will now fail. If you want both, use RegistrationStrategy.SelfAndImplementedInterfaces, which registers the concrete type
and each of its interfaces. Note that resolving through two registrations gives you two
instances unless you also set UseProxyFactory; see Registration strategies and Factories and proxies.What else it does
Beyond the lifetime attributes, the package covers most of what people reach for a container extension to do:
| Need | Where |
|---|---|
| Resolve by key | Keyed services |
IRepository<> → Repository<> | Open generics |
| Construct via a static method | Factories and proxies |
| Register only in some hosts | Tagged registration |
| Registration that needs real code | Manual registration |
| Set a default strategy for the whole assembly | Assembly-wide defaults |
Run it
dotnet run
curl http://localhost:5000/api/todosEverything behaves exactly as before — the only change is where the registration lives.
What you have
A complete Todo API where every registration is generated: handlers, behaviors, caches, endpoints
and services. Program.cs is five Add calls and one Map call, and none of them will drift out
of sync with the code they register.