question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Blazor Server DelegateHandler does not receive correct (circuit-)scoped service instance

See original GitHub issue

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

I have simple login process.

This works correctly in Windows Forms and .NET MAUI application because they don’t have any circuit scope in DI.

For username and password I receive token. I store token in circuit-scoped service “AuthState”.

Blazor Server login page code:

private async Task LogInInternal(LoginRequest data)
{
    InfoMessage = "Logging in...";
    var session = await Client.LogInAsync(data);
    AuthState.Session = session;
}

I add the token as HTTP header for every request using the ApiRequestAuthInterceptor which is a DelegateHandler (allows customizing outgoing HTTP requests), I inject the “AuthState” object.

public static class RestApiClientSetup
{
    public static IServiceCollection AddApiClient<T>(this IServiceCollection services, string apiBaseUrl)
        where T : class //and where T has HttpClient constructor parameter
    {
        //.AddHttpClient -> NuGet package Microsoft.Extensions.Http
        services.AddTransient<ApiRequestAuthInterceptor>();
        services.AddHttpClient<T>(httpClient =>
        {
            httpClient.BaseAddress = new Uri(apiBaseUrl);
        }).AddHttpMessageHandler<ApiRequestAuthInterceptor>();
        return services;
    }
}
services.AddScoped<AuthState>();
/// <summary>
/// Must be registered as transient
/// </summary>
public class ApiRequestAuthInterceptor : DelegatingHandler
{
    private readonly AuthState authState;
    public ApiRequestAuthInterceptor(AuthState authState)
    {
        this.authState = authState;
    }

    protected override async Task<HttpResponseMessage> SendAsync(
    HttpRequestMessage request, CancellationToken cancellationToken)
    {
        string sessionID = authState.Session?.SessionID;
        if (sessionID != null)
        {
            request.Headers.Authorization = new AuthenticationHeaderValue("SessionID", sessionID);
        }
        if (sessionID is null)
        {
            request.Headers.Authorization = null;
        }
        return await base.SendAsync(request, cancellationToken);
    }
}

When I log in, I have AuthState with ID 2 (using static counter from 1 incremented in constructor) in Blazor Server Page but when I do another call from the same page, the ApiRequestAuthInterceptor sees AuthState with ID 1 (incorrect).

public class AuthState
{
    private static int counter;
    public int ID { get; private set; }
    public AuthState()
    {
        counter++;
        ID = counter;
        Trace.WriteLine($"Created AuthState instance #{counter}");
    }
    public Session Session { get; set; }
}

It works correctly when I register AuthState as Singleton when I only do local testing with 1 browser tab but that will break with more tabs open because they would all get the same data if AuthState was Singleton.

_Host page looks like this if needed (I tried changing render-mode to Server):

@page "/"
@namespace SchoolSocialSite.Web.Blazor.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = "_Layout";
}

<component type="typeof(App)" render-mode="Server" />

Expected Behavior

Both Blazor Server Page and the DelegateHandler see the same AuthState which is scoped for the active circuit.

Steps To Reproduce

mentioned above

Exceptions (if any)

No response

.NET Version

7.0.302 but using .NET 6 in the project, thus probably 6.0.16

Anything else?

No response

Issue Analytics

  • State:closed
  • Created 4 months ago
  • Comments:7 (3 by maintainers)

github_iconTop GitHub Comments

1reaction
javiercncommented, Jun 4, 2023

@janseris thanks for contacting us.

This is something that we made possible in .NET 8.0 via new APIs on top of CircuitHandler. https://devblogs.microsoft.com/dotnet/asp-net-core-updates-in-dotnet-8-preview-3/

0reactions
msftbot[bot]commented, Jun 7, 2023

This issue has been resolved and has not had any activity for 1 day. It will be closed for housekeeping purposes.

See our Issue Management Policies for more information.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Obtaining a scoped service in a custom delegating handler ...
After several days of troubleshooting, I'm forced to concede that the delegating handler simply isn't a workable solution for this.
Read more >
ASP.NET Core Blazor dependency injection
An HttpClient is registered as a scoped service, not singleton. ... user's circuit, TimeTravel2 receives a new ITimeTravel service instance ...
Read more >
Injecting scoped services into DelegatingHandler throws ...
Coding example for the question Injecting scoped services into DelegatingHandler throws InvalidOperationException-blazor.
Read more >
Obtaining a scoped service in a custom delegating handler in ...
i had a similar issue in blazor server where i wanted to add some http headers to the request, the headers being relevant...
Read more >
Cannot Consume Scoped Service From Singleton
Scoped means that a new instance will be created essentially per page load (Atleast that's the “scope” within ASP.NET Core). services.AddScoped<MyFatherService> ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found