Call a web API from an ASP.NET Core Blazor app (original) (raw)

Important

This information relates to a pre-release product that may be substantially modified before it's commercially released. Microsoft makes no warranties, express or implied, with respect to the information provided here.

For the current release, see the .NET 9 version of this article.

This article describes how to call a web API from a Blazor app.

Package

The System.Net.Http.Json package provides extension methods for System.Net.Http.HttpClient and System.Net.Http.HttpContent that perform automatic serialization and deserialization using System.Text.Json. The System.Net.Http.Json package is provided by the .NET shared framework and doesn't require adding a package reference to the app.

Use a token handler for web API calls

Blazor Web Apps with OIDC authentication can use a token handler approach to make outgoing requests to secure external web API calls. This approach is used by the BlazorWebAppOidc and BlazorWebAppOidcServer sample apps described in the next section.

For more information, see the following resources:

Sample apps

For working examples, see the following sample apps in the Blazor samples GitHub repository (dotnet/blazor-samples) (how to download).

BlazorWebAppCallWebApi

Call an external (not in the Blazor Web App) todo list web API from a Blazor Web App:

For client-side rendering (CSR), which includes Interactive WebAssembly components and Auto components that have adopted CSR, calls are made with a preconfigured HttpClient registered in the Program file of the client project (BlazorApp.Client):

builder.Services.AddScoped(sp =>
    new HttpClient
    {
        BaseAddress = new Uri(builder.Configuration["FrontendUrl"] ?? 
            "https://localhost:5002")
    });

For server-side rendering (SSR), which includes prerendered and interactive Server components, prerendered WebAssembly components, and Auto components that are prerendered or have adopted SSR, calls are made with an HttpClient registered in the Program file of the server project (BlazorApp):

builder.Services.AddHttpClient();

Call an internal (inside the Blazor Web App) movie list API, where the API resides in the server project of the Blazor Web App:

For CSR, which includes Interactive WebAssembly components and Auto components that have adopted CSR, calls to the API are made via a client-based service (ClientMovieService) that uses a preconfigured HttpClient registered in the Program file of the client project (BlazorApp.Client). Because these calls are made over a public or private web, the movie list API is a web API.

The following example obtains a list of movies from the /movies endpoint:

public class ClientMovieService(HttpClient http) : IMovieService
{
    public async Task<Movie[]> GetMoviesAsync(bool watchedMovies) => 
        await http.GetFromJsonAsync<Movie[]>("movies") ?? [];
}

For SSR, which includes prerendered and interactive Server components, prerendered WebAssembly components, and Auto components that are prerendered or have adopted SSR, calls are made directly via a server-based service (ServerMovieService). The API doesn't rely on a network, so it's a standard API for movie list CRUD operations.

The following example obtains a list of movies:

public class ServerMovieService(MovieContext db) : IMovieService
{
    public async Task<Movie[]> GetMoviesAsync(bool watchedMovies) => 
        watchedMovies ? 
        await db.Movies.Where(t => t.IsWatched).ToArrayAsync() : 
        await db.Movies.ToArrayAsync();
}

For more information on how to secure movie data in this scenario, see the weather data example described by Secure data in Blazor Web Apps with Interactive Auto rendering.

BlazorWebAppCallWebApi_Weather

A weather data sample app that uses streaming rendering for weather data.

BlazorWebAssemblyCallWebApi

Calls a todo list web API from a Blazor WebAssembly app:

BlazorWebAssemblyStandaloneWithIdentity

A standalone Blazor WebAssembly app secured with ASP.NET Core Identity:

The solution demonstrates calling a secure web API for the following:

BlazorWebAppOidc

A Blazor Web App with global Auto interactivity that uses OIDC authentication with Microsoft Entra without using Entra-specific packages. The sample demonstrates how to use a token handler for web API calls to call an external secure web API.

BlazorWebAppOidcServer

A Blazor Web App with global Interactive Server interactivity that uses OIDC authentication with Microsoft Entra without using Entra-specific packages. The sample demonstrates how to pass an access token to call an external secure web API.

BlazorWebAppOidcBff

A Blazor Web App with global Auto interactivity that uses:

