20 KiB
title | author | description | monikerRange | ms.author | ms.custom | ms.date | no-loc | uid | zone_pivot_groups | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
ASP.NET Core Blazor dependency injection | guardrex | Learn how Blazor apps can inject services into components. | >= aspnetcore-3.1 | riande | mvc | 12/19/2020 |
|
blazor/fundamentals/dependency-injection | blazor-hosting-models |
ASP.NET Core Blazor dependency injection
By Rainer Stropek and Mike Rousos
Dependency injection (DI) is a technique for accessing services configured in a central location:
- Framework-registered services can be injected directly into components of Blazor apps.
- Blazor apps define and register custom services and make them available throughout the app via DI.
Default services
The services shown in the following table are commonly used in Blazor apps.
Service | Lifetime | Description |
---|---|---|
xref:System.Net.Http.HttpClient | Scoped | Provides methods for sending HTTP requests and receiving HTTP responses from a resource identified by a URI. The instance of xref:System.Net.Http.HttpClient in a Blazor WebAssembly app uses the browser for handling the HTTP traffic in the background. Blazor Server apps don't include an xref:System.Net.Http.HttpClient configured as a service by default. Provide an xref:System.Net.Http.HttpClient to a Blazor Server app. For more information, see xref:blazor/call-web-api. An xref:System.Net.Http.HttpClient is registered as a scoped service, not singleton. For more information, see the Service lifetime section. |
xref:Microsoft.JSInterop.IJSRuntime | Blazor WebAssembly: Singleton Blazor Server: Scoped |
Represents an instance of a JavaScript runtime where JavaScript calls are dispatched. For more information, see xref:blazor/js-interop/call-javascript-from-dotnet. |
xref:Microsoft.AspNetCore.Components.NavigationManager | Blazor WebAssembly: Singleton Blazor Server: Scoped |
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
::: zone pivot="webassembly"
Configure services for the app's service collection in the Program.Main
method of Program.cs
. In the following example, the MyDependency
implementation is registered for IMyDependency
:
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
...
builder.Services.AddSingleton<IMyDependency, MyDependency>();
...
await builder.Build().RunAsync();
}
}
After the host is built, services are available 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 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();
}
}
::: zone-end
::: zone pivot="server"
After creating a new app, examine the Startup.ConfigureServices
method in Startup.cs
:
using Microsoft.Extensions.DependencyInjection;
...
public void ConfigureServices(IServiceCollection services)
{
...
}
The xref:Microsoft.Extensions.Hosting.IHostBuilder.ConfigureServices%2A method is passed an xref:Microsoft.Extensions.DependencyInjection.IServiceCollection, which is a list of service descriptor objects. 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>();
}
::: zone-end
Service lifetime
Services can be configured with the lifetimes shown in the following table.
Lifetime | Description |
---|---|
xref:Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Scoped%2A | Blazor WebAssembly apps don't currently have a concept of DI scopes. The Blazor Server hosting model supports the
For more information on preserving user state across scoped services in Blazor Server apps, see xref:blazor/hosting-models?pivots=server. |
xref:Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Singleton%2A | DI creates a single instance of the service. All components requiring a Singleton service receive an instance of the same service. |
xref:Microsoft.Extensions.DependencyInjection.ServiceDescriptor.Transient%2A | 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 xref:fundamentals/dependency-injection.
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, which 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 xref:mvc/views/dependency-injection.
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:
::: moniker range=">= aspnetcore-5.0"
::: moniker-end
::: moniker range="< aspnetcore-5.0"
::: moniker-end
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 xref:Microsoft.AspNetCore.Components.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 following example, DataAccess
requires the xref:System.Net.Http.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 xref:System.Net.Http.HttpClient via DI. xref:System.Net.Http.HttpClient is a default service.
using System.Net.Http;
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 xref:Microsoft.AspNetCore.Components.OwningComponentBase type. xref:Microsoft.AspNetCore.Components.OwningComponentBase is an abstract type derived from xref:Microsoft.AspNetCore.Components.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 xref:Microsoft.AspNetCore.Components.OwningComponentBase type are available:
-
xref:Microsoft.AspNetCore.Components.OwningComponentBase is an abstract, disposable child of the xref:Microsoft.AspNetCore.Components.ComponentBase type with a protected xref:Microsoft.AspNetCore.Components.OwningComponentBase.ScopedServices property of type xref:System.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 xref:Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService%2A or xref:System.IServiceProvider.GetService%2A. Any services resolved using the xref:Microsoft.AspNetCore.Components.OwningComponentBase.ScopedServices provider have their dependencies provided from that same scope.::: moniker range=">= aspnetcore-5.0"
::: moniker-end
::: moniker range="< aspnetcore-5.0"
::: moniker-end
-
xref:Microsoft.AspNetCore.Components.OwningComponentBase%601 derives from xref:Microsoft.AspNetCore.Components.OwningComponentBase and adds a xref:Microsoft.AspNetCore.Components.OwningComponentBase%601.Service%2A 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 xref:System.IServiceProvider when there's one primary service the app requires from the DI container using the component's scope. The xref:Microsoft.AspNetCore.Components.OwningComponentBase.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 xref:blazor/blazor-server-ef-core.
Detect transient disposables
The following examples show how to detect disposable transient services in an app that should use xref:Microsoft.AspNetCore.Components.OwningComponentBase. For more information, see the Utility base component classes to manage a DI scope section.
::: zone pivot="webassembly"
DetectIncorrectUsagesOfTransientDisposables.cs
:
::: moniker range=">= aspnetcore-5.0"
::: moniker-end
::: moniker range="< aspnetcore-5.0"
::: moniker-end
The TransientDisposable
in the following example is detected (Program.cs
):
::: moniker range=">= aspnetcore-5.0"
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(builder.HostEnvironment.BaseAddress)
});
var host = builder.Build();
host.EnableTransientDisposableDetection();
await host.RunAsync();
}
}
public class TransientDisposable : IDisposable
{
public void Dispose() => throw new NotImplementedException();
}
::: moniker-end
::: moniker range="< aspnetcore-5.0"
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();
}
::: moniker-end
::: zone-end
::: zone pivot="server"
DetectIncorrectUsagesOfTransientDisposables.cs
:
::: moniker range=">= aspnetcore-5.0"
::: moniker-end
::: moniker range="< aspnetcore-5.0"
::: moniker-end
Add the namespace for xref:Microsoft.Extensions.DependencyInjection?displayProperty=fullName to Program.cs
:
using Microsoft.Extensions.DependencyInjection;
In Program.CreateHostBuilder
of Program.cs
:
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;
}
}
::: zone-end
The app can register transient disposables without throwing an exception. However, attempting to resolve a transient disposable results in an xref:System.InvalidOperationException, as the following example shows.
Pages/TransientDisposable.razor
:
@page "/transient-disposable"
@inject TransientDisposable TransientDisposable
<h1>Transient Disposable Detection</h1>
Navigate to the TransientDisposable
component at /transient-disposable
and an xref:System.InvalidOperationException is thrown when the framework attempts to construct an instance of TransientDisposable
:
System.InvalidOperationException: Trying to resolve transient disposable service TransientDisposable in the wrong scope. Use an 'OwningComponentBase<T>' component base class for the service 'T' you are trying to resolve.