从 ASP.NET Core Blazor 调用 Web API

注意

此版本不是本文的最新版本。 对于当前版本,请参阅此文的 .NET 8 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft 对此处提供的信息不提供任何明示或暗示的保证。

对于当前版本,请参阅此文的 .NET 8 版本

本文介绍如何在 Blazor 应用中调用 Web API。

程序包

System.Net.Http.Json 包为使用 System.Text.Json 执行自动序列化和反序列化的 System.Net.Http.HttpClientSystem.Net.Http.HttpContent 提供扩展方法。 System.Net.Http.Json 包由 .NET 共享框架提供,不需要向应用添加包引用。

示例应用

请在 dotnet/blazor-samples GitHub 存储库中查看示例应用。

BlazorWebAppCallWebApi

从 Blazor Web 应用调用外部(不在 Blazor Web 应用中)待办事项列表 Web API:

  • Backend:基于 Minimal API 的用于维护待办事项列表的 Web API 应用。 Web API 应用是独立于 Blazor Web 应用的应用,可能托管在不同的服务器上。
  • BlazorApp/BlazorApp.Client:Blazor Web 应用,使用 HttpClient 调用 Web API 应用来执行待办事项列表操作,例如从待办事项列表中创建、读取、更新和删除 (CRUD) 项目。

对于客户端呈现 (CSR),包括交互式 WebAssembly 组件和采用 CSR 的自动组件,通过在客户端项目 (BlazorApp.Client) 的 Program 文件中注册的预配置 HttpClient 进行调用:

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

对于服务器端呈现 (SSR),包括预呈现和交互式服务器组件、预呈现的 WebAssembly 组件以及预呈现或采用 SSR 的自动组件,通过在服务器项目 (BlazorApp) 的 Program 文件中注册的 HttpClient 进行调用:

builder.Services.AddHttpClient();

调用内部(在 Blazor Web 应用中)电影列表 API,其中 API 位于 Blazor Web 应用的服务器项目中:

  • BlazorApp:维护电影列表的 Blazor Web 应用:
    • 在服务器上的应用内对电影列表进行操作时,使用的是普通的 API 调用。
    • 当基于 Web 的客户端进行 API 调用时,根据 Minimal API,使用 Web API 对电影列表执行操作。
  • BlazorApp.Client:Blazor Web 应用的客户端项目,其中包含交互式 WebAssembly 和自动组件,用于用户管理电影列表。

对于 CSR(包括交互式 WebAssembly 组件和采用 CSR 的自动组件),对 API 的调用是通过基于客户端的服务 (ClientMovieService) 进行的,该服务使用在客户端项目 (BlazorApp.Client) 的 Program 文件中注册的预配置 HttpClient。 由于这些调用是通过公共或专用 Web 进行的,因此电影列表 API 是 Web API

以下示例从 /movies 终结点获取电影列表:

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

对于 SSR(包括预呈现和交互式服务器组件、预呈现 WebAssembly 组件以及预呈现或采用 SSR 的自动组件),直接通过基于服务器的服务 (ServerMovieService) 进行调用。 API 不依赖于网络,因此是针对电影列表 CRUD 操作的标准 API。

获取电影列表的示例如下:

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

BlazorWebAppCallWebApi_Weather

天气数据示例应用,以流式呈现方式显示天气数据。

BlazorWebAssemblyCallWebApi

在 Blazor WebAssembly 应用中调用待办事项列表 Web API:

  • Backend:基于 Minimal API 的用于维护待办事项列表的 Web API 应用。
  • BlazorTodo:Blazor WebAssembly 应用,该应用使用针对待办事项列表 CRUD 操作的预配置 HttpClient 来调用 Web API。

用于调用外部 Web API 的服务器端方案

基于服务器的组件使用 HttpClient 实例(通常使用 IHttpClientFactory 创建)调用外部 Web API。 有关适用于服务器端应用的指导,请参阅在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求

默认情况下,服务器端应用不包含 HttpClient 服务。 使用 HttpClient 工厂基础结构向应用提供 HttpClient