The solution includes a demonstration of obtaining weather data securely via an external web API when a component that adopts Interactive Auto rendering is rendered on the client.

Client-side scenarios for calling external web APIs

Client-based components call external web APIs using HttpClient instances, typically created with a preconfigured HttpClient registered in the Program file:

builder.Services.AddScoped(sp => 
    new HttpClient
    { 
        BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) 
    });

The following Razor component makes a request to a web API for GitHub branches similar to the Basic Usage example in the Make HTTP requests using IHttpClientFactory in ASP.NET Core article.

CallWebAPI.razor:

@page "/call-web-api"
@using System.Text.Json
@using System.Text.Json.Serialization
@inject HttpClient Client

<h1>Call web API from a Blazor WebAssembly Razor component</h1>

@if (getBranchesError || branches is null)
{
    <p>Unable to get branches from GitHub. Please try again later.</p>
}
else
{
    <ul>
        @foreach (var branch in branches)
        {
            <li>@branch.Name</li>
        }
    </ul>
}

@code {
    private IEnumerable<GitHubBranch>? branches = [];
    private bool getBranchesError;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    protected override async Task OnInitializedAsync()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var response = await Client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            branches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(responseStream);
        }
        else
        {
            getBranchesError = true;
        }

        shouldRender = true;
    }

    public class GitHubBranch
    {
        [JsonPropertyName("name")]
        public string? Name { get; set; }
    }
}

In the preceding example for C# 12 or later, an empty array ([]) is created for the branches variable. For earlier versions of C# compiled with an SDK earlier than .NET 8, create an empty array (Array.Empty<GitHubBranch>()).

To protect .NET/C# code and data, use ASP.NET Core Data Protection features with a server-side ASP.NET Core backend web API. The client-side Blazor WebAssembly app calls the server-side web API for secure app features and data processing.

Blazor WebAssembly apps are often prevented from making direct calls across origins to web APIs due to Cross-Origin Request Sharing (CORS) security. A typical exception looks like the following:

Access to fetch at '{URL}' from origin 'https://localhost:{PORT}' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Even if you call SetBrowserRequestMode with a BrowserRequestMode field of NoCors (1) seeking to circumvent the preceding exception, the request often fails due to CORS restrictions on the web API's origin, such as a restriction that only allows calls from specific origins or a restriction that prevents JavaScript fetch requests from a browser. The only way for such calls to succeed is for the web API that you're calling to allow your origin to call its origin with the correct CORS configuration. Most external web APIs don't allow you to configure their CORS policies. To deal with this restriction, adopt either of the following strategies:

@using System.Net  
@inject IHttpClientFactory ClientFactory  
...  
@code {  
    public async Task CallApi()  
    {  
        var client = ClientFactory.CreateClient();  
        var urlEncodedRequestUri = WebUtility.UrlEncode("{REQUEST URI}");  
        var request = new HttpRequestMessage(HttpMethod.Get,  
            $"https://corsproxy.io/?{urlEncodedRequestUri}");  
        var response = await client.SendAsync(request);  
        ...  
    }  
}  

Server-side scenarios for calling external web APIs

Server-based components call external web APIs using HttpClient instances, typically created using IHttpClientFactory. For guidance that applies to server-side apps, see Make HTTP requests using IHttpClientFactory in ASP.NET Core.

A server-side app doesn't include an HttpClient service. Provide an HttpClient to the app using the HttpClient factory infrastructure.

In the Program file:

builder.Services.AddHttpClient();

The following Razor component makes a request to a web API for GitHub branches similar to the Basic Usage example in the Make HTTP requests using IHttpClientFactory in ASP.NET Core article.

CallWebAPI.razor:

@page "/call-web-api"
@using System.Text.Json
@using System.Text.Json.Serialization
@inject IHttpClientFactory ClientFactory

<h1>Call web API from a server-side Razor component</h1>

@if (getBranchesError || branches is null)
{
    <p>Unable to get branches from GitHub. Please try again later.</p>
}
else
{
    <ul>
        @foreach (var branch in branches)
        {
            <li>@branch.Name</li>
        }
    </ul>
}

