ASP.NET Core Blazor dependency injection

By Rainer Stropek and Mike Rousos

Blazor supports dependency injection (DI). Apps can use built-in services by injecting them into components. Apps can also define and register custom services and make them available throughout the app via DI.

DI is a technique for accessing services configured in a central location. This can be useful in Blazor apps to:

  • Share a single instance of a service class across many components, known as a singleton service.
  • Decouple components from concrete service classes by using reference abstractions. For example, consider an interface IDataAccess for accessing data in the app. The interface is implemented by a concrete DataAccess class and registered as a service in the app's service container. When a component uses DI to receive an IDataAccess implementation, the component isn't coupled to the concrete type. The implementation can be swapped, perhaps for a mock implementation in unit tests.

Default services

Default services are automatically added to the app's service collection.

Service Lifetime Description
HttpClient Scoped Provides methods for sending HTTP requests and receiving HTTP responses from a resource identified by a URI.

The instance of HttpClient in a Blazor WebAssembly app uses the browser for handling the HTTP traffic in the background.

Blazor Server apps don't include an HttpClient configured as a service by default. Provide an HttpClient to a Blazor Server app.

For more information, see Call a web API from ASP.NET Core Blazor WebAssembly.

An HttpClient is registered as a scoped service, not singleton. For more information, see the Service lifetime section.
IJSRuntime Singleton (Blazor WebAssembly)
Scoped (Blazor Server)
Represents an instance of a JavaScript runtime where JavaScript calls are dispatched. For more information, see Call JavaScript functions from .NET methods in ASP.NET Core Blazor.
NavigationManager Singleton (Blazor WebAssembly)
Scoped (Blazor Server)
Contains helpers for working with URIs and navigation state. For more information, see URI and navigation state helpers.

A custom service provider doesn't automatically provide the default services listed in the table. If you use a custom service provider and require any of the services shown in the table, add the required services to the new service provider.

Add services to an app

Blazor WebAssembly

Configure services for the app's service collection in the Main method of Program.cs. In the following example, the MyDependency implementation is registered for IMyDependency:

using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.DependencyInjection;

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);

        builder.Services.AddSingleton<IMyDependency, MyDependency>();

        ...

        await builder.Build().RunAsync();
    }
}

Once the host is built, services can be accessed from the root DI scope before any components are rendered. This can be useful for running initialization logic before rendering content:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);

        builder.Services.AddSingleton<WeatherService>();

        ...

        var host = builder.Build();

        var weatherService = host.Services.GetRequiredService<WeatherService>();
        await weatherService.InitializeWeatherAsync();

        await host.RunAsync();
    }
}

The host also provides a central configuration instance for the app. Building on the preceding example, the weather service's URL is passed from a default configuration source (for example, appsettings.json) to InitializeWeatherAsync:

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);

        builder.Services.AddSingleton<WeatherService>();

        ...

        var host = builder.Build();

        var weatherService = host.Services.GetRequiredService<WeatherService>();
        await weatherService.InitializeWeatherAsync(
            host.Configuration["WeatherServiceUrl"]);

        await host.RunAsync();
    }
}

Blazor Server

After creating a new app, examine the Startup.ConfigureServices method:

using Microsoft.Extensions.DependencyInjection;

...

public void ConfigureServices(IServiceCollection services)
{
    ...
}

The ConfigureServices method is passed an IServiceCollection, which is a list of service descriptor objects (ServiceDescriptor). Services are added in the ConfigureServices method by providing service descriptors to the service collection. The following example demonstrates the concept with the IDataAccess interface and its concrete implementation DataAccess:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton<IDataAccess, DataAccess>();
}

Service lifetime

Services can be configured with the lifetimes shown in the following table.

Lifetime Description
Scoped

Blazor WebAssembly apps don't currently have a concept of DI scopes. Scoped-registered services behave like Singleton services.