Program 文件中:

builder.Services.AddHttpClient();

以下 Razor 组件会向 GitHub 分支的 Web API 发出请求,类似于在 ASP.NET Core 中使用 IHttpClientFactory 发出 HTTP 请求一文中的基本用法示例。

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 || 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; }
    }
}

在前面的 C# 12 或更高版本示例中,为 branches 变量创建了一个空数组 ([])。 对于早期版本的 C#,将创建一个空数组 (Array.Empty<GitHubBranch>())。

有关其他工作示例,请参阅 ASP.NET Core Blazor 文件上传一文中的服务器端文件上传示例,该示例将文件上传到了 Web API 控制器。

Web API 调用的服务抽象

本部分适用于在服务器项目中维护 Web API 或将 Web API 调用转换为外部 Web API 的 Blazor Web 应用。

使用交互式 WebAssembly 和自动呈现模式时,默认情况下会预呈现组件。 在将 Blazor 捆绑包下载到客户端并激活客户端运行时之前,系统最初也会从服务器中以交互方式呈现自动组件。 这意味着应该设计使用这些呈现模式的组件,以便它们能够在客户端和服务器中成功运行。 如果组件在客户端上运行时必须调用基于服务器项目的 API,或将请求转换为外部 Web API(位于 Blazor Web 应用外部),则建议的方法是在服务接口的后面抽象该 API 调用,并实现服务的客户端和服务器版本:

  • 客户端版本使用预配置 HttpClient 调用 Web API。
  • 服务器版本通常可以直接访问服务器端资源。 建议在服务器上注入 HttpClient 来回调服务器,因为网络请求通常不是必要项。 或者,API 可能位于服务器项目的外部,但需要对服务器进行服务抽象才能以某种方式转换请求,例如,向代理请求添加访问令牌。

使用 WebAssembly 呈现模式时,还可以选择禁用预呈现,以便仅在客户端中呈现组件。 有关详细信息,请参阅 ASP.NET Core Blazor 呈现模式

示例(示例应用):

  • 示例应用 BlazorWebAppCallWebApi 中的电影列表 Web API。
  • 示例应用 BlazorWebAppCallWebApi_Weather 中的流式呈现天气数据 Web API。
  • BlazorWebAppOidc(非 BFF 模式)或 BlazorWebAppOidcBff(BFF 模式)示例应用中返回到客户端的天气数据。 这些应用演示了安全的 (Web) API 调用。 有关详细信息,请参阅使用 OpenID Connect (OIDC) 保护 ASP.NET Core Blazor Web 应用

Blazor Web 应用的外部 Web API

本部分适用于调用由单独(外部)项目(可能托管在不同服务器上)维护的 Web API 的 Blazor 应用。

Blazor Web 应用通常会预呈现客户端 WebAssembly 组件,而 Auto 组件在静态或交互式服务器端呈现 (SSR) 期间呈现在服务器上。 默认情况下,HttpClient 服务不会在 Blazor Web 应用的主项目中注册。 如果应用仅与在 .Client 项目中注册的 HttpClient 服务一起运行,如“添加 HttpClient 服务”部分中所述,则执行应用会导致运行时错误:

InvalidOperationException:无法为类型“...{COMPONENT}”上的属性“Http”提供值。 不存在“System.Net.Http.HttpClient”类型的已注册服务。

请使用下述任一方法:

  • HttpClient 服务添加到服务器项目,使 HttpClient 在 SSR 期间可用。 在服务器项目的 Program 文件中使用以下服务注册:

    builder.Services.AddHttpClient();
    

    由于 HttpClient 服务是由共享框架提供的,因此不需要显式包引用。

    示例:BlazorWebAppCallWebApi示例应用中的待办事项列表 Web API

  • 如果不需要预呈现调用 Web API 的 WebAssembly 组件,请按照 ASP.NET Core Blazor 呈现模式中的指导禁用预呈现。 如果采用此方法,则无需将 HttpClient 服务添加到 Blazor Web 应用的主项目,因为系统不会在服务器上预呈现该组件。