@code {
    private IEnumerable<GitHubBranch>? branches = [];
    private bool getBranchesError;
    private bool shouldRender;

    protected override bool ShouldRender() => shouldRender;

    protected override async Task OnInitializedAsync()
    {
        var request = new HttpRequestMessage(HttpMethod.Get,
            "https://api.github.com/repos/dotnet/AspNetCore.Docs/branches");
        request.Headers.Add("Accept", "application/vnd.github.v3+json");
        request.Headers.Add("User-Agent", "HttpClientFactory-Sample");

        var client = ClientFactory.CreateClient();

        var response = await client.SendAsync(request);

        if (response.IsSuccessStatusCode)
        {
            using var responseStream = await response.Content.ReadAsStreamAsync();
            branches = await JsonSerializer.DeserializeAsync
                <IEnumerable<GitHubBranch>>(responseStream);
        }
        else
        {
            getBranchesError = true;
        }

        shouldRender = true;
    }

    public class GitHubBranch
    {
        [JsonPropertyName("name")]
        public string? Name { get; set; }
    }
}

In the preceding example for C# 12 or later, an empty array ([]) is created for the branches variable. For earlier versions of C# compiled with an SDK earlier than .NET 8, create an empty array (Array.Empty<GitHubBranch>()).

For an additional working example, see the server-side file upload example that uploads files to a web API controller in the ASP.NET Core Blazor file uploads article.

Service abstractions for web API calls

This section applies to Blazor Web Apps that maintain a web API in the server project or transform web API calls to an external web API.

