Dependency Injection in Blazor Components Explained

Dependency Injection in Blazor: Clean, Testable Services

Learn Blazor DI fast: inject HttpClient the right way, choose lifetimes wisely, and build components that are easy to test and maintain.

.NET Nuggets·By amarozka · October 14, 2025

Dependency Injection in Blazor: Clean, Testable Services

Are you still new-ing up HttpClient in Blazor components? Please stop – your tests (and sockets) will thank you.

Why DI matters in Blazor

In Blazor, components are just UI; your logic should live in services that you can swap in tests. DI (dependency injection) wires those services together. You gain:

  • Cleaner components: fewer try/catch blocks and no manual object setup.
  • Swap-in fakes: pass a mock or test double without touching UI.
  • Central config: logging, base URLs, and policies live in one place.

Quick scope note: Scoped is per circuit in Blazor Server (one user connection). In Blazor WebAssembly, Scoped behaves like Singleton for the app. Pick lifetimes with that in mind.

Inject HttpClient the right way

Most bugs I see come from hand-made new HttpClient(). In my projects I register typed clients:

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;

public interface ITodoApi
{
    Task<string> GetAsync();
}

public sealed class TodoApi : ITodoApi
{
    private readonly HttpClient _http;
    public TodoApi(HttpClient http) => _http = http;
    public Task<string> GetAsync() => _http.GetStringAsync("/todos/1");
}

var services = new ServiceCollection();
services.AddHttpClient<ITodoApi, TodoApi>(c =>
    c.BaseAddress = new Uri("https://jsonplaceholder.typicode.com"));

using var sp = services.BuildServiceProvider();
var api = sp.GetRequiredService<ITodoApi>();
Console.WriteLine(await api.GetAsync());

How this maps to Blazor:

  • Register in Program.cs:
    • builder.Services.AddHttpClient<ITodoApi, TodoApi>(c => c.BaseAddress = new Uri(navigation.BaseUri));
  • Inject into a component: @inject ITodoApi Api
  • Call: var todo = await Api.GetAsync();

This gives you pooled handlers, central config, and a seam for tests (swap ITodoApi with a fake).

Component injection: @inject vs services

  • In components use @inject (or [Inject] on properties) for service interfaces only.
  • Keep logic in services. If a method grows past ~10 lines, move it out. Your future self will be happier when writing tests.

Lifetimes that don’t bite

  • Singleton: one per app. Safe for stateless helpers (formatters, mappers). Never hold per-user state here.
  • Scoped: per user (Server) / app-wide (WASM). Good for state containers and business services.
  • Transient: new every time. Use for short-lived helpers.

Tip: if a service holds user state in Blazor Server, it should be Scoped, not Singleton.

Common pitfalls (and fixes)

  • Creating HttpClient with newsocket exhaustion & no policies.
    • Breaks when: traffic spikes or DNS changes.
    • Fix: use AddHttpClient (typed client). Add retries/timeouts with handlers.
  • Injecting DbContext into a Singletonconcurrency and disposed instances.
    • Breaks when: long-running background tasks call it.
    • Fix: register DbContext as Scoped; inject a factory into singletons and create scopes.
  • Wrong lifetime for statedata leaks between users (Server) or stale state (WASM).
    • Breaks when: two users see each other’s data.
    • Fix: keep per-user state in Scoped; clear it on sign-out.

Testing the easy way

  • Code against interfaces (ITodoApi).
  • For unit tests, provide a fake: services.AddSingleton<ITodoApi>(new FakeTodoApi());
  • Avoid injecting HttpClient directly into components; call a service instead. It’s far simpler to fake.

Checklist

  • Use AddHttpClient with typed clients; never new HttpClient().
  • Put logic in services; components just call them.
  • Pick Scoped for per-user state in Server.
  • Keep singletons stateless.
  • Test via interfaces and fakes.

Try it now (5‑minute task)

Find one component that creates HttpClient. Move the call into a service (ITodoApi), register it with AddHttpClient, inject the interface into the component, and run your tests. Timeboxed to one coffee.

Wrap‑up

DI is the backbone of tidy Blazor apps. Push code into services, inject interfaces, and choose lifetimes with care. Your components stay small, your tests stay fast, and your team stays calm.

What’s the most annoying DI bug you’ve hit in Blazor, and how did you fix it? Drop it in the comments.

Want to go deeper with Blazor? Experiment with different service lifetimes and scopes to see how they impact your app’s behavior and performance!

Leave a Reply

Your email address will not be published. Required fields are marked *