Skip to content

Wiring up dependency injection

Replace hand-written service registrations with [RegisterSingleton] and AddTodoServices().

2 min read

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

TodoRepository.cs
using Immediate.Injections.Shared;

namespace Todo;

[RegisterSingleton]
public sealed class TodoRepository
{
	// … unchanged …
}

Then replace the hand-written line with the generated method:

Program.cs
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:

TodoRepository.cs
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:

Features/GetTodosQuery.cs
[Handler]
public sealed partial class GetTodosQuery(ITodoRepository repository)
{
	// … unchanged …
}

What else it does

Beyond the lifetime attributes, the package covers most of what people reach for a container extension to do:

NeedWhere
Resolve by keyKeyed services
IRepository<>Repository<>Open generics
Construct via a static methodFactories and proxies
Register only in some hostsTagged registration
Registration that needs real codeManual registration
Set a default strategy for the whole assemblyAssembly-wide defaults

Run it

terminal
dotnet run
curl http://localhost:5000/api/todos

Everything 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.

Up next

Where to go next.