When using the interactive WebAssembly and Auto render modes, components are prerendered by default. Auto components are also initially rendered interactively from the server before the Blazor bundle downloads to the client and the client-side runtime activates. This means that components using these render modes should be designed so that they run successfully from both the client and the server. If the component must call a server project-based API or transform a request to an external web API (one that's outside of the Blazor Web App) when running on the client, the recommended approach is to abstract that API call behind a service interface and implement client and server versions of the service:

When using the WebAssembly render mode, you also have the option of disabling prerendering, so the components only render from the client. For more information, see ASP.NET Core Blazor render modes.

Examples (sample apps):

Blazor Web App external web APIs

This section applies to Blazor Web Apps that call a web API maintained by a separate (external) project, possibly hosted on a different server.

Blazor Web Apps normally prerender client-side WebAssembly components, and Auto components render on the server during static or interactive server-side rendering (SSR). HttpClient services aren't registered by default in a Blazor Web App's main project. If the app is run with only the HttpClient services registered in the .Client project, as described in the Add the HttpClient service section, executing the app results in a runtime error:

InvalidOperationException: Cannot provide a value for property 'Http' on type '...{COMPONENT}'. There is no registered service of type 'System.Net.Http.HttpClient'.

Use either of the following approaches:

builder.Services.AddHttpClient();  

HttpClient services are provided by the shared framework, so a package reference in the app's project file isn't required.
Example: Todo list web API in the BlazorWebAppCallWebApi sample app

For more information, see Client-side services fail to resolve during prerendering.

Prerendered data

When prerendering, components render twice: first statically, then interactively. State doesn't automatically flow from the prerendered component to the interactive one. If a component performs asynchronous initialization operations and renders different content for different states during initialization, such as a "Loading..." progress indicator, you may see a flicker when the component renders twice.

You can address this by flowing prerendered state using the Persistent Component State API, which the BlazorWebAppCallWebApi and BlazorWebAppCallWebApi_Weather sample apps demonstrate. When the component renders interactively, it can render the same way using the same state. However, the API doesn't currently work with enhanced navigation, which you can work around by disabling enhanced navigation on links to the page (data-enhanced-nav=false). For more information, see the following resources:

Client-side request streaming

For Chromium-based browsers (for example, Google Chrome and Microsoft Edge) using the HTTP/2 protocol, and HTTPS, client-side Blazor uses Streams API to permit request streaming.

To enable request streaming, set SetBrowserRequestStreamingEnabled to true on the HttpRequestMessage.

In the following file upload example:

var request = new HttpRequestMessage(HttpMethod.Post, "/Filesave");
request.SetBrowserRequestStreamingEnabled(true);
request.Content = content;

var response = await Http.SendAsync(request);

Streaming requests:

For more information on file uploads with an InputFile component, see ASP.NET Core Blazor file uploads and the example at Upload files to a server with client-side rendering (CSR).

Add the HttpClient service

The guidance in this section applies to client-side scenarios.

Client-side components call web APIs using a preconfigured HttpClient service, which is focused on making requests back to the server of origin. Additional HttpClient service configurations for other web APIs can be created in developer code. Requests are composed using Blazor JSON helpers or with HttpRequestMessage. Requests can include Fetch API option configuration.

The configuration examples in this section are only useful when a single web API is called for a single HttpClient instance in the app. When the app must call multiple web APIs, each with its own base address and configuration, you can adopt the following approaches, which are covered later in this article:

In the Program file, add an HttpClient service if it isn't already present from a Blazor project template used to create the app:

builder.Services.AddScoped(sp => 
    new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

The preceding example sets the base address with builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress), which gets the base address for the app and is typically derived from the <base> tag's href value in the host page.

The most common use cases for using the client's own base address are:

If you're calling an external web API (not in the same URL space as the client app), set the URI to the web API's base address. The following example sets the base address of the web API to https://localhost:5001, where a separate web API app is running and ready to respond to requests from the client app:

builder.Services.AddScoped(sp => 
    new HttpClient { BaseAddress = new Uri("https://localhost:5001") });

JSON helpers

HttpClient is available as a preconfigured service for making requests back to the origin server.

HttpClient and JSON helpers (System.Net.Http.Json.HttpClientJsonExtensions) are also used to call third-party web API endpoints. HttpClient is implemented using the browser's Fetch API and is subject to its limitations, including enforcement of the same-origin policy, which is discussed later in this article in the Cross-Origin Resource Sharing (CORS) section.

The client's base address is set to the originating server's address. Inject an HttpClient instance into a component using the @inject directive:

@using System.Net.Http
@inject HttpClient Http

Use the System.Net.Http.Json namespace for access to HttpClientJsonExtensions, including GetFromJsonAsync, PutAsJsonAsync, and PostAsJsonAsync:

@using System.Net.Http.Json

The following sections cover JSON helpers:

System.Net.Http includes additional methods for sending HTTP requests and receiving HTTP responses, for example to send a DELETE request. For more information, see the DELETE and additional extension methods section.

GET from JSON (GetFromJsonAsync)

GetFromJsonAsync sends an HTTP GET request and parses the JSON response body to create an object.

In the following component code, the todoItems are displayed by the component. GetFromJsonAsync is called when the component is finished initializing (OnInitializedAsync).

todoItems = await Http.GetFromJsonAsync<TodoItem[]>("todoitems");

POST as JSON (PostAsJsonAsync)

PostAsJsonAsync sends a POST request to the specified URI containing the value serialized as JSON in the request body.

In the following component code, newItemName is provided by a bound element of the component. The AddItem method is triggered by selecting a <button> element.

await Http.PostAsJsonAsync("todoitems", addItem);

PostAsJsonAsync returns an HttpResponseMessage. To deserialize the JSON content from the response message, use the ReadFromJsonAsync extension method. The following example reads JSON weather data as an array:

var content = await response.Content.ReadFromJsonAsync<WeatherForecast[]>() ?? 
    Array.Empty<WeatherForecast>();

PUT as JSON (PutAsJsonAsync)

PutAsJsonAsync sends an HTTP PUT request with JSON-encoded content.

In the following component code, editItem values for Name and IsCompleted are provided by bound elements of the component. The item's Id is set when the item is selected in another part of the UI (not shown) and EditItem is called. The SaveItem method is triggered by selecting the <button> element. The following example doesn't show loading todoItems for brevity. See the GET from JSON (GetFromJsonAsync) section for an example of loading items.

await Http.PutAsJsonAsync($"todoitems/{editItem.Id}", editItem);

PutAsJsonAsync returns an HttpResponseMessage. To deserialize the JSON content from the response message, use the ReadFromJsonAsync extension method. The following example reads JSON weather data as an array:

var content = await response.Content.ReadFromJsonAsync<WeatherForecast[]>() ?? 
    Array.Empty<WeatherForecast>();

PATCH as JSON (PatchAsJsonAsync)

PatchAsJsonAsync sends an HTTP PATCH request with JSON-encoded content.

In the following example, PatchAsJsonAsync receives a JSON PATCH document as a plain text string with escaped quotes:

await Http.PatchAsJsonAsync(
    $"todoitems/{id}", 
    "[{\"operationType\":2,\"path\":\"/IsComplete\",\"op\":\"replace\",\"value\":true}]");

As of C# 11 (.NET 7), you can compose a JSON string as a raw string literal. Specify JSON syntax with the StringSyntaxAttribute.Json field to the [StringSyntax] attribute for code analysis tooling:

@using System.Diagnostics.CodeAnalysis

...

@code {
    [StringSyntax(StringSyntaxAttribute.Json)]
    private const string patchOperation =
        """[{"operationType":2,"path":"/IsComplete","op":"replace","value":true}]""";

    ...

    await Http.PatchAsJsonAsync($"todoitems/{id}", patchOperation);
}

PatchAsJsonAsync returns an HttpResponseMessage. To deserialize the JSON content from the response message, use the ReadFromJsonAsync extension method. The following example reads JSON todo item data as an array. An empty array is created if no item data is returned by the method, so content isn't null after the statement executes:

var response = await Http.PatchAsJsonAsync(...);
var content = await response.Content.ReadFromJsonAsync<TodoItem[]>() ??
    Array.Empty<TodoItem>();

Laid out with indentation, spacing, and unescaped quotes, the unencoded PATCH document appears as the following JSON:

[
  {
    "operationType": 2,
    "path": "/IsComplete",
    "op": "replace",
    "value": true
  }
]

To simplify the creation of PATCH documents in the app issuing PATCH requests, an app can use .NET JSON PATCH support, as the following guidance demonstrates.

Install the Microsoft.AspNetCore.JsonPatch NuGet package and use the API features of the package to compose a JsonPatchDocument for a PATCH request.

Add @using directives for the System.Text.Json, System.Text.Json.Serialization, and Microsoft.AspNetCore.JsonPatch namespaces to the top of the Razor component:

@using System.Text.Json
@using System.Text.Json.Serialization
@using Microsoft.AspNetCore.JsonPatch

Compose the JsonPatchDocument for a TodoItem with IsComplete set to true using the Replace method:

var patchDocument = new JsonPatchDocument<TodoItem>()
    .Replace(p => p.IsComplete, true);

Pass the document's operations (patchDocument.Operations) to the PatchAsJsonAsync call:

private async Task UpdateItem(long id)
{
    await Http.PatchAsJsonAsync(
        $"todoitems/{id}", 
        patchDocument.Operations, 
        new JsonSerializerOptions()
        {
            DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingDefault
        });
}

JsonSerializerOptions.DefaultIgnoreCondition is set to JsonIgnoreCondition.WhenWritingDefault to ignore a property only if it equals the default value for its type.

Add JsonSerializerOptions.WriteIndented set to true if you want to present the JSON payload in a pleasant format for display. Writing indented JSON has no bearing on processing PATCH requests and isn't typically performed in production apps for web API requests.

Follow the guidance in the JsonPatch in ASP.NET Core web API article to add a PATCH controller action to the web API. Alternatively, PATCH request processing can be implemented as a Minimal API with the following steps.

Add a package reference for the Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet package to the web API app.

Note

There's no need to add a package reference for the Microsoft.AspNetCore.JsonPatch package to the app because the reference to the Microsoft.AspNetCore.Mvc.NewtonsoftJson package automatically transitively adds a package reference for Microsoft.AspNetCore.JsonPatch.

In the Program file add an @using directive for the Microsoft.AspNetCore.JsonPatch namespace:

using Microsoft.AspNetCore.JsonPatch;

Provide the endpoint to the request processing pipeline of the web API:

app.MapPatch("/todoitems/{id}", async (long id, TodoContext db) =>
{
    if (await db.TodoItems.FindAsync(id) is TodoItem todo)
    {
        var patchDocument = 
            new JsonPatchDocument<TodoItem>().Replace(p => p.IsComplete, true);
        patchDocument.ApplyTo(todo);
        await db.SaveChangesAsync();

        return TypedResults.Ok(todo);
    }

    return TypedResults.NoContent();
});

For a fully working PATCH experience, see the BlazorWebAppCallWebApi sample app.

DELETE (DeleteAsync) and additional extension methods

System.Net.Http includes additional extension methods for sending HTTP requests and receiving HTTP responses. HttpClient.DeleteAsync is used to send an HTTP DELETE request to a web API.

In the following component code, the <button> element calls the DeleteItem method. The bound <input> element supplies the id of the item to delete.

await Http.DeleteAsync($"todoitems/{id}");

Named HttpClient with IHttpClientFactory

IHttpClientFactory services and the configuration of a named HttpClient are supported.

Add the Microsoft.Extensions.Http NuGet package to the app.

In the Program file of a client project:

builder.Services.AddHttpClient("WebAPI", client => 
    client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));

