Blazor Server 应用程序中进行 HTTP 请求

翻译自 Waqas Anwar 2021年5月4日的文章 《Making HTTP Requests in Blazor Server Apps》 [1]

Blazor Server 应用使用标准的 ASP.NET Core 应用程序,在服务端执行 .NET 代码。在 Blazor Server 应用程序中,我们可以像在 ASP.NET Core Web 应用程序中那样,使用相同的方式访问任意 .NET 库或服务端功能。这其中的一项功能是,使用 HTTP Client 实例向第三方 Web API 发送 HTTP 请求。在本教程中,我将向您展示创建 HTTP Client 实例的不同方法。另外,我还会向您展示如何在 Blazor Server 应用程序中使用第三方 API 来获取和显示数据。

下载源码[2]

一、第三方 Web API 概览

我们将开发一个 Blazor Server 应用程序,该应用允许用户在 Blazor 页面组件上输入国家代码和年份,然后我们将调用第三方 API 以获取指定国家和年份的公共假期列表。我们使用的第三方 API 是Nager.Date[3],它是一个全球公共假期 API。

这是一个非常简单的 API,您可以轻松地在 Postman 中输入以下 URL 测试此 API。

https://date.nager.at/api/v2/PublicHolidays/2021/CN

该 API 的响应是 JSON 格式的公共假期列表,如下所示:

二、从 Blazor Sever 应用程序开始

在 Visual Studio 2019 中创建一个 Blazor Server 应用程序,并新建一个名为 Models 的文件夹。在 Models 文件夹中添加以下两个模型类,以映射上述 Holidays API 的请求和响应。

HolidayRequestModel.cs

public class HolidayRequestModel{    public string CountryCode { get; set; }    public int Year { get; set; }}
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; }}

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

三、在 Blazor Server 应用程序中使用 IHttpClientFactory 创建 HttpClient

在 Blazor Server 应用程序中使用 HttpClient 请求第三方 API 有多种不同的方式,让我们从一个基础的示例开始,在该示例中我们使用 IHttpClientFactory 创建 HttpClient 对象。

在项目中创建一个 Services 文件夹,并创建如下的 IHolidaysApiService 接口。该接口只有一个方法 GetHolidays,它以 HolidayRequestModel 作为参数并返回 HolidayResponseModel 对象的列表。

IHolidaysApiService.cs

public interface IHolidaysApiService{    Task<List<HolidayResponseModel>> GetHolidays(HolidayRequestModel holidaysRequest);}
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;    }}

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

四、在 Blazor Server 应用程序中创建命名 HttpClient 对象

上面的示例适用于您正在重构现有的应用程序,希望在不影响整个应用程序的情况下,在某些方法中使用 IHttpClientFactory 创建 HttpClient 对象的场景。如果您要创建一个全新的应用程序,或者您想要将创建 HttpClient 对象的方式集中化,那么您必须使用命名 HttpClient。

下面是创建命名 HTTP 客户端的好处:

  1. 我们可以为每个 HttpClient 命名,并在应用程序启动时指定与 HttpClient 相关的所有配置,而不是将配置分散在整个应用程序当中。
  2. 我们可以只配置一次命名的 HttpClient,并多次重用它调用一个特定 API 提供者的所有 API。
  3. 我们可以根据这些客户端在应用程序不同区域的使用情况,配置多个不同配置的命名 HttpClient 对象。

我们可以在 Startup.cs 文件的 ConfigureServices 方法中,使用前面用过的名为 AddHttpClient 方法指定一个命名的 HttpClient。

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

配置了命名 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;    }}

另外,我们不需要在请求的 URL 中指定 API 主机名称,因为我们在 Startup.cs 文件中已经指定过基地址了。

再次运行应用程序并提供国家代码和年份值,您应该能看到以下公共假期列表。

五、在 Blazor Server 应用程序中创建类型化 HttpClient 对象

创建和使用 HttpClient 对象的第三种选择是使用类型化客户端。这种客户端具有以下好处:

  1. 它们提供与命名客户端同样的功能,但无需使用字符串作为键。
  2. 它们在使用客户端时提供智能感知和编译器帮助。
  3. 它们提供了一个单一的存储单元来配置特定的 HttpClient 并与之交互。例如,我们可以配置针对 Facebook API 的一个特定终端的一个类型化 HttpClient,而且该 HttpClient 可以封装使用该特定终端所需的所有逻辑。
  4. 它们与依赖注入 (DI) 一起使用,可以在需要的地方注入。

要配置类型化的 HTTPClient,我们需要在 Startup.cs 文件中使用相同的 AddHttpClient 方法注册它,但这一次,我们需要传递我们的服务名称 HolidaysApiService 作为它的类型。

Startup.cs

public void ConfigureServices(IServiceCollection services){    services.AddRazorPages();    services.AddServerSideBlazor();     services.AddSingleton<IHolidaysApiService, HolidaysApiService>();     services.AddHttpClient<HolidaysApiService>();}

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

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");    });}
services.AddSingleton<IHolidaysApiService, HolidaysApiService>();
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;    }}

更多相关文章

  1. Android核心分析(21)----Android应用框架之AndroidApplication
  2. 关于Android(安卓)Studio3.2新建项目Android(安卓)resource link
  3. Android(安卓)- Manifest 文件 详解
  4. Android之应用程序基础
  5. Android四大组件的理解
  6. Android官方入门文档[1]创建一个Android项目
  7. 第三章 Android程序设计基础
  8. Android(安卓)Studio 3.0开始android Device Monitor弃用
  9. 使用uiautomatorviewer和uiautomator来做android的UI测试

随机推荐

  1. Java多线程之Thread、Runnable、Callable
  2. BufferedImage到InputStream - 格式不同
  3. 20145122《 Java网络编程》实验五实验报
  4. java操作ftp实现文件的上传下载(适用于图
  5. ***100分,谁有用java mail做的把表单直接
  6. 急求用jersey2.x+spring3.x 开发rest web
  7. 从AWS Lambda发布到SNS时超时
  8. Finder3.0 - 集群支持即将发布
  9. Java中的TreeMap、Comparable、Comparato
  10. 如何获得嵌入式Jetty Web服务器来转储其J