The Blazor Server hosting model supports the Scoped lifetime across HTTP requests but not across SingalR connection/circuit messages among components that are loaded on the client. The Razor Pages or MVC portion of the app treats scoped services normally and recreates the services on each HTTP request when navigating among pages or views or from a page or view to a component. Scoped services aren't reconstructed when navigating among components on the client, where the communication to the server takes place over the SignalR connection of the user's circuit, not via HTTP requests. In the following component scenarios on the client, scoped services are reconstructed because a new circuit is created for the user:

  • The user closes the browser's window. The user opens a new window and navigates back to the app.
  • The user closes the last tab of the app in a browser window. The user opens a new tab and navigates back to the app.
  • The user selects the browser's reload/refresh button.

For more information on preserving user state across scoped services in Blazor Server apps, see ASP.NET Core Blazor hosting models.

Singleton DI creates a single instance of the service. All components requiring a Singleton service receive an instance of the same service.
Transient Whenever a component obtains an instance of a Transient service from the service container, it receives a new instance of the service.

The DI system is based on the DI system in ASP.NET Core. For more information, see Dependency injection in ASP.NET Core.

Request a service in a component

After services are added to the service collection, inject the services into the components using the @inject Razor directive. @inject has two parameters:

  • Type: The type of the service to inject.
  • Property: The name of the property receiving the injected app service. The property doesn't require manual creation. The compiler creates the property.

For more information, see Dependency injection into views in ASP.NET Core.

Use multiple @inject statements to inject different services.

The following example shows how to use @inject. The service implementing Services.IDataAccess is injected into the component's property DataRepository. Note how the code is only using the IDataAccess abstraction:

@page "/customer-list"
@using Services
@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();
    }
}

Internally, the generated property (DataRepository) uses the [Inject] attribute. Typically, this attribute isn't used directly. If a base class is required for components and injected properties are also required for the base class, manually add the [Inject] attribute:

using Microsoft.AspNetCore.Components;

public class ComponentBase : IComponent
{
    [Inject]
    protected IDataAccess DataRepository { get; set; }

    ...
}

In components derived from the base class, the @inject directive isn't required. The InjectAttribute of the base class is sufficient:

@page "/demo"
@inherits ComponentBase

<h1>Demo Component</h1>

Use DI in services

Complex services might require additional services. In the prior example, DataAccess might require the HttpClient default service. @inject (or the [Inject] attribute) isn't available for use in services. Constructor injection must be used instead. Required services are added by adding parameters to the service's constructor. When DI creates the service, it recognizes the services it requires in the constructor and provides them accordingly. In the following example, the constructor receives an HttpClient via DI. HttpClient is a default service.

public class DataAccess : IDataAccess
{
    public DataAccess(HttpClient http)
    {
        ...
    }
}

Prerequisites for constructor injection:

  • One constructor must exist whose arguments can all be fulfilled by DI. Additional parameters not covered by DI are allowed if they specify default values.
  • The applicable constructor must be public.
  • One applicable constructor must exist. In case of an ambiguity, DI throws an exception.

Utility base component classes to manage a DI scope

In ASP.NET Core apps, scoped services are typically scoped to the current request. After the request completes, any scoped or transient services are disposed by the DI system. In Blazor Server apps, the request scope lasts for the duration of the client connection, which can result in transient and scoped services living much longer than expected. In Blazor WebAssembly apps, services registered with a scoped lifetime are treated as singletons, so they live longer than scoped services in typical ASP.NET Core apps.

Note

To detect disposable transient services in an app, see the Detect transient disposables section.

An approach that limits a service lifetime in Blazor apps is use of the OwningComponentBase type. OwningComponentBase is an abstract type derived from ComponentBase that creates a DI scope corresponding to the lifetime of the component. Using this scope, it's possible to use DI services with a scoped lifetime and have them live as long as the component. When the component is destroyed, services from the component's scoped service provider are disposed as well. This can be useful for services that:

  • Should be reused within a component, as the transient lifetime is inappropriate.
  • Shouldn't be shared across components, as the singleton lifetime is inappropriate.

