Search code examples
c#blazor-webassemblydotnet-httpclient

Is it possible to call async method in Blazor WASM when configuring named HttpClient?


I am trying to setup named HttpClient in Blazor WASM. Problem is that I need to call async method in order to get JWT.

builder.Services.AddHttpClient("auth", async c =>
    {
        // access the DI container
        var serviceProvider = builder.Services.BuildServiceProvider();
        // Find the HttpContextAccessor service
        var localStorageService = serviceProvider.GetService<ILocalStorageService>();
        // Get the bearer token.
        if (localStorageService == null) return;
        
        var jwt = await localStorageService.GetJwtAsync();
        Console.WriteLine("JWT "+jwt);
        if (jwt != null)
        {
            c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
        }
    }
);

This doesn't work since parameter c cannot be async function.

Next I tried something like this

builder.Services.AddHttpClient("auth", c =>
    {
        // access the DI container
        var serviceProvider = builder.Services.BuildServiceProvider();
        // Find the HttpContextAccessor service
        var localStorageService = serviceProvider.GetService<ILocalStorageService>();
        // Get the bearer token.
        if (localStorageService == null) return;
        
        var jwt = localStorageService.GetJwtAsync().Result;
        Console.WriteLine("JWT "+jwt);
        if (jwt != null)
        {
            c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", jwt);
        }
    }
);

In this case I get an error

Unhandled exception rendering component: Cannot wait on monitors on this runtime

because I am using .Result in WASM and it is blocking the main thread.

Any ideas is this possible to implement and how?


Solution

  • Consider writing a custom DelegatingHandler.

    A DelegatingHandler is a decorator that can be used to add behavior before or after the inner Http Handler will be called by overriding the SendAsync method.

    In your case the handler could look like in the example below:

    public class LocalStorageAuthHandler : DelegatingHandler
    {  
        private readonly ILocalStorageService _localStorageService;
      
        public LocalStorageAuthHandler(ILocalStorageService localStorageService)
        {
             _localStorageService = localStorageService;
        }
    
        protected override async Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request, 
            CancellationToken cancellationToken)
        {
            // Access local storage service and update headers accordingly
            return await base.SendAsync(request, cancellationToken);
        }
    }
    

    Then add the handler to your HttpClient:

    // Transient, Scoped or Singleton depends on your code:
    services.AddTransient<LocalStorageAuthHandler>();
    services.AddHttpClient("clientName")
        .AddHttpMessageHandler<LocalStorageAuthHandler>();
    

    This will use the LocalStorageAuthHandler with the default http message handler as inner handler.

    You can even combine multiple DelegatingHandlers to build a Chain of Responsibility.

    See Make HTTP requests using IHttpClientFactory in ASP.NET Core on Microsoft Learn for details.