有关详细信息,请参阅客户端服务在预呈现期间无法解析

预呈现的数据

预呈现时,系统会呈现两次组件:首先以静态方式呈现,然后以交互方式呈现。 状态不会自动从预呈现组件流向交互式组件。 如果组件执行异步初始化操作,并在初始化过程中针对不同状态呈现不同的内容,例如“正在加载...”进度指示器,则在系统呈现两次组件时,你可能会看到闪烁。

你可以通过使用持久组件状态 API 持续预呈现状态来解决此问题,BlazorWebAppCallWebApiBlazorWebAppCallWebApi_Weather示例应用程序演示了这一点。 在以交互方式呈现组件时,系统可以使用相同的状态以相同的方式呈现组件。 但是,该 API 目前无法使用增强型导航,你可以通过禁用页面链接 (data-enhanced-nav=false) 上的增强型导航来解决此问题。 有关更多信息,请参见以下资源:

添加 HttpClient 服务

本部分中的指南适用于客户端方案。

客户端组件使用预配置的 HttpClient 服务调用 Web API,该服务主要向源服务器返回请求。 可以在开发人员代码中创建其他 Web API 的更多 HttpClient 服务配置。 请求是使用 BlazorJSON 帮助程序或 HttpRequestMessage 组成的。 请求可以包含 Fetch API 选项配置。

仅当为应用中的单个 HttpClient 实例调用单个 Web API 时,本部分中的配置示例才有用。 当应用必须调用多个 Web API(每个 API 都有自己的基址和配置)时,可以采用本文会稍后介绍的以下方法:

Program 文件中,如果用于创建应用的 Blazor 项目模板中不存在 HttpClient 服务,请添加该服务:

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

前面的示例使用 builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress) 设置基址,该属性会获取应用的基址,并且通常派生自主机页中 <base> 标记的 href 值。

使用客户端自己的基址的最常见用例包括:

  • Blazor Web 应用(.NET 8 或更高版本)的客户端项目(.Client)从 WebAssembly 组件或在 WebAssembly 客户端上运行的代码向服务器应用中的 API 进行 Web API 调用。
  • 托管的 Blazor WebAssembly 应用的客户端项目(Client)对服务器项目(Server)进行 Web API 调用。 请注意,托管的 Blazor WebAssembly 项目模板在 .NET 8 和更高版本中不再可用。 但是,.NET 8 仍支持托管的 Blazor WebAssembly 应用。

如果要调用外部 Web API(与客户端应用不在同一 URL 空间中),请将 URI 设置为该 Web API 的基址。 以下示例将 Web API 的基址设置为 https://localhost:5001,其中正在运行单独 Web API 应用,且已准备好响应来自客户端应用的请求:

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

JSON 帮助程序

HttpClient 作为一个预配置服务提供,用于使请求返回源服务器。

HttpClient 和 JSON 帮助程序 (System.Net.Http.Json.HttpClientJsonExtensions) 还用于调用第三方 Web API 终结点。 HttpClient 是使用浏览器的 Fetch API 实现的,受其限制的制约,包括强制实施同源策略(本文后面的“跨源资源共享 (CORS)”部分对此进行了介绍)。

客户端的基址设置为原始服务器的地址。 使用 @inject 指令将 HttpClient 实例注入到组件中:

@using System.Net.Http
@inject HttpClient Http

使用 System.Net.Http.Json 命名空间访问 HttpClientJsonExtensions,包括 GetFromJsonAsyncPutAsJsonAsyncPostAsJsonAsync

@using System.Net.Http.Json

以下部分介绍了 JSON 帮助程序:

System.Net.Http 包括用于发送 HTTP 请求和接收 HTTP 响应的其他方法,例如发送 DELETE 请求。 有关详细信息,请参阅 DELETE 和其他扩展方法部分。

GET from JSON (GetFromJsonAsync)

GetFromJsonAsync 发送 HTTP GET 请求,并分析 JSON 响应正文来创建对象。

