实战ASP.NET Core使用Refit调用API示例,Asp.Net简化与 API 的集成可减少样板代码并使应用程序更加模块化,从而减少耦合并产生更清晰、更简洁的代码。了解如何在此过程中使用 Refit 以及处理 API 之间请求的最佳资源。
现代应用程序使用应用程序编程接口 (API) 来接收和发送数据或命令。但是,创建 HTTP 客户端来使用这些 API 可能非常耗时且容易出错。幸运的是,一些工具使这个过程变得更简单、更高效。这些工具之一就是 Refit,这是一个功能强大的库,有助于与 ASP.NET Core 应用程序中的 API 集成。
这篇文章将演示实战ASP.NET Core使用Refit调用API示例。我们将介绍从初始配置到创建 API 客户端和进行 HTTP 调用的所有内容。此外,我们将了解如何使用 JWT 实现简单的身份验证系统以及如何配置 Refit 以接收身份验证令牌。在文章的最后,您将看到 Refit 如何显著简化与 API 的交互,使您的代码更简洁、更易于维护。
API之间的请求是如何发出的?
在 Web 应用程序中,要请求外部 API,通常需要实现 API 客户端。软件开发中的 API 客户端是指负责发出请求并与外部 API 交互的应用程序组件或层。
API 客户端封装了向 API 端点发送 HTTP 请求、处理收到的响应以及在 JSON 等格式之间序列化/反序列化数据所需的逻辑。
默认情况下,ASP.NET CoreHttpClient
在System.Net.Http
命名空间中具有用于发送 HTTP 请求和接收请求-响应的类。
尽管 HttpClient 是用于在 ASP.NET Core 应用程序中发出 HTTP 请求的强大类,但必须考虑一些缺点。例如,需要手动配置和管理 HttpClient 实例。此外,可能需要管理超时、标头和处理程序,这会导致更大的复杂性,并且如果管理不当可能会导致错误。
HttpClient 的另一个不太有利的因素是,与其他资源(例如第三方库)相比,它的抽象性较低且复杂性较大,第三方库可以通过类型化接口提供高级抽象,而 HttpClient 需要更多的样板代码来配置和发送请求。
在应用程序多次调用外部 API 的情况下,过度使用 HttpClient 可能会导致代码更加冗长且可读性更差。
Refit:HttpClient 的良好替代品
在需要多次调用外部 API 的情况下,HttpClient 的一个不错的替代方案是Refit。 Refit 是 ASP.NET Core 的一个库,它提供了一种更简化、更优雅、更高效的方法来集成 HTTP API。
Refit 允许您定义简单、强类型的 C# 接口来描述 API 端点,从而无需手动编写样板代码来构造 URL、配置标头、序列化/反序列化 JSON 对象和其他低级细节。Refit 封装了所有这些标准设置,允许在必要时进行自定义。
此外,Refit 使用属性来配置 HTTP 请求的详细信息,例如身份验证和缓存。这提供了更具声明性和简化的请求配置,使代码更加直观。Refit 的另一个优点是它简化了基于 API 返回的 HTTP 状态代码的错误处理。
ASP.NET Core使用Refit调用API
在这篇文章中,我们将创建 Refit 的基本配置,并在向外部 API 添加身份验证层后,为此,我们将创建两个 API。第一个 (CustomerAPI) 将请求第二个 (CustomerAdditionalInfoApi)。此请求将通过 Refit 发出,我们将在其中探索一些细节并了解如何促进与外部 API 的集成。
完整的项目代码可以在 GitHub 上的这个存储库中访问:客户管理器源代码。
创建应用程序
帖子中的示例使用.NET 版本 8,您可以在旧版本中创建示例应用程序,但可能会出现帖子中未涉及的问题。
要创建解决方案和两个 API 项目,您可以使用以下命令:
dotnet new sln -n CustomerManagement dotnet new web -n CustomerApi dotnet new web -n CustomerAdditionalInfoApi dotnet sln add CustomerApi/CustomerApi.csproj dotnet sln add CustomerAdditionalInfoApi/CustomerAdditionalInfoApi.csproj
然后,您可以使用自己喜欢的 IDE 打开解决方案项目。本文使用 Visual Studio。
我们将要处理的第一个 API 是 CustomerApi。在项目中打开一个终端并运行以下命令来安装必要的 NuGet 包:
dotnet add package Refit dotnet add package Refit.HttpClientFactory
然后,在项目内部创建一个名为“Models”的新文件夹,并在其中创建以下类:
namespace CustomerApi.Models; public class Customer { public string Id { get; set; } public string Name { get; set; } public string Email { get; set; } }
下一步是创建一个数据传输对象 (DTO),以返回 API 请求的数据以获取更多信息。创建一个名为“Dtos”的新文件夹,并在其中添加以下类:
- 客户附加信息
namespace CustomerApi.Dtos; public class CustomerAdditionalInfoDto { public string Id { get; set; } public string CustomerId { get; set; } public string Address { get; set; } public string PhoneNumber { get; set; } }
现在让我们创建 Refit 将用于实现外部 API 端点的接口。因此,在 CustomerAPI 项目中,创建一个名为“Repositories”的新文件夹。在其中,创建另一个名为“Interfaces”的文件夹,并在其中添加以下接口:
using CustomerApi.Dtos; using Refit; namespace CustomerApi.Repositories.Interfaces; public interface ICustomerAdditionalInfoApi { [Get("/customerAdditionalInfos/{customerId}")] Task<CustomerAdditionalInfoDto> GetCustomerAdditionalInfo(string customerId); }
请注意,在此接口中声明了一个方法,使用 id 作为参数从客户那里获取其他数据。此外,还[Get("/customerAdditionalInfos/{customerId}")]
使用了 Refit 的属性,该属性声明 HTTP 方法是 GET,并且还声明了要访问的路由:"/customerAdditionalInfos/{customerId}"
。
现在让我们在项目的 Program 类中配置 Refit。为此,只需在创建 builder 变量的位置下方添加以下代码:
builder.Services .AddRefitClient<ICustomerAdditionalInfoApi>() .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://localhost:5080"));
C#
这里我们告知 Refit 应使用哪个接口来创建访问外部 API 的抽象。此外,我们将统一资源标识符 (Uri) 传递为“http://localhost:5080”,这是将运行外部 API 的 http 地址和端口。
为了简单起见,在这个例子中,我们直接在方法中配置 Uri ConfigureHttpClient()
,但在实际场景中,这些信息必须存储在安全的位置,例如受加密保护的云存储主机。
现在让我们添加与附加信息 API 通信的端点:
app.MapGet("/customers/{id}/additionalInfo", async (string id, ICustomerAdditionalInfoApi additionalInfoApi) => { try { var info = await additionalInfoApi.GetCustomerAdditionalInfo(id); return Results.Ok(info); } catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.NotFound) { return Results.NotFound($"Additional information not found for the customer id: {id}"); } });
在此端点中,我们使用实现 Refit 的接口来访问外部 API 并从客户那里获取其他信息。如果找到数据,则返回数据;如果没有找到,则返回 404 NotFound 状态,并返回错误,表明未找到该客户的其他信息。
请注意,通过 Refit 请求 API 非常简单,您可以根据需要添加任意数量的 API 和端点。并且可以通过 HTTP 状态代码处理 API 返回。
第一个 API 已经准备好了,现在让我们实现第二个 API,第一个 API 将通过 Refit 访问它。在项目内部CustomerAdditionalInfoApi
,创建一个名为“Models”的新文件夹,并在其中创建以下类:
- 客户附加信息
public string Id { get; set; } public string CustomerId { get; set; } public string Address { get; set; } public string PhoneNumber { get; set; }
并在 Program 类中添加以下代码:
app.MapGet("/customerAdditionalInfos/{customerId}", async (string customerId) => { var customerAdditionalInfo = GetCustomerAdditionalInfos().FirstOrDefault(a => a.CustomerId == customerId); return customerAdditionalInfo is CustomerAdditionalInfo info ? Results.Ok(info) : Results.NotFound(); }); List<CustomerAdditionalInfo> GetCustomerAdditionalInfos() { return new List<CustomerAdditionalInfo> { new CustomerAdditionalInfo { Id = "6FE892BB", CustomerId = "35A51B05", Address = "200 John Wesley Blvd, Bossier City, Louisiana, USA", PhoneNumber = "555-1234" }, new CustomerAdditionalInfo { Id = "189DF59F", CustomerId = "4D8AD7B2", Address = "103100 Overseas Hwy, Key Florida, USA", PhoneNumber = "555-5678" }, new CustomerAdditionalInfo { Id = "A9374B16", CustomerId = "23D4FCC2", Address = "6175 Brandt Pike, Huber Heights, Ohio, USA", PhoneNumber = "555-8765" } }; }
请注意,这里我们没有使用数据库 – 我们只是创建一个简单的 API 来通过端点返回一些示例数据。
另一个必要的配置是定义在其他 API 中配置的端口。因此,在 Properties 文件夹中,在架构launchSettings.json
内的文件中将关键端口profiles
更改为。这样,应用程序就会从客户 API 中配置的此端口启动。applicationUrl
5080
现在让我们运行这两个应用程序并验证 Refit 是否正常工作。在 Visual Studio 中,右键单击解决方案,然后选择选项properties
。在打开的窗口中,选择两者的Multiple startup projects
选项Start
。
然后,运行应用程序。
这篇文章使用 Progress Telerik Fiddler Everywhere进行请求。因此,在 Fiddler 中向端点执行请求http://localhost:PORT/customers/additionalInfo/4D8AD7B2
。在这里,我们向客户 API 发出请求,并在 URL 中传递客户 ID,这会向第二个 API(CustomerAdditionalInfoAPI)发出请求并返回客户数据。
现在,再次执行请求,但使用不存在的 ID(例如),1A2AD1B2
响应将是状态 HTTP 未找到:
请注意,使用 Refit 我们能够预测请求的响应并根据需要进行处理。在本例中,当返回 HTTP 状态 404 时,将执行异常并返回自定义错误消息。
向 Refit 添加 JWT 身份验证
JSON Web Token (JWT) 是用于 Web API 中的身份验证/授权的标准,它使用通过私钥或公钥/私钥签名的令牌。
在前面的例子中,我们访问了外部 API,但它没有任何身份验证/授权规则。接下来,我们将检查如何在使用 Refit 访问外部 API 时实现这一点。
首先,让我们配置 CustomerAPI 以将令牌发送到 CustomerAdditionalInfoApi。因此,在文件夹 Interfaces 中添加以下接口:
- 身份验证令牌提供者
namespace CustomerApi.Repositories.Interfaces; public interface IAuthTokenProvider { string GetToken(); }
然后在 Repositories 文件夹中添加以下类:
using CustomerApi.Repositories.Interfaces; namespace CustomerApi.Repositories; public class AuthTokenProvider : IAuthTokenProvider { public string GetToken() { return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.pg-7kh_cZ0m4yDULVjR7rKPZFh7nBprKGFVYzS7Y7y0"; } }
请注意,这里我们定义了一个接口,其中包含一个用于获取令牌的方法。然后我们创建一个实现该GetToken()
方法并返回 JWT 的类。此令牌是在 JWT 网站上生成的,并在代码中固定。
请记住,这只是一个示例 – 在实际应用中,必须通过安全端点获取此令牌才能生成令牌。 有关该主题的更多信息,您可以查看这篇深入介绍使用 JWT 进行身份验证和授权的文章:使用 JWT 进行身份验证和授权。
然后,在 Program 类中,在 builder 变量下方添加以下代码:
builder.Services.AddSingleton<IAuthTokenProvider, AuthTokenProvider>();
并替换代码:
builder.Services .AddRefitClient<ICustomerAdditionalInfoApi>() .ConfigureHttpClient(c => c.BaseAddress = new Uri("http://localhost:5080"));
内容如下:
builder.Services .AddRefitClient<ICustomerAdditionalInfoApi>() .ConfigureHttpClient((serviceProvider, c) => { var tokenProvider = serviceProvider.GetRequiredService<IAuthTokenProvider>(); var token = tokenProvider.GetToken(); c.BaseAddress = new Uri("http://localhost:5080"); c.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); });
请注意,这里我们获取令牌并通过请求标头将其传递给 Refit,该令牌为“Bearer”类型。
然后,以下是代码片段:
catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized) { return Results.Unauthorized(); }
添加以下内容:
catch (ApiException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized) { return Results.Unauthorized(); }
这里我们处理外部 API 的返回,从现在开始可以选择返回 HTTP 未授权状态。
第一个 API 中的 JWT 实现已准备就绪,现在让我们在 CustomerAdditionalInfoApi API 中进行配置。因此,首先,在 CustomerAdditionalInfoApi API 中打开一个终端并运行以下命令来安装 JWT NuGet 包依赖项:
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
然后,在 Program 类中创建“builder”变量的地方添加以下代码:
builder.Services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(options => { options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateLifetime = false, ValidateIssuerSigningKey = true, IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("MIICWwIBAAKBgHZO8IQouqjDyY47ZDGdw9j")) }; }); builder.Services.AddAuthorization();
这里我们配置 JWT 要求。请注意,大多数要求都标记为 false,因为如前所述,这只是一个教学示例。在实际应用中,必须小心处理这些信息。此外,我们直接配置密钥 (MIICWwIBAAKBgHZO8IQouqjDyY47ZDGdw9j)。此密钥用于创建在客户 API 中配置的 JWT 令牌。
仍然在程序类中,在app
变量下方添加以下内容:
app.UseAuthentication(); app.UseAuthorization();
这些方法用于告诉编译器使用身份验证和授权。
最后,为了让端点考虑 JWT 授权规则,需要添加RequireAuthorization()
扩展方法。然后完整的端点将如下所示:
app.MapGet("/customerAdditionalInfos/{customerId}", async (string customerId) => { var customerAdditionalInfo = GetCustomerAdditionalInfos().FirstOrDefault(a => a.CustomerId == customerId); return customerAdditionalInfo is CustomerAdditionalInfo info ? Results.Ok(info) : Results.NotFound(); }); }).RequireAuthorization();
通过 Refit 测试对经过身份验证的端点的访问
现在所有身份验证和授权配置都已准备就绪,我们可以运行应用程序并检查端点是否继续工作。然后启动两个 API 并再次通过 Fiddler 执行请求。
请注意,由于 API 已通过身份验证,因此数据再次成功返回。要测试授权错误,请修改 CustomerAdditionalInfoApi API 的 Program 类中的密钥 (MIICWwIBAAKBgHZO8IQouqjDyY47ZDGdw9j),然后再次发出请求。
这次附加信息 API 无法识别请求中发送的密钥,并返回了 HTTP 状态代码 401(未授权)。
结论
在这篇文章中,我们研究了 ASP.NET Core 原生 HttpClient 类的一些缺点,尽管它满足了 API 之间通信的主要需求,但在管理许多对外部 API 的请求时可能会成为问题。
相比之下,我们探索了 Refit 的功能,它是 HttpClient 的一个很好的替代方案,允许开发人员创建更少的样板代码并简化 API 之间的请求过程。此外,我们还检查了如何实现一个简单的身份验证系统并通过 Refit 将令牌发送到外部 API。
Refit 是处理 Web API 的绝佳工具,因此,无论何时您有需要,都可以考虑使用 Refit 来管理外部调用并确保应用程序的成功。