blazorasp.net-core-webapiblazor-webassemblydotnet-httpclient

Problem with HttpClient injection in Blazor WebAssembly application


I have an ASP.NET Core Web API project as the backend and a Blazor web assembly as the front-end.

The Web API has ProductController which returns JSON data (products, groups, etc). Blazor app consumes that API.

I created ProductService on the Blazor side. The ProductService gets HttpClient in constructor injection and sends requests to the API. And here is the problem — I get a 404 NotFound error. When I use injected HttpClient or create it directly on the page it works (please see the code shown below).

If I input API URL in a browser I get the data (product catalog). I initialize HttpClient as Microsoft says in the article. Please help to fix this.

This is the Blazor init code:

var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");

builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
var appSettings = builder.Configuration.Get<AppSettings>();
builder.Services.AddSingleton<AppSettings>(appSettings);

builder.Services.AddBlazoredLocalStorage();

builder.Services.AddHttpClient("Shop.WebApi", client => client.BaseAddress =
    new Uri(appSettings.ApiBaseUri))
         .AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Shop.WebApi"));
builder.Services.AddApiAuthorization();

builder.Services.AddScoped<CustomAuthenticationStateProvider>();
builder.Services.AddScoped<AuthenticationStateProvider>(
    provider => provider.GetRequiredService<CustomAuthenticationStateProvider>());
builder.Services.AddScoped<AuthService>();
builder.Services.AddHttpClient<IProductService, HttpProductService>(client =>
{
    client.BaseAddress = new Uri(appSettings.ApiBaseUri);
}).AddHttpMessageHandler<CustomAuthorizationMessageHandler>();

await builder.Build().RunAsync();

This is product catalog page:

@inject IProductService ProductService
@inject HttpClient TheHttpClient
...
protected override async Task OnInitializedAsync()
{
    try
    {
    // this works   
        _ProductCatalog = await TheHttpClient.GetFromJsonAsync<ProductCatalog>("api/Product/GetProductCatalog");
    // this doesn't work
        //await LoadProductCatalogAsync();
    }
    catch (Exception ex)
    {
        Console.WriteLine("An error occurred while initializing the page: " + ex.Message);
    }
}

private async Task LoadProductCatalogAsync()
{
    try
    {
        _ProductCatalog = await ProductService.GetProductCatalog();
    }
    catch (Exception ex)
    {
        Console.WriteLine("An error occurred while loading the product catalog: " + ex.Message);
    }
}

Product service:

public class HttpProductService : IProductService
{
    public HttpProductService(HttpClient httpClient)
    {
        TheHttpClient = httpClient;
    }

    public async Task<ProductCatalog> GetProductCatalog()
    {        
        var response = await TheHttpClient.GetAsync($"{GetApiBaseUri()}");

        if (response.IsSuccessStatusCode)
        {
            return await response.Content.ReadFromJsonAsync<ProductCatalog>();
        }

        return new ProductCatalog();
    }

    protected virtual string GetApiBaseUri() => $"api/Product";
    protected HttpClient TheHttpClient { get; }
}

Solution

  • The problem in your code is the URL in the GetProductCatalog was "api/Product" instead of "api/Product/GetProductCatalog".

    Add the "/GetProductCatalog" path after GetApiBaseUri().

    public class HttpProductService : IProductService
    {
        ...
    
        public async Task<ProductCatalog> GetProductCatalog()
        {        
            var response = await TheHttpClient.GetAsync($"{GetApiBaseUri()}/GetProductCatalog");
    
            if (response.IsSuccessStatusCode)
            {
                return await response.Content.ReadFromJsonAsync<ProductCatalog>();
            }
    
            return new ProductCatalog();
        }
    
        ...
    }