在下面的组件代码中,todoItems 是由组件显示的。 在组件完成初始化 (OnInitializedAsync) 后调用 GetFromJsonAsync

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

POST as JSON (PostAsJsonAsync)

PostAsJsonAsync 向指定 URI 发送 POST 请求(请求正文中包含序列化为 JSON 的值)。

在下面的组件代码中,newItemName 是由组件的绑定元素提供的。 通过选择 <button> 元素来触发 AddItem 方法。

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

PostAsJsonAsync 返回 HttpResponseMessage。 若要对响应消息中的 JSON 内容进行反序列化处理,请使用 ReadFromJsonAsync 扩展方法。 以下示例将 JSON 天气数据读取为数组:

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

PUT as JSON (PutAsJsonAsync)

PutAsJsonAsync 发送 HTTP PUT 请求(包括 JSON 编码的内容)。

在下面的组件代码中,NameIsCompletededitItem 值是由组件的绑定元素提供的。 当在 UI 的另一个部分中选择项(不显示)并调用 EditItem 时,会设置项的 Id。 通过选择 <button> 元素来触发 SaveItem 方法。 为简洁起见,以下示例不显示加载 todoItems。 有关加载项的示例,请参阅 GET from JSON (GetFromJsonAsync) 部分。

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

PutAsJsonAsync 返回 HttpResponseMessage。 若要对响应消息中的 JSON 内容进行反序列化处理,请使用 ReadFromJsonAsync 扩展方法。 以下示例将 JSON 天气数据读取为数组:

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

PATCH as JSON (PatchAsJsonAsync)

PatchAsJsonAsync 发送 HTTP PATCH 请求(包括 JSON 编码的内容)。

注意

有关详细信息,请参阅 ASP.NET Core Web API 中的 JsonPatch

在以下示例中,PatchAsJsonAsync 接收一个 JSON PATCH 文档,该文档采用带转义引号的纯文本字符串形式:

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

PatchAsJsonAsync 返回 HttpResponseMessage。 若要对响应消息中的 JSON 内容进行反序列化处理,请使用 ReadFromJsonAsync 扩展方法。 以下示例将 JSON 待办事项数据读取为数组。 如果方法没有返回任何项目数据,则会创建空数组,因此在执行语句后 content 不会为 null:

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

未编码的 PATCH 文档采用缩进、间距和未转义引号布局,显示为以下 JSON:

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

为了简化在发出 PATCH 请求的应用中创建 PATCH 文档,应用可以使用 .NET JSON PATCH 支持,如以下指南所示。

安装 NuGet 包Microsoft.AspNetCore.JsonPatch并使用包的 API 功能为 PATCH 请求编写 JsonPatchDocument

注意

有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。

System.Text.JsonSystem.Text.Json.SerializationMicrosoft.AspNetCore.JsonPatch 命名空间的 @using 指令添加到 Razor 组件的顶部:

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

使用 Replace 方法将 JsonPatchDocumentTodoItemIsComplete 设置为 true 的编写:

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

将文档的操作 (patchDocument.Operations) 传递给 PatchAsJsonAsync 调用:

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

JsonSerializerOptions.DefaultIgnoreCondition 设置为 JsonIgnoreCondition.WhenWritingDefault,仅当属性等于其类型的默认值时,才会忽略该属性。

如果你要以舒适的显示格式呈现 JSON 有效负载,请添加设置为 trueJsonSerializerOptions.WriteIndented。 编写缩进 JSON 与处理 PATCH 请求无关,通常不会在 Web API 请求的生产应用中执行。

按照 ASP.NET Core Web API 中的 JsonPatch 一文中的指南将 PATCH 控制器操作添加到 Web API。 或者,可以通过以下步骤将 PATCH 请求处理作为极简 API 来实现。

Microsoft.AspNetCore.Mvc.NewtonsoftJson NuGet 包添加对 Web API 应用的包引用。

注意

