-
Notifications
You must be signed in to change notification settings - Fork 25.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document how to access Blazor Services from another DI service scope. #27908
Comments
Here's some content for this issue: (Blazor Server) Access Blazor Services from Another DI ScopeThere may be times when a Blazor components invoke asynchronous methods that execute code in a different DI scope. Without some work, these DI scopes won't have access to Blazor's services, like For example, Until this is addressed as a product feature at a future time, there is a workaround to configure Blazor services to be accessible from code in other DI scopes when invoked from a Blazor component. First, create a static class internal static class BlazorServiceAccessor
{
private static readonly AsyncLocal<IServiceProvider> s_blazorServices = new();
public static IServiceProvider Services
{
get => s_blazorServices.Value ?? throw new InvalidOperationException(
"Blazor services are not available in the current context.");
set => s_blazorServices.Value = value;
}
} This class defines an Next, we need a way to set the value of
Following is the implementation for this base component, using Microsoft.AspNetCore.Components;
public class CustomComponentBase : ComponentBase, IHandleEvent, IHandleAfterRender
{
private bool _hasCalledOnAfterRender;
[Inject]
private IServiceProvider Services { get; set; } = default!;
public override Task SetParametersAsync(ParameterView parameters)
=> InvokeWithBlazorServiceContext(() => base.SetParametersAsync(parameters));
Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg)
=> InvokeWithBlazorServiceContext(() =>
{
var task = callback.InvokeAsync(arg);
var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&
task.Status != TaskStatus.Canceled;
StateHasChanged();
return shouldAwaitTask ?
CallStateHasChangedOnAsyncCompletion(task) :
Task.CompletedTask;
});
Task IHandleAfterRender.OnAfterRenderAsync()
=> InvokeWithBlazorServiceContext(() =>
{
var firstRender = !_hasCalledOnAfterRender;
_hasCalledOnAfterRender |= true;
OnAfterRender(firstRender);
return OnAfterRenderAsync(firstRender);
});
private async Task CallStateHasChangedOnAsyncCompletion(Task task)
{
try
{
await task;
}
catch
{
if (task.IsCanceled)
{
return;
}
throw;
}
StateHasChanged();
}
private async Task InvokeWithBlazorServiceContext(Func<Task> func)
{
BlazorServiceAccessor.Services = Services;
await func();
}
} Any components extending I'm not completely sure what the right place for this content is. Maybe a page one of the "Advanced" sections? |
@MackinnonBuck ... Do you want to address Javier's ideas for the code updates on the PR? I can try to make those ideas work, but I suspect that I'm going to hit problems that I don't know how to solve and end up wasting Gaurav's 💰💰💰. |
This issue applies to Blazor Server specifically.
We've received some issues like dotnet/aspnetcore#25758 and dotnet/aspnetcore#40336 where customers try to get Blazor services from an
IServiceProvider
created from a different DI scope. In both the previous linked issues, anIHttpClientFactory
was used to create anHttpClient
configured to utilize a Blazor service in some way. This fails because theseHttpClient
s will have their own DI service scopes, so they can't access Blazor services directly.We should document how customers can access Blazor Server services from code where the Blazor
IServiceProvider
is not already accessible, specifically when that code is invoked from a Blazor component.One approach to make this work is by using an
AsyncLocal
to capture the Blazor component'sIServiceProvider
so it can be retrieved later in the async call stack.In the future, we hope to add a product feature that addresses this issue more fundamentally, making the code provided by this example unnecessary.
The text was updated successfully, but these errors were encountered: