AspNetCore.Docs/aspnetcore/blazor/call-web-api.md

13 KiB

title author description monikerRange ms.author ms.custom ms.date no-loc uid
Call a web API from ASP.NET Core Blazor WebAssembly guardrex Learn how to call a web API from a Blazor WebAssembly app using JSON helpers, including making cross-origin resource sharing (CORS) requests. >= aspnetcore-3.1 riande mvc 06/24/2020
cookie
Cookie
Blazor
Blazor Server
Blazor WebAssembly
Identity
Let's Encrypt
Razor
SignalR
blazor/call-web-api

Call a web API from ASP.NET Core Blazor

By Luke Latham, Daniel Roth, and Juan De la Cruz

[!NOTE] This topic applies to Blazor WebAssembly. Blazor Server apps call web APIs using xref:System.Net.Http.HttpClient instances, typically created using xref:System.Net.Http.IHttpClientFactory. For guidance that applies to Blazor Server, see xref:fundamentals/http-requests.

Blazor WebAssembly apps call web APIs using a preconfigured xref:System.Net.Http.HttpClient service. Compose requests, which can include JavaScript Fetch API options, using Blazor JSON helpers or with xref:System.Net.Http.HttpRequestMessage. The xref:System.Net.Http.HttpClient service in Blazor WebAssembly apps is focused on making requests back to the server of origin. The guidance in this topic only pertains to Blazor WebAssembly apps.

View or download sample code (how to download): Select the BlazorWebAssemblySample app.

See the following components in the BlazorWebAssemblySample sample app:

  • Call Web API (Pages/CallWebAPI.razor)
  • HTTP Request Tester (Components/HTTPRequestTester.razor)

Packages

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

Add the HttpClient service

In Program.Main, add an xref:System.Net.Http.HttpClient service if it doesn't already exist:

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

HttpClient and JSON helpers

In a Blazor WebAssembly app, HttpClient is available as a preconfigured service for making requests back to the origin server.

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

xref:System.Net.Http.HttpClient and JSON helpers are also used to call third-party web API endpoints. xref:System.Net.Http.HttpClient is implemented using the browser Fetch API and is subject to its limitations, including enforcement of the same origin policy.

The client's base address is set to the originating server's address. Inject an xref:System.Net.Http.HttpClient instance using the @inject directive:

@using System.Net.Http
@inject HttpClient Http

In the following examples, a Todo web API processes create, read, update, and delete (CRUD) operations. The examples are based on a TodoItem class that stores the:

  • 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.
private class TodoItem
{
    public long Id { get; set; }
    public string Name { get; set; }
    public bool IsComplete { get; set; }
}

JSON helper methods send requests to a URI (a web API in the following examples) and process the response:

  • xref:System.Net.Http.Json.HttpClientJsonExtensions.GetFromJsonAsync%2A: Sends an HTTP GET request and parses the JSON response body to create an object.

    In the following code, the todoItems are displayed by the component. The GetTodoItems method is triggered when the component is finished rendering (OnInitializedAsync). See the sample app for a complete example.

    @using System.Net.Http
    @inject HttpClient Http
    
    @code {
        private TodoItem[] todoItems;
    
        protected override async Task OnInitializedAsync() => 
            todoItems = await Http.GetFromJsonAsync<TodoItem[]>("api/TodoItems");
    }
    
  • xref:System.Net.Http.Json.HttpClientJsonExtensions.PostAsJsonAsync%2A: Sends an HTTP POST request, including JSON-encoded content, and parses the JSON response body to create an object.

    In the following code, newItemName is provided by a bound element of the component. The AddItem method is triggered by selecting a <button> element. See the sample app for a complete example.

    @using System.Net.Http
    @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 xref:System.Net.Http.Json.HttpClientJsonExtensions.PostAsJsonAsync%2A return an xref:System.Net.Http.HttpResponseMessage. To deserialize the JSON content from the response message, use the ReadFromJsonAsync<T> extension method:

    var content = response.Content.ReadFromJsonAsync<WeatherForecast>();
    
  • xref:System.Net.Http.Json.HttpClientJsonExtensions.PutAsJsonAsync%2A: Sends an HTTP PUT request, including JSON-encoded content.

    In the following 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 and EditItem is called. The SaveItem method is triggered by selecting the Save <button> element. See the sample app for a complete example.

    @using System.Net.Http
    @inject HttpClient Http
    
    <input type="checkbox" @bind="editItem.IsComplete" />
    <input @bind="editItem.Name" />
    <button @onclick="@SaveItem">Save</button>
    
    @code {
        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 xref:System.Net.Http.Json.HttpClientJsonExtensions.PutAsJsonAsync%2A return an xref:System.Net.Http.HttpResponseMessage. To deserialize the JSON content from the response message, use the xref:System.Net.Http.Json.HttpContentJsonExtensions.ReadFromJsonAsync%2A extension method:

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

xref:System.Net.Http includes additional extension methods for sending HTTP requests and receiving HTTP responses. xref:System.Net.Http.HttpClient.DeleteAsync%2A?displayProperty=nameWithType is used to send an HTTP DELETE request to a web API.

In the following code, the Delete <button> element calls the DeleteItem method. The bound <input> element supplies the id of the item to delete. See the sample app for a complete example.

@using System.Net.Http
@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

xref:System.Net.Http.IHttpClientFactory services and the configuration of a named xref:System.Net.Http.HttpClient are supported.

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

Program.Main (Program.cs):

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

FetchData component (Pages/FetchData.razor):

@inject IHttpClientFactory ClientFactory

...

@code {
    private WeatherForecast[] forecasts;

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

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

Typed HttpClient

Typed xref:System.Net.Http.HttpClient uses one or more of the app's xref:System.Net.Http.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 WeatherForecastClient
{
    private readonly HttpClient client;

    public WeatherForecastClient(HttpClient client)
    {
        this.client = client;
    }

    public async Task<WeatherForecast[]> GetForecastAsync()
    {
        var forecasts = new WeatherForecast[0];
    
        try
        {
            forecasts = await client.GetFromJsonAsync<WeatherForecast[]>(
                "WeatherForecast");
        }
        catch
        {
            ...
        }
    
        return forecasts;
    }
}

Program.Main (Program.cs):

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

Components inject the typed xref:System.Net.Http.HttpClient to call the web API.

FetchData component (Pages/FetchData.razor):

@inject WeatherForecastClient Client

...

@code {
    private WeatherForecast[] forecasts;

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

Handle errors

When errors occur while interacting with a web API, they can be handled by developer code. For example, xref:System.Net.Http.Json.HttpClientJsonExtensions.GetFromJsonAsync%2A expects a JSON response from the server API with a Content-Type of application/json. If the response isn't in JSON format, content validation throws a xref:System.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 (missing "e").

The xref:System.Net.Http.Json.HttpClientJsonExtensions.GetFromJsonAsync%2A call expects JSON to be returned, but the server returns HTML for an unhandled exception on the server with a Content-Type of text/html. The unhandled exception occurs on the server because the path isn't found and middleware can't serve a page or view for the request.

In xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A on the client, xref:System.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:

protected override async Task OnInitializedAsync()
{
    try
    {
        forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>(
            "WeatherForcast");
    }
    catch (NotSupportedException exception)
    {
        ...
    }
}

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

For more information, see xref:blazor/fundamentals/handle-errors.

Cross-origin resource sharing (CORS)

Browser security prevents 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 prevents 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).

The Blazor WebAssembly sample app (BlazorWebAssemblySample) demonstrates the use of CORS in the Call Web API component (Pages/CallWebAPI.razor).

For more information on CORS with secure requests in Blazor apps, see xref:blazor/security/webassembly/additional-scenarios#cross-origin-resource-sharing-cors.

For general information on CORS with ASP.NET Core apps, see xref:security/cors.

Additional resources