无需将 Microsoft.AspNetCore.JsonPatch 包的包引用添加到应用,因为对 Microsoft.AspNetCore.Mvc.NewtonsoftJson 包的引用会自动以传递方式添加对 Microsoft.AspNetCore.JsonPatch 的包引用。

Program 文件中,为 Microsoft.AspNetCore.JsonPatch 命名空间添加 @using 指令:

using Microsoft.AspNetCore.JsonPatch;

为 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();
});

警告

ASP.NET Core Web API 中的 JsonPatch 一文中的其他示例一样,前面的 PATCH API 不会保护 Web API 免受过度发布攻击。 有关详细信息,请参阅教程:使用 ASP.NET Core 创建 Web API

若要获得完全正常的 PATCH 体验,请参阅BlazorWebAppCallWebApi示例应用

DELETE (DeleteAsync) 和其他扩展方法

System.Net.Http 包括用于发送 HTTP 请求和接收 HTTP 响应的附加扩展方法。 HttpClient.DeleteAsync 用于将 HTTP DELETE 请求发送到 Web API。

在下面的组件代码中,<button> 元素调用 DeleteItem 方法。 绑定 <input> 元素提供要删除的项的 id

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

使用 IHttpClientFactory 命名的 HttpClient

支持已命名的 HttpClientIHttpClientFactory 服务和配置。

注意

使用 IHttpClientFactory 中的命名 HttpClient 的替代方法是使用类型化 HttpClient。 有关详细信息,请参阅类型化 HttpClient 部分。

Microsoft.Extensions.Http NuGet 包添加到应用。

注意

有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。

在客户端项目的 Program 文件中:

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

如果命名的客户端将由 Blazor Web 应用的预呈现客户端组件使用,则前面的服务注册应同时出现在服务器项目和 .Client 项目中。 在服务器上,builder.HostEnvironment.BaseAddress 将由 Web API 的基址替换,下面将进一步对此进行介绍。

前面的客户端示例使用 builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress) 设置基址,该属性会获取客户端应用的基址,并且通常派生自主机页中 <base> 标记的 href 值。

使用客户端自己的基址的最常见用例包括:

  • Blazor Web 应用的客户端项目 (.Client),该应用从 WebAssembly/Auto 组件或从在 WebAssembly 客户端上运行的代码,向位于同一主机地址处的服务器应用中的 API 进行 Web API 调用。
  • 托管的 Blazor WebAssembly 应用的客户端项目 (Client),该应用对服务器项目 (Server) 进行 Web API 调用。

使用客户端自身基址的最常见用例出现在托管的 Blazor WebAssembly 应用的客户端项目 (Client) 中,该应用对服务器项目 (Server) 进行 Web API 调用。

如果你正在调用外部 Web API(与客户端应用不在同一 URL 空间中)或者在服务器端应用中配置服务(例如处理服务器上客户端组件的预呈现),请将 URI 设置为 Web API 的基址。 以下示例将 Web API 的基址设置为 https://localhost:5001,其中正在运行单独 Web API 应用,且已准备好响应来自客户端应用的请求:

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

在下面的组件代码中:

@inject IHttpClientFactory ClientFactory

...