If the named client is to be used by prerendered client-side components of a Blazor Web App, the preceding service registration should appear in both the server project and the .Client project. On the server, builder.HostEnvironment.BaseAddress is replaced by the web API's base address, which is described further below.

The preceding client-side example sets the base address with builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress), which gets the base address for the client-side app and is typically derived from the <base> tag's href value in the host page.

The most common use cases for using the client's own base address are:

The most common use case for using the client's own base address is in the client project (Client) of a hosted Blazor WebAssembly app that makes web API calls to the server project (Server).

If you're calling an external web API (not in the same URL space as the client app) or you're configuring the services in a server-side app (for example to deal with prerendering of client-side components on the server), set the URI to the web API's base address. The following example sets the base address of the web API to https://localhost:5001, where a separate web API app is running and ready to respond to requests from the client app:

builder.Services.AddHttpClient("WebAPI", client => 
    client.BaseAddress = new Uri("https://localhost:5001"));

In the following component code:

@inject IHttpClientFactory ClientFactory

...

@code {
    private Forecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        var client = ClientFactory.CreateClient("WebAPI");

        forecasts = await client.GetFromJsonAsync<Forecast[]>("forecast") ?? [];
    }
}

The BlazorWebAppCallWebApi sample app demonstrates calling a web API with a named HttpClient in its CallTodoWebApiCsrNamedClient component. For an additional working demonstration in a client app based on calling Microsoft Graph with a named HttpClient, see Use Graph API with ASP.NET Core Blazor WebAssembly.

