Call a web API from ASP.NET Core Blazor

Blazor WebAssembly apps 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.

Examples in this article

In this article's component examples, a hypothetical todo list web API is used to create, read, update, and delete (CRUD) todo items on a server. The examples are based on a TodoItem class that stores the following todo item data:

  • ID (Id, long): Unique ID of the item.
  • Name (Name, string): Name of the item.
  • Status (IsComplete, bool): Indication if the todo item is finished.

Use the following TodoItem class with this article's examples if you build the examples into a test app:

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

For guidance on how to create a server-side web API, see Tutorial: Create a web API with ASP.NET Core. For information on Cross-origin resource sharing (CORS), see the CORS guidance later in this article.

Packages

Reference the System.Net.Http.Json NuGet package in the project file.

Add the HttpClient service

In Program.Main, 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)
    });

HttpClient and 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 (discussed later in this article).

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

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).

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

@if (todoItems == null)
{
    <p>No Todo Items found.</p>
}
else
{
    <ul>
        @foreach (var item in todoItems)
        {
            <li>@item.Name</li>
        }
    </ul>
}

@code {
    private TodoItem[] todoItems;

    protected override async Task OnInitializedAsync() => 
        todoItems = await Http.GetFromJsonAsync<TodoItem[]>("api/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.

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

<input @bind="newItemName" placeholder="New Todo Item" />
<button @onclick="AddItem">Add</button>

@code {
    private string newItemName;

    private async Task AddItem()
    {
        var addItem = new TodoItem { Name = newItemName, IsComplete = false };
        await Http.PostAsJsonAsync("api/TodoItems", addItem);
    }
}

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

var content = await response.Content.ReadFromJsonAsync<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.

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

<input type="checkbox" @bind="editItem.IsComplete" />
<input @bind="editItem.Name" />
<button @onclick="SaveItem">Save</button>

@code {
    private string id;
    private TodoItem editItem = new TodoItem();

    private void EditItem(long id)
    {
        editItem = todoItems.Single(i => i.Id == id);
    }

    private async Task SaveItem() =>
        await Http.PutAsJsonAsync($"api/TodoItems/{editItem.Id}", editItem);
}

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

var content = await response.Content.ReadFromJsonAsync<WeatherForecast>();

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.

@using System.Net.Http
@using System.Threading.Tasks
@inject HttpClient Http

<input @bind="id" />
<button @onclick="DeleteItem">Delete</button>

@code {
    private long id;

    private async Task DeleteItem() =>
        await Http.DeleteAsync($"api/TodoItems/{id}");
}

Named HttpClient with IHttpClientFactory

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

Reference the Microsoft.Extensions.Http NuGet package in the project file:

<PackageReference Include="Microsoft.Extensions.Http" Version="{VERSION}" />

In the preceding example, the {VERSION} placeholder is the version of the package.

In Program.Main of the Program.cs file:

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

In the following component code:

Pages/FetchDataViaFactory.razor:

@page "/fetch-data-via-factory"
@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject IHttpClientFactory ClientFactory

<h1>Fetch data via <code>IHttpClientFactory</code></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>
}

@code {
    private WeatherForecast[] forecasts;

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

        forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
            "WeatherForecast");
    }
}

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.

WeatherForecastClient.cs:

using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

public class WeatherForecastHttpClient
{
    private readonly HttpClient http;

    public WeatherForecastHttpClient(HttpClient http)
    {
        this.http = http;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        try
        {
            return await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch
        {
            ...

            return new WeatherForecast[0];
        }
    }
}

In Program.Main of the Program.cs file:

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

Components inject the typed HttpClient to call the web API.

In the following component code:

  • An instance of the preceding WeatherForecastHttpClient is injected, which creates a typed HttpClient.
  • The typed HttpClient is used to issue a GET request for JSON weather forecast data from the web API.

Pages/FetchDataViaTypedHttpClient.razor:

@page "/fetch-data-via-typed-httpclient"
@using System.Threading.Tasks
@inject WeatherForecastHttpClient Http

<h1>Fetch data via typed <code>HttpClient</code></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>
}