@code {
    private Forecast[]? forecasts;

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

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

BlazorWebAppCallWebApi示例应用演示了如何在其 CallTodoWebApiCsrNamedClient 组件中调用具有命名 HttpClient 的 Web API。 有关客户端应用中的其他工作演示(使用命名的 HttpClient 调用 Microsoft Graph),请参阅将 Graph API 与 ASP.NET Core 配合使用Blazor WebAssembly

有关客户端应用中的工作演示(使用命名的 HttpClient 调用 Microsoft Graph),请参阅将 Graph API 与 ASP.NET Core 配合使用Blazor WebAssembly

类型化 HttpClient

类型化 HttpClient 使用应用的一个或多个 HttpClient 实例(默认或命名)从一个或多个 web API 终结点返回数据。

注意

使用类型化 HttpClient 的替代方法是使用 IHttpClientFactory 中的命名 HttpClient。 有关详细信息,请参阅使用 IHttpClientFactory 的命名 HttpClient 部分。

Microsoft.Extensions.Http NuGet 包添加到应用。

注意

有关将包添加到 .NET 应用的指南,请参阅包使用工作流(NuGet 文档)中“安装和管理包”下的文章。 在 NuGet.org 中确认正确的包版本。

以下示例从 /forecast 处的 Web API 发出对 JSON 天气预报数据的 GET 请求。

ForecastHttpClient.cs

using System.Net.Http.Json;

namespace BlazorSample.Client;

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

在客户端项目的 Program 文件中:

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

如果类型化客户端将由 Blazor Web 应用的预呈现客户端组件使用,则前面的服务注册应同时出现在服务器项目和 .Client 项目中。 在服务器上,builder.HostEnvironment.BaseAddress 将由 Web API 的基址替换,下面将进一步对此进行介绍。

前面的示例使用 builder.HostEnvironment.BaseAddress (IWebAssemblyHostEnvironment.BaseAddress) 设置基址,该属性会获取客户端应用的基址,并且通常派生自主机页中 <base> 标记的 href 值。

使用客户端自己的基址的最常见用例包括:

  • Blazor Web 应用的客户端项目 (.Client),该应用从 WebAssembly/Auto 组件或从在 WebAssembly 客户端上运行的代码,向位于同一主机地址处的服务器应用中的 API 进行 Web API 调用。
  • 托管的 Blazor WebAssembly 应用的客户端项目 (Client),该应用对服务器项目 (Server) 进行 Web API 调用。

使用客户端自身基址的最常见用例出现在托管的 Blazor WebAssembly 应用的客户端项目 (Client) 中,该应用对服务器项目 (Server) 进行 Web API 调用。

如果你正在调用外部 Web API(与客户端应用不在同一 URL 空间中)或者在服务器端应用中配置服务(例如处理服务器上客户端组件的预呈现),请将 URI 设置为 Web API 的基址。 以下示例将 Web API 的基址设置为 https://localhost:5001,其中正在运行单独 Web API 应用,且已准备好响应来自客户端应用的请求:

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

组件插入类型化的 HttpClient 来调用 web API。

在下面的组件代码中:

  • 前面的 ForecastHttpClient 的实例被注入,它创建了一个已限定类型的 HttpClient
  • 限定类型的 HttpClient 用于发出 GET 请求,从 Web API 获取 JSON 天气预报数据。
@inject ForecastHttpClient Http

...

@code {
    private Forecast[]? forecasts;

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

BlazorWebAppCallWebApi示例应用演示了如何在其 CallTodoWebApiCsrTypedClient 组件中调用具有类型化 HttpClient 的 Web API。 请注意,该组件采用客户端呈现 (CSR)(InteractiveWebAssembly 呈现模式)和预呈现,因此类型化客户端服务注册会出现在服务器项目和 .Client 项目的 Program 文件中。

本部分中的指导适用于依赖于身份验证 cookie 的客户端方案。

对于被视为相比持有者令牌更为安全的基于 cookie 的身份验证,可以通过在预配置的 HttpClient 上使用 DelegatingHandler 调用 AddHttpMessageHandler 来随每个 Web API 请求发送 cookie 凭据。 该处理程序会使用 BrowserRequestCredentials.Include 配置 SetBrowserRequestCredentials,它会建议浏览器将凭据((例如 cookie 或 HTTP 身份验证标头))和每个请求一起发送,包括跨源请求。

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);
    }
}

CookieHandler 是在 Program 文件中注册的:

builder.Services.AddTransient<CookieHandler>();

消息处理程序将添加到需要 cookie 身份验证的任何预配置 HttpClient 中:

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

在撰写 HttpRequestMessage 时,直接设置浏览器请求凭据和标头:

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

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

具有提取 API 请求选项的 HttpClientHttpRequestMessage

本部分中的指南适用于依赖于持有者令牌身份验证的客户端方案。