Typed HttpClient

Typed HttpClient uses one or more of the app's HttpClient instances, default or named, to return data from one or more web API endpoints.

Add the Microsoft.Extensions.Http NuGet package to the app.

The following example issues a GET request for JSON weather forecast data from the web API at /forecast.

ForecastHttpClient.cs:

using System.Net.Http.Json;

namespace BlazorSample.Client;

public class ForecastHttpClient(HttpClient http)
{
    public async Task<Forecast[]> GetForecastAsync() => 
        await http.GetFromJsonAsync<Forecast[]>("forecast") ?? [];
}

In the Program file of a client project:

builder.Services.AddHttpClient<ForecastHttpClient>(client => 
    client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress));

If the typed client is to be used by prerendered client-side components of a Blazor Web App, the preceding service registration should appear in both the server project and the .Client project. On the server, builder.HostEnvironment.BaseAddress is replaced by the web API's base address, which is described further below.

The preceding example sets the base address with builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress), which gets the base address for the client-side app and is typically derived from the <base> tag's href value in the host page.

The most common use cases for using the client's own base address are:

The most common use case for using the client's own base address is in the client project (Client) of a hosted Blazor WebAssembly app that makes web API calls to the server project (Server).

If you're calling an external web API (not in the same URL space as the client app) or you're configuring the services in a server-side app (for example to deal with prerendering of client-side components on the server), set the URI to the web API's base address. The following example sets the base address of the web API to https://localhost:5001, where a separate web API app is running and ready to respond to requests from the client app:

builder.Services.AddHttpClient<ForecastHttpClient>(client => 
    client.BaseAddress = new Uri("https://localhost:5001"));

Components inject the typed HttpClient to call the web API.

In the following component code:

@inject ForecastHttpClient Http

...

@code {
    private Forecast[]? forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await Http.GetForecastAsync();
    }
}

The BlazorWebAppCallWebApi sample app demonstrates calling a web API with a typed HttpClient in its CallTodoWebApiCsrTypedClient component. Note that the component adopts and client-side rendering (CSR) (InteractiveWebAssembly render mode) with prerendering, so the typed client service registration appears in the Program file of both the server project and the .Client project.

The guidance in this section applies to client-side scenarios that rely upon an authentication cookie.

For cookie-based authentication, which is considered more secure than bearer token authentication, cookie credentials can be sent with each web API request by calling AddHttpMessageHandler with a DelegatingHandler on a preconfigured HttpClient. The handler configures SetBrowserRequestCredentials with BrowserRequestCredentials.Include, which advises the browser to send credentials with each request, such as cookies or HTTP authentication headers, including for cross-origin requests.

CookieHandler.cs:

public class CookieHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
        request.Headers.Add("X-Requested-With", [ "XMLHttpRequest" ]);

        return base.SendAsync(request, cancellationToken);
    }
}

The CookieHandler is registered in the Program file:

builder.Services.AddTransient<CookieHandler>();

The message handler is added to any preconfigured HttpClient that requires cookie authentication:

builder.Services.AddHttpClient(...)
    .AddHttpMessageHandler<CookieHandler>();

When composing an HttpRequestMessage, set the browser request credentials and header directly:

var requestMessage = new HttpRequestMessage() { ... };

requestMessage.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
requestMessage.Headers.Add("X-Requested-With", [ "XMLHttpRequest" ]);

HttpClient and HttpRequestMessage with Fetch API request options

The guidance in this section applies to client-side scenarios that rely upon bearer token authentication.

HttpClient (API documentation) and HttpRequestMessage can be used to customize requests. For example, you can specify the HTTP method and request headers. The following component makes a POST request to a web API endpoint and shows the response body.

TodoRequest.razor:

@page "/todo-request"
@using System.Net.Http.Headers
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@inject HttpClient Http
@inject IAccessTokenProvider TokenProvider

<h1>ToDo Request</h1>

<h1>ToDo Request Example</h1>

<button @onclick="PostRequest">Submit POST request</button>

<p>Response body returned by the server:</p>

<p>@responseBody</p>

@code {
    private string? responseBody;

    private async Task PostRequest()
    {
        var requestMessage = new HttpRequestMessage()
        {
            Method = new HttpMethod("POST"),
            RequestUri = new Uri("https://localhost:10000/todoitems"),
            Content =
                JsonContent.Create(new TodoItem
                {
                    Name = "My New Todo Item",
                    IsComplete = false
                })
        };

        var tokenResult = await TokenProvider.RequestAccessToken();

        if (tokenResult.TryGetToken(out var token))
        {
            requestMessage.Headers.Authorization =
                new AuthenticationHeaderValue("Bearer", token.Value);

            requestMessage.Content.Headers.TryAddWithoutValidation(
                "x-custom-header", "value");

            var response = await Http.SendAsync(requestMessage);
            var responseStatusCode = response.StatusCode;

            responseBody = await response.Content.ReadAsStringAsync();
        }
    }

    public class TodoItem
    {
        public long Id { get; set; }
        public string? Name { get; set; }
        public bool IsComplete { get; set; }
    }
}

Blazor's client-side implementation of HttpClient uses Fetch API and configures the underlying request-specific Fetch API options via HttpRequestMessage extension methods and WebAssemblyHttpRequestMessageExtensions. Set additional options using the generic SetBrowserRequestOption extension method. Blazor and the underlying Fetch API don't directly add or modify request headers. For more information on how user agents, such as browsers, interact with headers, consult external user agent documentation sets and other web resources.

To include credentials in a cross-origin request, use the SetBrowserRequestCredentials extension method:

requestMessage.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);

For more information on Fetch API options, see MDN web docs: WindowOrWorkerGlobalScope.fetch(): Parameters.

Handle errors

Handle web API response errors in developer code when they occur. For example, GetFromJsonAsync expects a JSON response from the web API with a Content-Type of application/json. If the response isn't in JSON format, content validation throws a NotSupportedException.

In the following example, the URI endpoint for the weather forecast data request is misspelled. The URI should be to WeatherForecast but appears in the call as WeatherForcast, which is missing the letter e in Forecast.

The GetFromJsonAsync call expects JSON to be returned, but the web API returns HTML for an unhandled exception with a Content-Type of text/html. The unhandled exception occurs because the path to /WeatherForcast isn't found and middleware can't serve a page or view for the request.

In OnInitializedAsync on the client, NotSupportedException is thrown when the response content is validated as non-JSON. The exception is caught in the catch block, where custom logic could log the error or present a friendly error message to the user.

ReturnHTMLOnException.razor:

@page "/return-html-on-exception"
@using {PROJECT NAME}.Shared
@inject HttpClient Http

<h1>Fetch data but receive HTML on unhandled exception</h1>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <h2>Temperatures by Date</h2>

    <ul>
        @foreach (var forecast in forecasts)
        {
            <li>
                @forecast.Date.ToShortDateString():
                @forecast.TemperatureC &#8451;
                @forecast.TemperatureF &#8457;
            </li>
        }
    </ul>
}

<p>
    @exceptionMessage
</p>

@code {
    private WeatherForecast[]? forecasts;
    private string? exceptionMessage;

    protected override async Task OnInitializedAsync()
    {
        try
        {
            // The URI endpoint "WeatherForecast" is misspelled on purpose on the 
            // next line. See the preceding text for more information.
            forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForcast");
        }
        catch (NotSupportedException exception)
        {
            exceptionMessage = exception.Message;
        }
    }
}

Note

The preceding example is for demonstration purposes. A web API can be configured to return JSON even when an endpoint doesn't exist or an unhandled exception occurs on the server.

For more information, see Handle errors in ASP.NET Core Blazor apps.

Cross-Origin Resource Sharing (CORS)

Browser security often restricts a webpage from making requests to a different origin than the one that served the webpage. This restriction is called the same-origin policy. The same-origin policy restricts (but doesn't prevent) a malicious site from reading sensitive data from another site. To make requests from the browser to an endpoint with a different origin, the endpoint must enable Cross-Origin Resource Sharing (CORS).

For more information on server-side CORS, see Enable Cross-Origin Requests (CORS) in ASP.NET Core. The article's examples don't pertain directly to Razor component scenarios, but the article is useful for learning general CORS concepts.

For information on client-side CORS requests, see ASP.NET Core Blazor WebAssembly additional security scenarios.

Antiforgery support

To add antiforgery support to an HTTP request, inject the AntiforgeryStateProvider and add a RequestToken to the headers collection as a RequestVerificationToken:

@inject AntiforgeryStateProvider Antiforgery
private async Task OnSubmit()
{
    var antiforgery = Antiforgery.GetAntiforgeryToken();
    var request = new HttpRequestMessage(HttpMethod.Post, "action");
    request.Headers.Add("RequestVerificationToken", antiforgery.RequestToken);
    var response = await client.SendAsync(request);
    ...
}

For more information, see ASP.NET Core Blazor authentication and authorization.

Blazor framework component examples for testing web API access

Various network tools are publicly available for testing web API backend apps directly, such as Firefox Browser Developer. Blazor framework's reference source includes HttpClient test assets that are useful for testing:

HttpClientTest assets in the dotnet/aspnetcore GitHub repository

Additional resources

General

Mitigation of overposting attacks

Web APIs can be vulnerable to an overposting attack, also known as a mass assignment attack. An overposting attack occurs when a malicious user issues an HTML form POST to the server that processes data for properties that aren't part of the rendered form and that the developer doesn't wish to allow users to modify. The term "overposting" literally means that the malicious user has _over_-POSTed with the form.

For guidance on mitigating overposting attacks, see Tutorial: Create a controller-based web API with ASP.NET Core.

Server-side

Client-side