@code {
    private WeatherForecast[] forecasts;

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

HttpClient and HttpRequestMessage with Fetch API request options

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.

Pages/TodoRequest.razor:

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

<h1>ToDo Request</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/api/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 WebAssembly's implementation of HttpClient uses Fetch API. Fetch API allows the configuration of several request-specific options. Options can be configured with HttpRequestMessage extension methods shown in the following table.

Extension method Fetch API request property
SetBrowserRequestCache cache
SetBrowserRequestCredentials credentials
SetBrowserRequestIntegrity integrity
SetBrowserRequestMode mode

Set additional options using the generic SetBrowserRequestOption extension method.

The HTTP response is typically buffered to enable support for synchronous reads on the response content. To enable support for response streaming, use the SetBrowserResponseStreamingEnabled extension method on the request.

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.

Call web API example

The following example calls a web API. The example requires a running web API based on the sample app described by the Tutorial: Create a web API with ASP.NET Core article. This example makes requests to the web API at https://localhost:10000/api/TodoItems. If a different web API address is used, update the ServiceEndpoint constant value in the component's @code block.

The following example makes a cross-origin resource sharing (CORS) request from http://localhost:5000 or https://localhost:5001 to the web API. Add the following CORS middleware configuration to the web API's service's Startup.Configure method:

app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
    .AllowAnyMethod()
    .WithHeaders(HeaderNames.ContentType));

Adjust the domains and ports of WithOrigins as needed for the Blazor app. For more information, see Enable Cross-Origin Requests (CORS) in ASP.NET Core.

By default, ASP.NET Core apps use ports 5000 (HTTP) and 5001 (HTTPS). To run both apps on the same machine at the same time for testing, use a different port for the web API app (for example, port 10000). For more information on setting the port, see Configure endpoints for the ASP.NET Core Kestrel web server.

Pages/CallWebAPI.razor:

@page "/call-web-api"
@inject HttpClient Http

<h1>Todo Items</h1>

@if (todoItems == null)
{
    <p>No Todo Items found.</p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th class="text-center">Complete</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr id="editRow" style="display:@editRowStyle">
                <td class="text-center">
                    <input type="checkbox" @bind="editItem.IsComplete" />
                </td>
                <td>
                    <input @bind="editItem.Name" />
                </td>
                <td class="text-center">
                    <button class="btn btn-success" @onclick="SaveItem">
                        Save
                    </button>
                    <button class="btn btn-danger" 
                            @onclick="@(() => editRowStyle = "none")">
                        Cancel
                    </button>
                </td>
            </tr>
            @foreach (var item in todoItems)
            {
                <tr>
                    <td class="text-center">
                        @if (item.IsComplete)
                        {
                            <span>&#10004;</span>
                        }
                    </td>
                    <td>@item.Name</td>
                    <td class="text-center">
                        <button class="btn btn-warning" 
                                @onclick="@(() => EditItem(item.Id))">
                            Edit
                        </button>
                        <button class="btn btn-danger" 
                                @onclick="@(async () => await DeleteItem(item.Id))">
                            Delete
                        </button>
                    </td>
                </tr>
            }
            <tr id="addRow">
                <td></td>
                <td>
                    <input @bind="newItemName" placeholder="New Todo Item" />
                </td>
                <td class="text-center">
                    <button class="btn btn-success" @onclick="AddItem">Add</button>
                </td>
            </tr>
        </tbody>
    </table>
}