HttpClientAPI 文档)和 HttpRequestMessage 可用于自定义请求。 例如,可以指定 HTTP 方法和请求标头。 以下组件向 Web API 终结点发出 POST 请求,并显示响应正文。

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 对 HttpClient 的客户端实现使用 Fetch API,并通过 HttpRequestMessage 扩展方法和 WebAssemblyHttpRequestMessageExtensions 配置底层的特定于请求的 Fetch API 选项。 使用通用的 SetBrowserRequestOption 扩展方法设置其他选项。 Blazor 和底层的 Fetch API 不直接添加或修改请求头。 有关用户代理(如浏览器)如何与标头交互的详细信息,请查阅外部用户代理文档集和其他 Web 资源。

HTTP 响应通常进行缓冲,以便能够支持同步读取响应内容。 要支持响应流式处理,请对请求使用 SetBrowserResponseStreamingEnabled 扩展方法。

要在跨源请求中加入凭据,请使用 SetBrowserRequestCredentials 扩展方法:

requestMessage.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);

有关 Fetch API 选项的详细信息,请参阅 MDN Web 文档:WindowOrWorkerGlobalScope.fetch():参数

处理错误

处理开发人员代码中发生的 Web API 响应错误。 例如,GetFromJsonAsync 需要 Web API 的 JSON 响应,其中 Content-Typeapplication/json。 如果响应不是 JSON 格式,则内容验证会引发 NotSupportedException

在下面的示例中,天气预测数据请求的 URI 终结点拼写错误。 URI 应为 WeatherForecast,但在调用中显示为 WeatherForcastForecast 中缺少字母 e

GetFromJsonAsync 调用需要返回 JSON,但 Web API 为未经处理的异常返回 HTML,其中 Content-Typetext/html。 由于找不到 /WeatherForcast 的路径并且中间件无法为请求提供页面或视图,因此发生未经处理的异常。

在客户端上的 OnInitializedAsync 中,当响应内容验证为非 JSON 时,将引发 NotSupportedException。 在 catch 块中捕获到异常,其中自定义逻辑可以记录错误或向用户显示友好错误消息。

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;
        }
    }
}

注意

前面示例代码为了方便本文演示。 即使终结点不存在或服务器上发生未经处理的异常,也可将 Web API 配置为返回 JSON。

有关详细信息,请参阅处理 ASP.NET Core Blazor 应用中的错误

跨源资源共享 (CORS)

浏览器安全限制网页向不同域(而不是向网页提供服务的域)发出请求。 此限制称为同域策略。 同源策略可限制(但不阻止)恶意站点从另一站点读取敏感数据。 若要从浏览器向具有不同源的终结点进行请求,终结点必须启用跨源资源共享 (CORS)

有关服务器端 CORS 的详细信息,请参阅在 ASP.NET Core 中启用跨源请求 (CORS)。 本文的示例与 Razor 组件方案不直接相关,但本文对于学习一般 CORS 概念很有用。

有关客户端 CORS 请求的信息,请参阅 ASP.NET Core Blazor WebAssembly 附加安全方案

防伪支持

若要向 HTTP 请求添加防伪支持,请注入 AntiforgeryStateProvider 并将 RequestToken 作为 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);
    ...
}

有关详细信息,请参阅 ASP.NET Core Blazor 身份验证和授权

用于测试 Web API 访问的 Blazor 框架组件示例

各种网络工具都可公开用于直接测试 Web API 后端应用,如 Firefox 浏览器(开发者版)。 Blazor 框架的参考源包括适用于测试的 HttpClient 测试资产:

dotnet/aspnetcore GitHub 存储库中的 HttpClientTest 资产

注意

指向 .NET 参考源的文档链接通常会加载存储库的默认分支,该分支表示针对下一个 .NET 版本的当前开发。 若要为特定版本选择标记,请使用“切换分支或标记”下拉列表。 有关详细信息,请参阅如何选择 ASP.NET Core 源代码的版本标记 (dotnet/AspNetCore.Docs #26205)

其他资源

常规

服务器端

客户端