Blazor · App Structure

Services

Components should display things and react to users. Services do the work — loading data, calculating totals, holding shared state.

Intermediate 5 min Lesson 17 of 31Examples target .NET 9 Blazor Web App.
Before you start:Dependency Injection
0/31 in Blazor
On this page · 5 min · Intermediate

Learning objectives#

  • Move business logic into services
  • Design interfaces that work on server and WebAssembly
  • Build a state container service
  • Test services without a UI

Explanation#

A service is a plain C# class registered in DI. Components inject it and stay thin.

For apps that use both Interactive Server and WebAssembly, define an interface (IProductService) with two implementations: one that uses EF Core on the server, one that calls the API with HttpClient in the client.

A state container is a scoped service holding shared UI state (cart, filters) and raising an OnChange event.

Code example: A cart state container#

C#Services/CartState.cs Illustrative
public class CartState
{
    private readonly List<CartLine> lines = [];
    public IReadOnlyList<CartLine> Lines => lines;
    public decimal Total => lines.Sum(l => l.Price * l.Qty);
    public event Action? OnChange;


    public void Add(Product p)
    {
        var line = lines.FirstOrDefault(l => l.ProductId == p.Id);
        if (line is null) lines.Add(new CartLine(p.Id, p.Name, p.Price, 1));
        else line.Qty++;
        OnChange?.Invoke();
    }
}
public class CartLine(int productId, string name, decimal price, int qty)
{
    public int ProductId { get; } = productId; public string Name { get; } = name;
    public decimal Price { get; } = price; public int Qty { get; set; } = qty;
}


// Program.cs: builder.Services.AddScoped<CartState>();
// CartBadge.razor: @inject CartState Cart  →  subscribe in OnInitialized: Cart.OnChange += StateHasChanged;
//                  and unsubscribe in Dispose: Cart.OnChange -= StateHasChanged;
Expected output
Adding a laptop on the product page instantly updates the cart badge in the header to “1 · ₹55,000”.

This example is illustrative. Copy or download it and run it in your own environment; the expected output is shown above.

Step-by-step explanation#

  1. The service owns the cart lines and the total calculation.
  2. Add raises OnChange after every change.
  3. Any component can subscribe and call StateHasChanged.
  4. Unsubscribing in Dispose prevents memory leaks.

Real-world use case#

A header cart badge and the product page stay in sync without passing parameters through every layout.

Common mistakes#

Registering per-user state as a singleton

All users would share one cart. Use Scoped.

Calling StateHasChanged from a background thread

Use InvokeAsync(StateHasChanged) when the event may come from another thread.

Best practices#

  • Thin components, rich services
  • Interfaces for render-mode-specific implementations
  • Unit test services directly
  • Unsubscribe from events in Dispose

Remember this#

Components show; services do.

State container = scoped service + OnChange event.

Subscribe in OnInitialized, unsubscribe in Dispose.

Practice exercise#

Your task: Add a Remove(int productId) method to CartState.

Show solution
C# Illustrative
public void Remove(int productId)
{
    lines.RemoveAll(l => l.ProductId == productId);
    OnChange?.Invoke();
}

Mini quiz#

Services quizMultiple choice Question 1 of 1

Which lifetime suits a per-user cart in Interactive Server?

Score so far: 0/0

Want more? Practise all Blazor MCQs · Blazor interview questions

My notes#

Saved in this browser.

Progress is saved in this browser. Sign in to sync.