Blazor 服务器应用程序使用标准的 ASP.NET Core 应用程序,它们在服务器上执行 .NET 代码。我们可以按照在 ASP.NET Core Web 应用程序中使用的相同方式访问这些应用程序中的任何 .NET 库或服务器端功能。其中一项功能是使用 HTTP 客户端实例向第三方 Web API 发出 HTTP 请求。在本教程中,我将向您展示创建 HTTP 客户端实例的不同方法。我还将向您展示如何使用第三方 API 在 Blazor 服务器应用程序中获取和显示数据。
Blazor 服务器应用程序中发出 HTTP 请求
第三方 Web API 概述
我们将开发一个 Blazor 服务器应用程序,该应用程序将允许用户在 Blazor 页面组件上输入国家代码和年份,然后我们将调用第三方 API 以获取该特定国家/地区在该特定年份的公共假期列表。我们将使用的第三方 API 是 Nager.Date,它是一个全球公共假期 API。
这是一个非常简单的 API,您可以通过输入以下 URL 在 Postman 中轻松测试此 API。
https://date.nager.at/api/v2/PublicHolidays/2020/US
该 API 的响应是 JSON 格式的公共假期列表,如下所示:
Blazor 服务器应用程序入门
在 Visual Studio 2019 中创建一个 Blazor 服务器应用程序并创建一个名为 Models 的文件夹。在 Models 文件夹中添加以下两个模型类以映射上面显示的 Holidays API 请求和响应。
HolidayRequestModel.cs
public class HolidayRequestModel
{
public string CountryCode { get; set; }
public int Year { get; set; }
}
HolidayResponseModel.cs
public class HolidayResponseModel
{
public string Name { get; set; }
public string LocalName { get; set; }
public DateTime? Date { get; set; }
public string CountryCode { get; set; }
public bool Global { get; set; }
}
接下来,在 Pages 文件夹中创建一个新的 Razor 组件 HolidaysExplorer.razor 及其代码隐藏文件 HolidaysExplorer.razor.cs。如果您想了解有关 Razor 组件和代码隐藏文件的更多信息,可以阅读我的文章 Blazor 组件初学者指南。
HolidaysExplorer.razor.cs
public partial class HolidaysExplorer
{
private HolidayRequestModel HolidaysModel = new HolidayRequestModel();
private List<HolidayResponseModel> Holidays = new List<HolidayResponseModel>();
[Inject]
protected IHolidaysApiService HolidaysApiService { get; set; }
private async Task HandleValidSubmit()
{
Holidays = await HolidaysApiService.GetHolidays(HolidaysModel);
}
}
HolidaysModel 字段是 HolidayRequestModel 类的一个实例,它将帮助我们创建一个简单的表单来询问用户国家代码和年份。以下代码片段显示了使用 HolidaysModel 对象创建的 Blazor 表单。 HandleValidSubmit 方法是使用 Blazor 表单的 OnValidSubmit 事件配置的,它将在用户提交表单时调用。
<EditForm Model="@HolidaysModel" OnValidSubmit="@HandleValidSubmit" class="form-inline">
<label class="ml-2">Country Code:</label>
<InputText id="CountryCode" @bind-Value="HolidaysModel.CountryCode" class="form-control" />
<label class="ml-2">Year:</label>
<InputNumber id="Year" @bind-Value="HolidaysModel.Year" class="form-control" />
<button class="btn btn-primary ml-2" type="submit">Submit</button>
</EditForm>
Holidays列表将用于显示从第三方 API 返回的假期。我们需要通过使用简单的 @foreach 循环迭代假期来生成一个简单的引导表。
@if (Holidays.Count > 0)
{
<table class="table table-bordered table-striped table-sm">
<thead>
<tr>
<th>Date</th>
<th>Name</th>
<th>Local Name</th>
<th>Country Code</th>
<th>Global</th>
</tr>
</thead>
<tbody>
@foreach (var item in Holidays)
{
<tr>
<td>@item.Date.Value.ToShortDateString()</td>
<td>@item.Name</td>
<td>@item.LocalName</td>
<td>@item.CountryCode</td>
<td>@item.Global</td>
</tr>
}
</tbody>
</table>
}
HolidaysExplorer.razor 视图的完整代码如下所示。
HolidaysExplorer.razor
@page "/"
<h3>Holidays Explorer</h3>
<br />
<EditForm Model="@HolidaysModel" OnValidSubmit="@HandleValidSubmit" class="form-inline">
<label class="ml-2">Country Code:</label>
<InputText id="CountryCode" @bind-Value="HolidaysModel.CountryCode" class="form-control" />
<label class="ml-2">Year:</label>
<InputNumber id="Year" @bind-Value="HolidaysModel.Year" class="form-control" />
<button class="btn btn-primary ml-2" type="submit">Submit</button>
</EditForm>
<br />
@if (Holidays.Count > 0)
{
<table class="table table-bordered table-striped table-sm">
<thead>
<tr>
<th>Date</th>
<th>Name</th>
<th>Local Name</th>
<th>Country Code</th>
<th>Global</th>
</tr>
</thead>
<tbody>
@foreach (var item in Holidays)
{
<tr>
<td>@item.Date.Value.ToShortDateString()</td>
<td>@item.Name</td>
<td>@item.LocalName</td>
<td>@item.CountryCode</td>
<td>@item.Global</td>
</tr>
}
</tbody>
</table>
}
如果此时您将运行该应用程序,您将看到一个没有任何假期的简单 HTML 表单。这是因为 HandleValidSubmit 方法是空的,我们还没有调用任何 API 来获取假期数据。
在 Blazor 服务器应用程序中使用 IHttpClientFactory
创建 HttpClient 使用 HttpClient 在 Blazor 服务器应用程序中使用第三方 API 有多种不同的方法,因此让我们从一个基本示例开始,在该示例中我们将使用 IHttpClientFactory 创建 HttpClient 对象。
在项目中创建一个Services文件夹,创建如下IHolidaysApiService接口。该接口只有一个 GetHolidays 方法,它将 HolidayRequestModel 作为参数并返回 HolidayResponseModel 对象的列表。
IHolidaysApiService.cs
public interface IHolidaysApiService
{
Task<List<HolidayResponseModel>> GetHolidays(HolidayRequestModel holidaysRequest);
}
接下来,在Services文件夹中创建一个HolidaysApiService类,实现上面的接口。
HolidaysApiService.cs
public class HolidaysApiService : IHolidaysApiService
{
private readonly IHttpClientFactory _clientFactory;
public HolidaysApiService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<List<HolidayResponseModel>> GetHolidays(HolidayRequestModel holidaysRequest)
{
var result = new List<HolidayResponseModel>();
var url = string.Format("https://date.nager.at/api/v2/PublicHolidays/{0}/{1}",
holidaysRequest.Year, holidaysRequest.CountryCode);
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Accept", "application/vnd.github.v3+json");
var client = _clientFactory.CreateClient();
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var stringResponse = await response.Content.ReadAsStringAsync();
result = JsonSerializer.Deserialize<List<HolidayResponseModel>>(stringResponse,
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
}
else
{
result = Array.Empty<HolidayResponseModel>().ToList();
}
return result;
}
}
在上面的 GetHolidays 方法中,我们首先为第三方 API 创建了一个 URL,并在 URL 中附加了国家/地区代码和年份参数。
var url = string.Format("https://date.nager.at/api/v2/PublicHolidays/{0}/{1}", holidaysRequest.Year, holidaysRequest.CountryCode);
接下来,我们创建了 HttpRequestMessage 对象并将其配置为向第三方 API URL 发送 HTTP GET 请求。
var request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Accept", "application/vnd.github.v3+json");
可以使用依赖注入 (DI) 请求 IHttpClientFactory,这就是我们将其注入上述类的构造函数的原因。下面这行使用 IHttpClientFactory 创建一个 HttpClient 实例。
var client = _clientFactory.CreateClient();
一旦我们有可用的 HttpClient 对象,我们只需调用它的 SendAsync 方法来发送一个 HTTP GET 请求到
var response = await client.SendAsync(request);
如果 API 调用成功,我们将使用以下行将响应作为字符串读取。
var stringResponse = await response.Content.ReadAsStringAsync();
最后,我们使用 JsonSerializer 类的 Deserialize 方法反序列化响应。
result = JsonSerializer.Deserialize<List<HolidayResponseModel>>(stringResponse,
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
在我们测试我们的应用程序之前,我们需要在 Startup.cs 文件中注册 HolidaysApiService。我们还需要使用 AddHttpClient 方法注册 IHttpClientFactory。
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<IHolidaysApiService, HolidaysApiService>();
services.AddHttpClient();
}
运行应用程序并在文本字段中提供任何国家/地区代码和年份。单击提交按钮应该会在后台调用我们的 GetHolidays 方法,您应该能够看到如下所示的公共假期列表。
在 Blazor 服务器应用程序中创建命名的 HttpClient 对象
上面的示例适用于您正在重构现有应用程序并且您希望在不影响整个应用程序的情况下使用 IHttpClientFactory 在某些方法中创建 HttpClient 对象的场景。如果您正在创建一个新应用程序或者您想要集中创建 HttpClient 对象的方式,那么您必须使用命名的 HTTP 客户端。
以下是创建命名 HTTP 客户端的好处:
- 我们可以给每个 HttpClient 一个名称,并在应用程序启动时指定与 HttpClient相关的所有配置,而不是将配置分散在整个应用程序中。
- 我们可以一次配置命名的 HttpClient 并多次重用它来调用特定 API 提供者的API。
- 我们可以根据这些客户端在应用程序不同领域的使用情况,配置多个具有不同配置的命名 HttpClient 对象。
我们可以在 ConfigureServices 中指定一个命名的客户端 Startup.cs 文件的方法使用我们上面使用的名称 AddHttpClient 方法。
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<IHolidaysApiService, HolidaysApiService>();
services.AddHttpClient("HolidaysApi", c =>
{
c.BaseAddress = new Uri("https://date.nager.at/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
});
}
我们需要指定客户端的名称,例如HolidaysApi,我们还可以配置 BaseAddress、DefaultRequestHeaders 和其他属性,如上所示。
一旦配置了命名的 HttpClient,我们现在可以使用相同的 CreateClient 方法在整个应用程序中创建 HttpClient 对象,但这次我们需要指定哪个命名的客户端,例如我们要创建的 HolidaysApi。
HolidaysApiService.cs
public class HolidaysApiService : IHolidaysApiService
{
private readonly IHttpClientFactory _clientFactory;
public HolidaysApiService(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<List<HolidayResponseModel>> GetHolidays(HolidayRequestModel holidaysRequest)
{
var result = new List<HolidayResponseModel>();
var url = string.Format("api/v2/PublicHolidays/{0}/{1}",
holidaysRequest.Year, holidaysRequest.CountryCode);
var request = new HttpRequestMessage(HttpMethod.Get, url);
var client = _clientFactory.CreateClient("HolidaysApi");
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
var stringResponse = await response.Content.ReadAsStringAsync();
result = JsonSerializer.Deserialize<List<HolidayResponseModel>>(stringResponse,
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
}
else
{
result = Array.Empty<HolidayResponseModel>().ToList();
}
return result;
}
}
名称例如我们在 CreateClient 方法中提到的 HolidaysApi 必须与我们在 Startup.cs 文件中配置的名称匹配。每次调用 CreateClient 方法时,都会为我们创建一个新的 HttpClient 实例。
我们也不需要在请求 URL 中指定 API 主机名,因为我们已经在 Startup.cs 文件中指定了基于地址。
再次运行应用程序并提供国家代码和年份值,您应该能够看到公共假期列表。
在 Blazor 服务器应用程序中创建类型化 HttpClient 对象
创建和使用 HttpClient 对象的第三个选项是使用 Typed 客户端。这些客户端具有以下优点:
- 它们提供与命名客户端相同的功能,而无需使用字符串作为键。
- 它们在使用客户端时提供 IntelliSense 和编译器帮助。
- 它们提供了一个位置来配置特定的 HttpClient 并与之交互。 例如,我们可以配置特定于 Facebook API 特定端点的类型化HttpClient,并且该 HttpClient 可以封装使用该特定端点所需的所有逻辑。
- 它们与依赖注入 (DI)一起使用,并且可以在需要时注入。
要配置类型化的 HTTPClient,我们需要使用相同的 AddHttpClient 方法在 Startup.cs 文件中注册它,但这一次,我们需要将我们的服务名称 HolidaysApiService 作为类型传递。
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<IHolidaysApiService, HolidaysApiService>();
services.AddHttpClient<HolidaysApiService>();
}
在上面的代码片段中,HTTP 客户端和我们的服务 HolidaysApiService 都将注册为临时客户端和服务。这将允许我们在服务的构造函数中传递 HttpClient,如下面的代码片段所示。请注意,HttpClient 如何公开为服务的公共属性。
HolidaysApiService.cs
public class HolidaysApiService : IHolidaysApiService
{
public HttpClient Client { get; }
public HolidaysApiService(HttpClient client)
{
client.BaseAddress = new Uri("https://date.nager.at/");
client.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
Client = client;
}
public async Task<List<HolidayResponseModel>> GetHolidays(HolidayRequestModel holidaysRequest)
{
var result = new List<HolidayResponseModel>();
var url = string.Format("api/v2/PublicHolidays/{0}/{1}",
holidaysRequest.Year, holidaysRequest.CountryCode);
var response = await Client.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var stringResponse = await response.Content.ReadAsStringAsync();
result = JsonSerializer.Deserialize<List<HolidayResponseModel>>(stringResponse,
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
}
else
{
result = Array.Empty<HolidayResponseModel>().ToList();
}
return result;
}
}
类型化客户端的配置可以在注册期间在 Startup.cs 文件的 ConfigureServices 方法中指定,而不是在类型化客户端的构造函数中
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddHttpClient<IHolidaysApiService, HolidaysApiService>(c =>
{
c.BaseAddress = new Uri("https://date.nager.at/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
});
}
如果您正在使用此技术,则无需单独注册您的服务。您可以从 ConfigureServices 方法中删除以下行。
services.AddSingleton<IHolidaysApiService, HolidaysApiService>();
HttpClient 对象可以封装在类型化客户端中,而不是作为公共属性公开。然后我们可以在任何服务方法中内部使用这个客户端。
public class HolidaysApiService : IHolidaysApiService
{
private readonly HttpClient _httpClient;
public HolidaysApiService(HttpClient client)
{
_httpClient = client;
}
public async Task<List<HolidayResponseModel>> GetHolidays(HolidayRequestModel holidaysRequest)
{
var result = new List<HolidayResponseModel>();
var url = string.Format("api/v2/PublicHolidays/{0}/{1}",
holidaysRequest.Year, holidaysRequest.CountryCode);
var response = await _httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
var stringResponse = await response.Content.ReadAsStringAsync();
result = JsonSerializer.Deserialize<List<HolidayResponseModel>>(stringResponse,
new JsonSerializerOptions() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
}
else
{
result = Array.Empty<HolidayResponseModel>().ToList();
}
return result;
}
}
再次运行应用程序并提供国家代码和年份值,您应该能够看到公共假期列表。
总结概括
在本教程中,我介绍了在 Blazor 服务器应用程序中创建和使用 HTTP 客户端的不同技术。此处提到的大多数技术也可用于 ASP.NET Core 应用程序,因为 Blazor 服务器应用程序构建在 ASP.NET Core 基础结构之上。