Two versions of the OwningComponentBase type are available:

  • OwningComponentBase is an abstract, disposable child of the ComponentBase type with a protected ScopedServices property of type IServiceProvider. This provider can be used to resolve services that are scoped to the lifetime of the component.

    DI services injected into the component using @inject or the [Inject] attribute aren't created in the component's scope. To use the component's scope, services must be resolved using GetRequiredService or GetService. Any services resolved using the ScopedServices provider have their dependencies provided from that same scope.

    @page "/preferences"
    @using Microsoft.Extensions.DependencyInjection
    @inherits OwningComponentBase
    
    <h1>User (@UserService.Name)</h1>
    
    <ul>
        @foreach (var setting in SettingService.GetSettings())
        {
            <li>@setting.SettingName: @setting.SettingValue</li>
        }
    </ul>
    
    @code {
        private IUserService UserService { get; set; }
        private ISettingService SettingService { get; set; }
    
        protected override void OnInitialized()
        {
            UserService = ScopedServices.GetRequiredService<IUserService>();
            SettingService = ScopedServices.GetRequiredService<ISettingService>();
        }
    }
    
  • OwningComponentBase<TService> derives from OwningComponentBase and adds a Service property that returns an instance of T from the scoped DI provider. This type is a convenient way to access scoped services without using an instance of IServiceProvider when there's one primary service the app requires from the DI container using the component's scope. The ScopedServices property is available, so the app can get services of other types, if necessary.

    @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>
    

Use of an Entity Framework Core (EF Core) DbContext from DI

For more information, see ASP.NET Core Blazor Server with Entity Framework Core (EFCore).

Detect transient disposables

The following examples show how to detect disposable transient services in an app that should use OwningComponentBase. For more information, see the Utility base component classes to manage a DI scope section.

Blazor WebAssembly

DetectIncorrectUsagesOfTransientDisposables.cs:

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

namespace Microsoft.Extensions.DependencyInjection
{
    using BlazorServerTransientDisposable;
    using Microsoft.AspNetCore.Components.WebAssembly.Hosting;

    public static class WebHostBuilderTransientDisposableExtensions
    {
        public static WebAssemblyHostBuilder DetectIncorrectUsageOfTransients(
            this WebAssemblyHostBuilder builder)
        {
            builder
                .ConfigureContainer(
                    new DetectIncorrectUsageOfTransientDisposablesServiceFactory());

            return builder;
        }

        public static WebAssemblyHost EnableTransientDisposableDetection(
            this WebAssemblyHost webAssemblyHost)
        {
            webAssemblyHost.Services
                .GetRequiredService<ThrowOnTransientDisposable>().ShouldThrow = true;
            return webAssemblyHost;
        }
    }
}