@code {
    private const string ServiceEndpoint = "https://localhost:10000/api/TodoItems";
    private TodoItem[] todoItems;
    private TodoItem editItem = new();
    private string editRowStyle = "none";
    private string newItemName;

    protected override async Task OnInitializedAsync() => await GetTodoItems();

    private async Task GetTodoItems() => 
        todoItems = await Http.GetFromJsonAsync<TodoItem[]>(ServiceEndpoint);

    private void EditItem(long id)
    {
        editItem = todoItems.Single(i => i.Id == id);
        editRowStyle = "table-row";
    }

    private async Task AddItem()
    {
        var addItem = new TodoItem { Name = newItemName, IsComplete = false };
        await Http.PostAsJsonAsync(ServiceEndpoint, addItem);
        newItemName = string.Empty;
        await GetTodoItems();
        editRowStyle = "none";
    }

    private async Task SaveItem()
    {
        await Http.PutAsJsonAsync($"{ServiceEndpoint}/{editItem.Id}", editItem);
        await GetTodoItems();
        editRowStyle = "none";
    }

    private async Task DeleteItem(long id)
    {
        await Http.DeleteAsync($"{ServiceEndpoint}/{id}");
        await GetTodoItems();
        editRowStyle = "none";
    }

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

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.

Pages/FetchDataReturnsHTMLOnException.razor:

@page "/fetch-data-returns-html-on-exception"
@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@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.

Blazor Server apps call web APIs using HttpClient instances, typically created using IHttpClientFactory. For guidance that applies to Blazor Server, see Make HTTP requests using IHttpClientFactory in ASP.NET Core.

A Blazor Server app doesn't include an HttpClient service by default. Provide an HttpClient to the app using the HttpClient factory infrastructure.

In Startup.ConfigureServicesofStartup.cs`:

services.AddHttpClient();

The following Blazor Server 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.

Pages/CallWebAPI.razor:

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

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

@if (getBranchesError)
{
    <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 = Array.Empty<GitHubBranch>();
    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; }
    }
}

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

Cross-origin resource sharing (CORS)

Browser security restricts a webpage from making requests to a different domain 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 information on CORS requests in Blazor WebAssembly apps, see ASP.NET Core Blazor WebAssembly additional security scenarios.

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

Blazor framework component examples for testing web API access

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

HttpClientTest assets in the dotnet/aspnetcore GitHub repository

Additional resources

Blazor WebAssembly apps 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.

Examples in this article

In this article's component examples, a hypothetical todo list web API is used to create, read, update, and delete (CRUD) todo items on a server. The examples are based on a TodoItem class that stores the following todo item data:

  • ID (Id, long): Unique ID of the item.
  • Name (Name, string): Name of the item.
  • Status (IsComplete, bool): Indication if the todo item is finished.

Use the following TodoItem class with this article's examples if you build the examples into a test app:

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

For guidance on how to create a server-side web API, see Tutorial: Create a web API with ASP.NET Core. For information on Cross-origin resource sharing (CORS), see the CORS guidance later in this article.

Packages

Reference the System.Net.Http.Json NuGet package in the project file.

Add the HttpClient service

In Program.Main, 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)
    });

HttpClient and 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 (discussed later in this article).

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

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).

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

@if (todoItems == null)
{
    <p>No Todo Items found.</p>
}
else
{
    <ul>
        @foreach (var item in todoItems)
        {
            <li>@item.Name</li>
        }
    </ul>
}

@code {
    private TodoItem[] todoItems;

    protected override async Task OnInitializedAsync() => 
        todoItems = await Http.GetFromJsonAsync<TodoItem[]>("api/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.

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

<input @bind="newItemName" placeholder="New Todo Item" />
<button @onclick="AddItem">Add</button>

@code {
    private string newItemName;

    private async Task AddItem()
    {
        var addItem = new TodoItem { Name = newItemName, IsComplete = false };
        await Http.PostAsJsonAsync("api/TodoItems", addItem);
    }
}

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

var content = await response.Content.ReadFromJsonAsync<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.

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

<input type="checkbox" @bind="editItem.IsComplete" />
<input @bind="editItem.Name" />
<button @onclick="SaveItem">Save</button>

@code {
    private string id;
    private TodoItem editItem = new TodoItem();

    private void EditItem(long id)
    {
        editItem = todoItems.Single(i => i.Id == id);
    }

    private async Task SaveItem() =>
        await Http.PutAsJsonAsync($"api/TodoItems/{editItem.Id}", editItem);
}

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

var content = await response.Content.ReadFromJsonAsync<WeatherForecast>();

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.

@using System.Net.Http
@using System.Threading.Tasks
@inject HttpClient Http

<input @bind="id" />
<button @onclick="DeleteItem">Delete</button>

@code {
    private long id;

    private async Task DeleteItem() =>
        await Http.DeleteAsync($"api/TodoItems/{id}");
}

Named HttpClient with IHttpClientFactory

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

Reference the Microsoft.Extensions.Http NuGet package in the project file:

<PackageReference Include="Microsoft.Extensions.Http" Version="{VERSION}" />

In the preceding example, the {VERSION} placeholder is the version of the package.

In Program.Main of the Program.cs file:

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

In the following component code:

Pages/FetchDataViaFactory.razor:

@page "/fetch-data-via-factory"
@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject IHttpClientFactory ClientFactory

<h1>Fetch data via <code>IHttpClientFactory</code></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>
}

@code {
    private WeatherForecast[] forecasts;

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

        forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
            "WeatherForecast");
    }
}

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.

WeatherForecastClient.cs:

using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

public class WeatherForecastHttpClient
{
    private readonly HttpClient http;

    public WeatherForecastHttpClient(HttpClient http)
    {
        this.http = http;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        try
        {
            return await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch
        {
            ...

            return new WeatherForecast[0];
        }
    }
}

In Program.Main of the Program.cs file:

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

Components inject the typed HttpClient to call the web API.

In the following component code:

  • An instance of the preceding WeatherForecastHttpClient is injected, which creates a typed HttpClient.
  • The typed HttpClient is used to issue a GET request for JSON weather forecast data from the web API.

Pages/FetchDataViaTypedHttpClient.razor:

@page "/fetch-data-via-typed-httpclient"
@using System.Threading.Tasks
@inject WeatherForecastHttpClient Http

<h1>Fetch data via typed <code>HttpClient</code></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>
}

@code {
    private WeatherForecast[] forecasts;

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

HttpClient and HttpRequestMessage with Fetch API request options

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.

Pages/TodoRequest.razor:

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

<h1>ToDo Request</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/api/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 WebAssembly's implementation of HttpClient uses Fetch API. Fetch API allows the configuration of several request-specific options. Options can be configured with HttpRequestMessage extension methods shown in the following table.

Extension method Fetch API request property
SetBrowserRequestCache cache
SetBrowserRequestCredentials credentials
SetBrowserRequestIntegrity integrity
SetBrowserRequestMode mode

Set additional options using the generic SetBrowserRequestOption extension method.

The HTTP response is typically buffered to enable support for synchronous reads on the response content. To enable support for response streaming, use the SetBrowserResponseStreamingEnabled extension method on the request.

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.

Call web API example

The following example calls a web API. The example requires a running web API based on the sample app described by the Tutorial: Create a web API with ASP.NET Core article. This example makes requests to the web API at https://localhost:10000/api/TodoItems. If a different web API address is used, update the ServiceEndpoint constant value in the component's @code block.

The following example makes a cross-origin resource sharing (CORS) request from http://localhost:5000 or https://localhost:5001 to the web API. Add the following CORS middleware configuration to the web API's service's Startup.Configure method:

app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
    .AllowAnyMethod()
    .WithHeaders(HeaderNames.ContentType));

Adjust the domains and ports of WithOrigins as needed for the Blazor app. For more information, see Enable Cross-Origin Requests (CORS) in ASP.NET Core.

By default, ASP.NET Core apps use ports 5000 (HTTP) and 5001 (HTTPS). To run both apps on the same machine at the same time for testing, use a different port for the web API app (for example, port 10000). For more information on setting the port, see Configure endpoints for the ASP.NET Core Kestrel web server.

Pages/CallWebAPI.razor:

@page "/call-web-api"
@inject HttpClient Http

<h1>Todo Items</h1>

@if (todoItems == null)
{
    <p>No Todo Items found.</p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th class="text-center">Complete</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr id="editRow" style="display:@editRowStyle">
                <td class="text-center">
                    <input type="checkbox" @bind="editItem.IsComplete" />
                </td>
                <td>
                    <input @bind="editItem.Name" />
                </td>
                <td class="text-center">
                    <button class="btn btn-success" @onclick="SaveItem">
                        Save
                    </button>
                    <button class="btn btn-danger" 
                            @onclick="@(() => editRowStyle = "none")">
                        Cancel
                    </button>
                </td>
            </tr>
            @foreach (var item in todoItems)
            {
                <tr>
                    <td class="text-center">
                        @if (item.IsComplete)
                        {
                            <span>&#10004;</span>
                        }
                    </td>
                    <td>@item.Name</td>
                    <td class="text-center">
                        <button class="btn btn-warning" 
                                @onclick="@(() => EditItem(item.Id))">
                            Edit
                        </button>
                        <button class="btn btn-danger" 
                                @onclick="@(async () => await DeleteItem(item.Id))">
                            Delete
                        </button>
                    </td>
                </tr>
            }
            <tr id="addRow">
                <td></td>
                <td>
                    <input @bind="newItemName" placeholder="New Todo Item" />
                </td>
                <td class="text-center">
                    <button class="btn btn-success" @onclick="AddItem">Add</button>
                </td>
            </tr>
        </tbody>
    </table>
}

@code {
    private const string ServiceEndpoint = "https://localhost:10000/api/TodoItems";
    private TodoItem[] todoItems;
    private TodoItem editItem = new();
    private string editRowStyle = "none";
    private string newItemName;

    protected override async Task OnInitializedAsync() => await GetTodoItems();

    private async Task GetTodoItems() => 
        todoItems = await Http.GetFromJsonAsync<TodoItem[]>(ServiceEndpoint);

    private void EditItem(long id)
    {
        editItem = todoItems.Single(i => i.Id == id);
        editRowStyle = "table-row";
    }

    private async Task AddItem()
    {
        var addItem = new TodoItem { Name = newItemName, IsComplete = false };
        await Http.PostAsJsonAsync(ServiceEndpoint, addItem);
        newItemName = string.Empty;
        await GetTodoItems();
        editRowStyle = "none";
    }

    private async Task SaveItem()
    {
        await Http.PutAsJsonAsync($"{ServiceEndpoint}/{editItem.Id}", editItem);
        await GetTodoItems();
        editRowStyle = "none";
    }

    private async Task DeleteItem(long id)
    {
        await Http.DeleteAsync($"{ServiceEndpoint}/{id}");
        await GetTodoItems();
        editRowStyle = "none";
    }

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

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.

Pages/FetchDataReturnsHTMLOnException.razor:

@page "/fetch-data-returns-html-on-exception"
@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@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.

Blazor Server apps call web APIs using HttpClient instances, typically created using IHttpClientFactory. For guidance that applies to Blazor Server, see Make HTTP requests using IHttpClientFactory in ASP.NET Core.

A Blazor Server app doesn't include an HttpClient service by default. Provide an HttpClient to the app using the HttpClient factory infrastructure.

In Startup.ConfigureServicesofStartup.cs`:

services.AddHttpClient();

The following Blazor Server 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.

Pages/CallWebAPI.razor:

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

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

@if (getBranchesError)
{
    <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 = Array.Empty<GitHubBranch>();
    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; }
    }
}

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

Cross-origin resource sharing (CORS)

Browser security restricts a webpage from making requests to a different domain 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 information on CORS requests in Blazor WebAssembly apps, see ASP.NET Core Blazor WebAssembly additional security scenarios.

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

Blazor framework component examples for testing web API access

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

HttpClientTest assets in the dotnet/aspnetcore GitHub repository

Note

Documentation links to the ASP.NET Core reference source load the repository's main branch, which represents the product unit's current development for the next release of ASP.NET Core. To select the branch for a different release, use the Switch branches or tags dropdown list to select the branch. For example, select the release/5.0 branch for the ASP.NET Core 5.0 release.

Additional resources

Blazor WebAssembly apps 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.

Examples in this article

In this article's component examples, a hypothetical todo list web API is used to create, read, update, and delete (CRUD) todo items on a server. The examples are based on a TodoItem class that stores the following todo item data:

  • ID (Id, long): Unique ID of the item.
  • Name (Name, string): Name of the item.
  • Status (IsComplete, bool): Indication if the todo item is finished.

Use the following TodoItem class with this article's examples if you build the examples into a test app:

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

For guidance on how to create a server-side web API, see Tutorial: Create a web API with ASP.NET Core. For information on Cross-origin resource sharing (CORS), see the CORS guidance later in this article.

Packages

Reference the System.Net.Http.Json NuGet package in the project file.

Add the HttpClient service

In Program.Main, 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)
    });

HttpClient and 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 (discussed later in this article).

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

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).

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

@if (todoItems == null)
{
    <p>No Todo Items found.</p>
}
else
{
    <ul>
        @foreach (var item in todoItems)
        {
            <li>@item.Name</li>
        }
    </ul>
}

@code {
    private TodoItem[] todoItems;

    protected override async Task OnInitializedAsync() => 
        todoItems = await Http.GetFromJsonAsync<TodoItem[]>("api/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.

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

<input @bind="newItemName" placeholder="New Todo Item" />
<button @onclick="AddItem">Add</button>

@code {
    private string newItemName;

    private async Task AddItem()
    {
        var addItem = new TodoItem { Name = newItemName, IsComplete = false };
        await Http.PostAsJsonAsync("api/TodoItems", addItem);
    }
}

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

var content = await response.Content.ReadFromJsonAsync<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.

@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject HttpClient Http

<input type="checkbox" @bind="editItem.IsComplete" />
<input @bind="editItem.Name" />
<button @onclick="SaveItem">Save</button>

@code {
    private string id;
    private TodoItem editItem = new TodoItem();

    private void EditItem(long id)
    {
        editItem = todoItems.Single(i => i.Id == id);
    }

    private async Task SaveItem() =>
        await Http.PutAsJsonAsync($"api/TodoItems/{editItem.Id}", editItem);
}

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

var content = await response.Content.ReadFromJsonAsync<WeatherForecast>();

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.

@using System.Net.Http
@using System.Threading.Tasks
@inject HttpClient Http

<input @bind="id" />
<button @onclick="DeleteItem">Delete</button>

@code {
    private long id;

    private async Task DeleteItem() =>
        await Http.DeleteAsync($"api/TodoItems/{id}");
}

Named HttpClient with IHttpClientFactory

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

Reference the Microsoft.Extensions.Http NuGet package in the project file:

<PackageReference Include="Microsoft.Extensions.Http" Version="{VERSION}" />

In the preceding example, the {VERSION} placeholder is the version of the package.

In Program.Main of the Program.cs file:

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

In the following component code:

Pages/FetchDataViaFactory.razor:

@page "/fetch-data-via-factory"
@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@inject IHttpClientFactory ClientFactory

<h1>Fetch data via <code>IHttpClientFactory</code></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>
}

@code {
    private WeatherForecast[] forecasts;

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

        forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
            "WeatherForecast");
    }
}

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.

WeatherForecastClient.cs:

using System.Net.Http;
using System.Net.Http.Json;
using System.Threading.Tasks;

public class WeatherForecastHttpClient
{
    private readonly HttpClient http;

    public WeatherForecastHttpClient(HttpClient http)
    {
        this.http = http;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        try
        {
            return await http.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch
        {
            ...

            return new WeatherForecast[0];
        }
    }
}

In Program.Main of the Program.cs file:

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

Components inject the typed HttpClient to call the web API.

In the following component code:

  • An instance of the preceding WeatherForecastHttpClient is injected, which creates a typed HttpClient.
  • The typed HttpClient is used to issue a GET request for JSON weather forecast data from the web API.

Pages/FetchDataViaTypedHttpClient.razor:

@page "/fetch-data-via-typed-httpclient"
@using System.Threading.Tasks
@inject WeatherForecastHttpClient Http

<h1>Fetch data via typed <code>HttpClient</code></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>
}

@code {
    private WeatherForecast[] forecasts;

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

HttpClient and HttpRequestMessage with Fetch API request options

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.

Pages/TodoRequest.razor:

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

<h1>ToDo Request</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/api/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 WebAssembly's implementation of HttpClient uses Fetch API. Fetch API allows the configuration of several request-specific options. Options can be configured with HttpRequestMessage extension methods shown in the following table.

Extension method Fetch API request property
SetBrowserRequestCache cache
SetBrowserRequestCredentials credentials
SetBrowserRequestIntegrity integrity
SetBrowserRequestMode mode

Set additional options using the generic SetBrowserRequestOption extension method.

The HTTP response is typically buffered to enable support for synchronous reads on the response content. To enable support for response streaming, use the SetBrowserResponseStreamingEnabled extension method on the request.

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.

Call web API example

The following example calls a web API. The example requires a running web API based on the sample app described by the Tutorial: Create a web API with ASP.NET Core article. This example makes requests to the web API at https://localhost:10000/api/TodoItems. If a different web API address is used, update the ServiceEndpoint constant value in the component's @code block.

The following example makes a cross-origin resource sharing (CORS) request from http://localhost:5000 or https://localhost:5001 to the web API. Add the following CORS middleware configuration to the web API's service's Startup.Configure method:

app.UseCors(policy => 
    policy.WithOrigins("http://localhost:5000", "https://localhost:5001")
    .AllowAnyMethod()
    .WithHeaders(HeaderNames.ContentType));

Adjust the domains and ports of WithOrigins as needed for the Blazor app. For more information, see Enable Cross-Origin Requests (CORS) in ASP.NET Core.

By default, ASP.NET Core apps use ports 5000 (HTTP) and 5001 (HTTPS). To run both apps on the same machine at the same time for testing, use a different port for the web API app (for example, port 10000). For more information on setting the port, see Kestrel web server implementation in ASP.NET Core.

Pages/CallWebAPI.razor:

@page "/call-web-api"
@inject HttpClient Http

<h1>Todo Items</h1>

@if (todoItems == null)
{
    <p>No Todo Items found.</p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th class="text-center">Complete</th>
                <th>Name</th>
                <th></th>
            </tr>
        </thead>
        <tbody>
            <tr id="editRow" style="display:@editRowStyle">
                <td class="text-center">
                    <input type="checkbox" @bind="editItem.IsComplete" />
                </td>
                <td>
                    <input @bind="editItem.Name" />
                </td>
                <td class="text-center">
                    <button class="btn btn-success" @onclick="SaveItem">
                        Save
                    </button>
                    <button class="btn btn-danger" 
                            @onclick="@(() => editRowStyle = "none")">
                        Cancel
                    </button>
                </td>
            </tr>
            @foreach (var item in todoItems)
            {
                <tr>
                    <td class="text-center">
                        @if (item.IsComplete)
                        {
                            <span>&#10004;</span>
                        }
                    </td>
                    <td>@item.Name</td>
                    <td class="text-center">
                        <button class="btn btn-warning" 
                                @onclick="@(() => EditItem(item.Id))">Edit</button>
                        <button class="btn btn-danger" 
                                @onclick="@(async () => await DeleteItem(item.Id))">
                            Delete
                        </button>
                    </td>
                </tr>
            }
            <tr id="addRow">
                <td></td>
                <td>
                    <input @bind="newItemName" placeholder="New Todo Item" />
                </td>
                <td class="text-center">
                    <button class="btn btn-success" @onclick="@AddItem">Add</button>
                </td>
            </tr>
        </tbody>
    </table>
}

@code {
    private const string ServiceEndpoint = "https://localhost:10000/api/TodoItems";
    private TodoItem[] todoItems;
    private TodoItem editItem = new TodoItem();
    private string editRowStyle = "none";
    private string newItemName;

    protected override async Task OnInitializedAsync() => await GetTodoItems();

    private async Task GetTodoItems() => 
        todoItems = await Http.GetFromJsonAsync<TodoItem[]>(ServiceEndpoint);

    private void EditItem(long id)
    {
        editItem = todoItems.Single(i => i.Id == id);
        editRowStyle = "table-row";
    }

    private async Task AddItem()
    {
        var addItem = new TodoItem { Name = newItemName, IsComplete = false };
        await Http.PostAsJsonAsync(ServiceEndpoint, addItem);
        newItemName = string.Empty;
        await GetTodoItems();
        editRowStyle = "none";
    }

    private async Task SaveItem()
    {
        await Http.PutAsJsonAsync($"{ServiceEndpoint}/{editItem.Id}", editItem);
        await GetTodoItems();
        editRowStyle = "none";
    }

    private async Task DeleteItem(long id)
    {
        await Http.DeleteAsync($"{ServiceEndpoint}/{id}");
        await GetTodoItems();
        editRowStyle = "none";
    }

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

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.

Pages/FetchDataReturnsHTMLOnException.razor:

@page "/fetch-data-returns-html-on-exception"
@using System.Net.Http
@using System.Net.Http.Json
@using System.Threading.Tasks
@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.

Blazor Server apps call web APIs using HttpClient instances, typically created using IHttpClientFactory. For guidance that applies to Blazor Server, see Make HTTP requests using IHttpClientFactory in ASP.NET Core.

A Blazor Server app doesn't include an HttpClient service by default. Provide an HttpClient to the app using the HttpClient factory infrastructure.

In Startup.ConfigureServicesofStartup.cs`:

services.AddHttpClient();

The following Blazor Server 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.

Pages/CallWebAPI.razor:

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

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

@if (getBranchesError)
{
    <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 = Array.Empty<GitHubBranch>();
    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; }
    }
}

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

Cross-origin resource sharing (CORS)

Browser security restricts a webpage from making requests to a different domain 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 information on CORS requests in Blazor WebAssembly apps, see ASP.NET Core Blazor WebAssembly additional security scenarios.

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

Blazor framework component examples for testing web API access

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

HttpClientTest assets in the dotnet/aspnetcore GitHub repository

Note

Documentation links to the ASP.NET Core reference source load the repository's main branch, which represents the product unit's current development for the next release of ASP.NET Core. To select the branch for a different release, use the Switch branches or tags dropdown list to select the branch. For example, select the release/5.0 branch for the ASP.NET Core 5.0 release.

Additional resources