Default Services

  • HttpClient (Scoped)
  • IJSRuntime (Server=Scoped, WASM=Singleton) — an instance of a JavaScript runtime where JS calls are dispatched
  • NavigationManager (Server=Scoped, WASM=Singleton) — helpers for working with URIs and navigation state

Requesting Services in Components

Inject services into components using @inject directive:

@page "/customer-list"
@* Inject type into property *@
@inject IDataAccess DataRepository

@if (customers != null)
{
    <ul>
        @foreach (var customer in customers)
        {
            <li>@customer.FirstName @customer.LastName</li>
        }
    </ul>
}
@code {
    private IReadOnlyList<Customer>? customers;

    protected override async Task OnInitializedAsync()
    {
        customers = await DataRepository.GetAllCustomersAsync();
    }

    private class Customer
    {
        public string? FirstName { get; set; }
        public string? LastName { get; set; }
    }

    private interface IDataAccess
    {
        public Task<IReadOnlyList<Customer>> GetAllCustomersAsync();
    }
}

If a base class is required for a component, and injected properties are required for that component, use the [Inject] attribute:

using Microsoft.AspNetCore.Components;

public class ComponentBase : IComponent
{
    [Inject]
    // Do not mark injected services nullable.  Use a default value instead.
    protected IDataAccess DataRepository { get; set; }
    ...
}

In components derived from the base class, the @inject directive is not necessary.

Managing DI Scope

OwningComponentBase

Use OwningComponentBase to limit a service’s lifetime in Blazor apps. OwningComponentBase has property protected IServiceProvider ScopedServices and creates a DI scope corresponding to the lifetime of the component. This allows DI services with a scoped lifetime to live as long as the component.

If a service is injected into a component with @inject or [Inject] are not created in the component’s scope. They must be resolved from ScopedServices.

Pages/TimeTravel.razor

@page "/time-travel"
@inject ITimeTravel TimeTravel1
@inherits OwningComponentBase

<h1><code>OwningComponentBase</code> Example</h1>

<ul>
    <li>TimeTravel1.DT: @TimeTravel1?.DT</li> @* receives service from DI container *@
    <li>TimeTravel2.DT: @TimeTravel2?.DT</li> @* receives service from OwningComponentBase.ScopedServices *@
</ul>
@code {
    private ITimeTravel? TimeTravel2 { get; set; }

    protected override void OnInitialized()
    {
        TimeTravel2 = ScopedServices.GetRequiredService<ITimeTravel>();
    }
}

Shared/NavMenu.razor

<div class="nav-item px-3">
    <NavLink class="nav-link" href="time-travel">
        <span class="oi oi-list-rich" aria-hidden="true"></span> Time travel
    </NavLink>
</div>

Initially navigate to TimeTravel component. Output:

TimeTravel1.DT: 8/31/2022 2:54:45 PM
TimeTravel2.DT: 8/31/2022 2:54:45 PM

Navigate away and back again. Output:

// value the same since TimeTravel1 has same service instance from when component was first created; tied to the user's circuit:
TimeTravel1.DT: 8/31/2022 2:54:45 PM 
// value is different since TimeTravel2 obtains a new TimeTravel instance:
TimeTravel2.DT: 8/31/2022 2:54:59 PM

OwningComponentBase<T>

OwningComponentBase<T> has a Service property that returns an instance of T from the scoped DI container. Use this when there’s only one primary service the app needs from the DI container using the component’s scope. The ScopedServices property is still available if needed:

@page "/users"
@attribute [Authorize]
@inherits OwningComponentBase<AppDbContext>

<h1>Users (@Service.Users.Count())</h1>

<ul>
    @foreach (var user in Service.Users)
    {
        <li>@user.UserName</li>
    }
</ul>