namespace BlazorServerTransientDisposable
{
    public class DetectIncorrectUsageOfTransientDisposablesServiceFactory 
        : IServiceProviderFactory<IServiceCollection>
    {
        public IServiceCollection CreateBuilder(IServiceCollection services) => 
            services;

        public IServiceProvider CreateServiceProvider(
            IServiceCollection containerBuilder)
        {
            var collection = new ServiceCollection();
            foreach (var descriptor in containerBuilder)
            {
                if (descriptor.Lifetime == ServiceLifetime.Transient &&
                    descriptor.ImplementationType != null && 
                    typeof(IDisposable).IsAssignableFrom(
                        descriptor.ImplementationType))
                {
                    collection.Add(CreatePatchedDescriptor(descriptor));
                }
                else if (descriptor.Lifetime == ServiceLifetime.Transient &&
                         descriptor.ImplementationFactory != null)
                {
                    collection.Add(CreatePatchedFactoryDescriptor(descriptor));
                }
                else
                {
                    collection.Add(descriptor);
                }
            }

            collection.AddScoped<ThrowOnTransientDisposable>();

            return collection.BuildServiceProvider();
        }

        private ServiceDescriptor CreatePatchedFactoryDescriptor(
            ServiceDescriptor original)
        {
            var newDescriptor = new ServiceDescriptor(
                original.ServiceType,
                (sp) =>
                {
                    var originalFactory = original.ImplementationFactory;
                    var originalResult = originalFactory(sp);

                    var throwOnTransientDisposable = 
                        sp.GetRequiredService<ThrowOnTransientDisposable>();
                    if (throwOnTransientDisposable.ShouldThrow && 
                        originalResult is IDisposable d)
                    {
                        throw new InvalidOperationException("Trying to resolve " +
                            $"transient disposable service {d.GetType().Name} in " +
                            "the wrong scope. Use an 'OwningComponentBase<T>' " +
                            "component base class for the service 'T' you are " +
                            "trying to resolve.");
                    }

                    return originalResult;
                },
                original.Lifetime);

            return newDescriptor;
        }

        private ServiceDescriptor CreatePatchedDescriptor(ServiceDescriptor original)
        {
            var newDescriptor = new ServiceDescriptor(
                original.ServiceType,
                (sp) => {
                    var throwOnTransientDisposable = 
                        sp.GetRequiredService<ThrowOnTransientDisposable>();
                    if (throwOnTransientDisposable.ShouldThrow)
                    {
                        throw new InvalidOperationException("Trying to resolve " +
                        "transient disposable service " +
                        $"{original.ImplementationType.Name} in the wrong " +
                        "scope. Use an 'OwningComponentBase<T>' component base " +
                        "class for the service 'T' you are trying to resolve.");
                    }

                    return ActivatorUtilities.CreateInstance(sp, 
                        original.ImplementationType);
                },
                ServiceLifetime.Transient);
            return newDescriptor;
        }
    }

    internal class ThrowOnTransientDisposable
    {
        public bool ShouldThrow { get; set; }
    }
}

The TransientDisposable in the following example is detected (Program.cs):

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.DetectIncorrectUsageOfTransients();
        builder.RootComponents.Add<App>("#app");

        builder.Services.AddTransient<TransientDisposable>();
        builder.Services.AddScoped(sp =>
            new HttpClient
            {
                BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
            });

        var host = builder.Build();
        host.EnableTransientDisposableDetection();
        await host.RunAsync();
    }
}

public class TransientDisposable : IDisposable
{
    public void Dispose() => throw new NotImplementedException();
}
public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.DetectIncorrectUsageOfTransients();
        builder.RootComponents.Add<App>("app");

        builder.Services.AddTransient<TransientDisposable>();
        builder.Services.AddScoped(sp =>
            new HttpClient
            {
                BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
            });

        var host = builder.Build();
        host.EnableTransientDisposableDetection();
        await host.RunAsync();
    }
}

public class TransientDisposable : IDisposable
{
    public void Dispose() => throw new NotImplementedException();
}

Blazor Server

DetectIncorrectUsagesOfTransientDisposables.cs:

using System;
using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

namespace Microsoft.Extensions.DependencyInjection
{
    using BlazorServerTransientDisposable;

    public static class WebHostBuilderTransientDisposableExtensions
    {
        public static IHostBuilder DetectIncorrectUsageOfTransients(
            this IHostBuilder builder)
        {
            builder
                .UseServiceProviderFactory(
                    new DetectIncorrectUsageOfTransientDisposablesServiceFactory())
                .ConfigureServices(
                    s => s.TryAddEnumerable(ServiceDescriptor.Scoped<CircuitHandler,
                        ThrowOnTransientDisposableHandler>()));

            return builder;
        }
    }
}

namespace BlazorServerTransientDisposable
{
    internal class ThrowOnTransientDisposableHandler : CircuitHandler
    {
        public ThrowOnTransientDisposableHandler(
            ThrowOnTransientDisposable throwOnTransientDisposable)
        {
            throwOnTransientDisposable.ShouldThrow = true;
        }
    }

    public class DetectIncorrectUsageOfTransientDisposablesServiceFactory 
        : IServiceProviderFactory<IServiceCollection>
    {
        public IServiceCollection CreateBuilder(IServiceCollection services) => 
            services;

        public IServiceProvider CreateServiceProvider(
            IServiceCollection containerBuilder)
        {
            var collection = new ServiceCollection();
            foreach (var descriptor in containerBuilder)
            {
                if (descriptor.Lifetime == ServiceLifetime.Transient &&
                    descriptor.ImplementationType != null && 
                    typeof(IDisposable).IsAssignableFrom(
                        descriptor.ImplementationType))
                {
                    collection.Add(CreatePatchedDescriptor(descriptor));
                }
                else if (descriptor.Lifetime == ServiceLifetime.Transient &&
                         descriptor.ImplementationFactory != null)
                {
                    collection.Add(CreatePatchedFactoryDescriptor(descriptor));
                }
                else
                {
                    collection.Add(descriptor);
                }
            }

            collection.AddScoped<ThrowOnTransientDisposable>();

            return collection.BuildServiceProvider();
        }

        private ServiceDescriptor CreatePatchedFactoryDescriptor(
            ServiceDescriptor original)
        {
            var newDescriptor = new ServiceDescriptor(
                original.ServiceType,
                (sp) =>
                {
                    var originalFactory = original.ImplementationFactory;
                    var originalResult = originalFactory(sp);

                    var throwOnTransientDisposable = 
                        sp.GetRequiredService<ThrowOnTransientDisposable>();
                    if (throwOnTransientDisposable.ShouldThrow && 
                        originalResult is IDisposable d)
                    {
                        throw new InvalidOperationException("Trying to resolve " +
                            $"transient disposable service {d.GetType().Name} in " +
                            "the wrong scope. Use an 'OwningComponentBase<T>' " +
                            "component base class for the service 'T' you are " +
                            "trying to resolve.");
                    }

                    return originalResult;
                },
                original.Lifetime);

            return newDescriptor;
        }

        private ServiceDescriptor CreatePatchedDescriptor(
            ServiceDescriptor original)
        {
            var newDescriptor = new ServiceDescriptor(
                original.ServiceType,
                (sp) => {
                    var throwOnTransientDisposable = 
                        sp.GetRequiredService<ThrowOnTransientDisposable>();
                    if (throwOnTransientDisposable.ShouldThrow)
                    {
                        throw new InvalidOperationException("Trying to resolve " +
                            "transient disposable service " +
                            $"{original.ImplementationType.Name} in the wrong " +
                            "scope. Use an 'OwningComponentBase<T>' component " +
                            "base class for the service 'T' you are trying to " +
                            "resolve.");
                    }

                    return ActivatorUtilities.CreateInstance(sp, 
                        original.ImplementationType);
                },
                ServiceLifetime.Transient);
            return newDescriptor;
        }
    }

    internal class ThrowOnTransientDisposable
    {
        public bool ShouldThrow { get; set; }
    }
}

Program:

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .DetectIncorrectUsageOfTransients()
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

The TransientDependency in the following example is detected (Startup.cs):

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();
    services.AddServerSideBlazor();
    services.AddSingleton<WeatherForecastService>();
    services.AddTransient<TransientDependency>();
    services.AddTransient<ITransitiveTransientDisposableDependency, 
        TransitiveTransientDisposableDependency>();
}

public class TransitiveTransientDisposableDependency 
    : ITransitiveTransientDisposableDependency, IDisposable
{
    public void Dispose() { }
}

public interface ITransitiveTransientDisposableDependency
{
}

public class TransientDependency
{
    private readonly ITransitiveTransientDisposableDependency 
        _transitiveTransientDisposableDependency;

    public TransientDependency(ITransitiveTransientDisposableDependency 
        transitiveTransientDisposableDependency)
    {
        _transitiveTransientDisposableDependency = 
            transitiveTransientDisposableDependency;
    }
}

Additional resources