跳到主要内容

6.3 调用 API 与身份认证

学习目标​

你将把 HTTP 传输细节留在服务层,把用户提示留在 ViewModel,并能区别未登录、无权限、业务校验失败和网络故障。前提:实战 API 监听 http://localhost:5080,DTO 为 TodoDto(int Id, string Title, bool IsCompleted);客户端采用 上一节 的命名 HTTP 客户端注册。

桌面应用是公共客户端​

熟悉 ASP.NET Core 的你仍要在服务端验证身份、授权和请求数据。按钮禁用与客户端输入校验只改善体验,用户可以修改客户端或直接发送请求。桌面安装包无法保密内置值,不能放入服务端签名密钥、数据库凭据或需要保密的 OAuth client secret。

企业登录通常使用身份提供方支持的授权码流程与 PKCE,通过系统浏览器或官方登录库完成认证。访问令牌用于请求;刷新令牌与缓存按身份库和操作系统保护机制保存,不写入明文 JSON。教学 API 的匿名访问不代表生产认证方案。

完整的读取服务​

下列是独立演示的完整 TodoApiClient.cs。前提:TaskDesk.Client.Models 已有上述 DTO,且注册名为 TaskDeskApi 的客户端。参数演示请求携带令牌;匿名 API 可传 null,服务本身不实现登录。实战使用 TodoService 封装四种操作。

TodoApiClient.cs
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Threading;
using System.Threading.Tasks;
using TaskDesk.Client.Models;

namespace TaskDesk.Client;

public sealed class LoginRequiredException : Exception
{
public LoginRequiredException() : base("登录已失效,请重新登录。") { }
}

public sealed class TodoApiClient
{
private readonly IHttpClientFactory _clients;

public TodoApiClient(IHttpClientFactory clients) => _clients = clients;

public async Task<TodoDto[]> GetAllAsync(
string? accessToken, CancellationToken cancellationToken)
{
using var client = _clients.CreateClient("TaskDeskApi");
using var request = new HttpRequestMessage(HttpMethod.Get, "api/todos");
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
timeout.CancelAfter(TimeSpan.FromSeconds(20));
if (!string.IsNullOrWhiteSpace(accessToken))
request.Headers.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);

using var response = await client.SendAsync(request,
HttpCompletionOption.ResponseHeadersRead,
timeout.Token).ConfigureAwait(false);

if (response.StatusCode == HttpStatusCode.Unauthorized)
throw new LoginRequiredException();

response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<TodoDto[]>(
cancellationToken: timeout.Token).ConfigureAwait(false)
?? Array.Empty<TodoDto>();
}
}

ViewModel await 服务后更新界面,捕获 LoginRequiredException 时进入登录流程。若身份库支持续期,仍失败时应重新登录,不能无限重试 401。按请求设置授权头,避免并发修改共享 Authorization。这里用关联令牌限制整个操作;ResponseHeadersRead 模式下,客户端 Timeout 本身不覆盖后续响应体读取。

契约与失败分类​

接口客户端操作与注意点
GET api/todos读取任务数组,支持取消
POST api/todos提交标题,使用服务端返回的编号与状态
PUT api/todos/{id}提交接口约定的更新数据,成功后同步结果
DELETE api/todos/{id}成功后移除本地项,再处理不存在等状态

按 实战契约 定义 JSON,不能仅凭 DTO 猜测写接口的请求体。响应及时释放,所有请求传递令牌。本机 HTTP 用于联调,生产使用 HTTPS;本地 HTTPS 故障应检查证书与地址,不能关闭证书验证。

401 表示认证不足或失效,通常需要更新会话或重新登录;403 表示已识别身份但没有权限,重复登录未必解决。400 类校验错误应给出字段或业务提示,404 可能是数据已被其他用户删除,网络失败和 5xx 则保留重试入口。展示经过整理的提示并记录技术详情,不把完整响应、访问令牌或堆栈直接显示给用户。

本例其他失败使用 EnsureSuccessStatusCode;生产可解析约定的 Problem Details。写操作超时后可能已提交,盲目重试 POST 会新增重复记录,应采用幂等策略或重新查询。

常见错误​

每次 new HttpClient() 可能破坏连接复用;工厂客户端可按调用释放,底层处理器由工厂管理。HTTP 无需 Task.Run;读取响应也要传令牌。BaseAddress 末尾斜杠会影响 URI 组合,统一使用带斜杠的基地址和不以斜杠开头的相对路径。

练习与验收​

分别测试 API 正常、服务停止、令牌失效和无权限四种情况;匿名教学 API 的后两种需使用受保护的测试服务或模拟响应。验收:失败后可继续操作;401 引导重新登录、403 显示权限提示;取消不会误报成功;日志中没有令牌。新增请求超时后,先查询列表确认服务端状态。

官方资料​