Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 60 additions & 30 deletions aspnetcore/blazor/fundamentals/dependency-injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,9 @@ Prerequisites for constructor injection:

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](#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.
Expand Down Expand Up @@ -220,8 +216,6 @@ Two versions of the <xref:Microsoft.AspNetCore.Components.OwningComponentBase> t

For more information, see <xref:blazor/blazor-server-ef-core>.

<!--

## Detect transient disposables

The following example shows 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](#utility-base-component-classes-to-manage-a-di-scope) section.
Expand Down Expand Up @@ -267,6 +261,21 @@ host.EnableTransientDisposableDetection();
await host.RunAsync();
```

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/TransientExample.razor`:

```razor
@page "/transient-example"
@inject TransientDisposable TransientDisposable

<h1>Transient Disposable Detection</h1>
```

Navigate to the `TransientExample` component at `/transient-example` 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.

::: zone-end

::: zone pivot="server"
Expand Down Expand Up @@ -307,37 +316,28 @@ The `TransientDependency` in the following example is detected.
In `Program.cs`:

```csharp
...

builder.DetectIncorrectUsageOfTransients();
builder.Services.AddTransient<TransientDependency>();
builder.Services.AddTransient<ITransitiveTransientDisposableDependency,
TransitiveTransientDisposableDependency>();

...

app.DetectIncorrectUsageOfTransients();

...
```

::: 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/TransientExample.razor`:

```razor
@page "/transient-example"
@inject TransientDisposable TransientDisposable
@inject TransientDependency TransientDependency

<h1>Transient Disposable Detection</h1>
```

Navigate to the `TransientExample` component at `/transient-example` and an <xref:System.InvalidOperationException> is thrown when the framework attempts to construct an instance of `TransientDisposable`:
Navigate to the `TransientExample` component at `/transient-example` and an <xref:System.InvalidOperationException> is thrown when the framework attempts to construct an instance of `TransientDependency`:

> 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.
> System.InvalidOperationException: Trying to resolve transient disposable service TransientDependency in the wrong scope. Use an 'OwningComponentBase\<T>' component base class for the service 'T' you are trying to resolve.

-->
::: zone-end

## Additional resources

Expand Down Expand Up @@ -613,6 +613,21 @@ public class TransientDisposable : IDisposable
}
```

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/TransientExample.razor`:

```razor
@page "/transient-example"
@inject TransientDisposable TransientDisposable

<h1>Transient Disposable Detection</h1>
```

Navigate to the `TransientExample` component at `/transient-example` 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.

::: zone-end

::: zone pivot="server"
Expand Down Expand Up @@ -676,22 +691,22 @@ public class TransientDependency
}
```

::: 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/TransientExample.razor`:

```razor
@page "/transient-example"
@inject TransientDisposable TransientDisposable
@inject TransientDependency TransientDependency

<h1>Transient Disposable Detection</h1>
```

Navigate to the `TransientExample` component at `/transient-example` and an <xref:System.InvalidOperationException> is thrown when the framework attempts to construct an instance of `TransientDisposable`:
Navigate to the `TransientExample` component at `/transient-example` and an <xref:System.InvalidOperationException> is thrown when the framework attempts to construct an instance of `TransientDependency`:

> 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.
> System.InvalidOperationException: Trying to resolve transient disposable service TransientDependency in the wrong scope. Use an 'OwningComponentBase\<T>' component base class for the service 'T' you are trying to resolve.

::: zone-end

## Additional resources

Expand Down Expand Up @@ -967,6 +982,21 @@ public class TransientDisposable : IDisposable
}
```

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/TransientExample.razor`:

```razor
@page "/transient-example"
@inject TransientDisposable TransientDisposable

<h1>Transient Disposable Detection</h1>
```

Navigate to the `TransientExample` component at `/transient-example` 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.

::: zone-end

::: zone pivot="server"
Expand Down Expand Up @@ -1030,22 +1060,22 @@ public class TransientDependency
}
```

::: 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/TransientExample.razor`:

```razor
@page "/transient-example"
@inject TransientDisposable TransientDisposable
@inject TransientDependency TransientDependency

<h1>Transient Disposable Detection</h1>
```

Navigate to the `TransientExample` component at `/transient-example` and an <xref:System.InvalidOperationException> is thrown when the framework attempts to construct an instance of `TransientDisposable`:
Navigate to the `TransientExample` component at `/transient-example` and an <xref:System.InvalidOperationException> is thrown when the framework attempts to construct an instance of `TransientDependency`:

> 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.
> System.InvalidOperationException: Trying to resolve transient disposable service TransientDependency in the wrong scope. Use an 'OwningComponentBase\<T>' component base class for the service 'T' you are trying to resolve.

::: zone-end

## Additional resources

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ namespace Microsoft.Extensions.DependencyInjection

public static class WebHostBuilderTransientDisposableExtensions
{
public static IHostBuilder DetectIncorrectUsageOfTransients(
this IHostBuilder builder)
public static WebApplicationBuilder DetectIncorrectUsageOfTransients(
this WebApplicationBuilder builder)
{
builder
builder.Host
.UseServiceProviderFactory(
new DetectIncorrectUsageOfTransientDisposablesServiceFactory())
.ConfigureServices(
Expand Down Expand Up @@ -81,6 +81,13 @@ private ServiceDescriptor CreatePatchedFactoryDescriptor(
(sp) =>
{
var originalFactory = original.ImplementationFactory;

if (originalFactory is null)
{
throw new InvalidOperationException(
"originalFactory is null.");
}

var originalResult = originalFactory(sp);

var throwOnTransientDisposable =
Expand Down Expand Up @@ -114,12 +121,18 @@ private ServiceDescriptor CreatePatchedDescriptor(
{
throw new InvalidOperationException("Trying to resolve " +
"transient disposable service " +
$"{original.ImplementationType.Name} in the wrong " +
$"{original.ImplementationType?.Name} in the wrong " +
"scope. Use an 'OwningComponentBase<T>' component " +
"base class for the service 'T' you are trying to " +
"resolve.");
}

if (original.ImplementationType is null)
{
throw new InvalidOperationException(
"ImplementationType is null.");
}

return ActivatorUtilities.CreateInstance(sp,
original.ImplementationType);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ private ServiceDescriptor CreatePatchedFactoryDescriptor(
(sp) =>
{
var originalFactory = original.ImplementationFactory;

if (originalFactory is null)
{
throw new InvalidOperationException(
"originalFactory is null.");
}

var originalResult = originalFactory(sp);

var throwOnTransientDisposable =
Expand Down Expand Up @@ -108,11 +115,17 @@ private ServiceDescriptor CreatePatchedDescriptor(ServiceDescriptor original)
{
throw new InvalidOperationException("Trying to resolve " +
"transient disposable service " +
$"{original.ImplementationType.Name} in the wrong " +
$"{original.ImplementationType?.Name} in the wrong " +
"scope. Use an 'OwningComponentBase<T>' component base " +
"class for the service 'T' you are trying to resolve.");
}

if (original.ImplementationType is null)
{
throw new InvalidOperationException(
"ImplementationType is null.");
}

return ActivatorUtilities.CreateInstance(sp,
original.ImplementationType);
},
Expand Down