代码示例

常见场景的完整可用示例

基本 GET 请求

使用类型安全的错误处理从 REST API 获取数据。

using System.Net.Http.Json;
using RestClient.Net;
using Urls;

// 定义模型
record Post(int UserId, int Id, string Title, string Body);
record ApiError(string Message);

// 类型别名,用于简洁的模式匹配
using OkPost = Outcome.Result<Post, Outcome.HttpError<ApiError>>
    .Ok<Post, Outcome.HttpError<ApiError>>;
using ErrorPost = Outcome.Result<Post, Outcome.HttpError<ApiError>>
    .Error<Post, Outcome.HttpError<ApiError>>;
using ResponseErrorPost = Outcome.HttpError<ApiError>.ErrorResponseError;
using ExceptionErrorPost = Outcome.HttpError<ApiError>.ExceptionError;

// 发起请求
using var httpClient = new HttpClient();
var result = await httpClient.GetAsync(
    url: "https://jsonplaceholder.typicode.com/posts/1".ToAbsoluteUrl(),
    deserializeSuccess: async (content, ct) =>
        await content.ReadFromJsonAsync<Post>(ct) ?? throw new Exception("Null"),
    deserializeError: async (content, ct) =>
        await content.ReadFromJsonAsync<ApiError>(ct) ?? new ApiError("Unknown")
);

// 处理所有情况
var message = result switch
{
    OkPost(var post) => $"标题: {post.Title}",
    ErrorPost(ResponseErrorPost(var err, var status, _)) => $"错误 {status}: {err.Message}",
    ErrorPost(ExceptionErrorPost(var ex)) => $"异常: {ex.Message}",
};

Console.WriteLine(message);

带请求体的 POST 请求

使用 JSON 请求体创建新资源。

record CreatePostRequest(string Title, string Body, int UserId);

var newPost = new CreatePostRequest("我的标题", "我的内容", 1);

var result = await httpClient.PostAsync(
    url: "https://jsonplaceholder.typicode.com/posts".ToAbsoluteUrl(),
    body: newPost,
    serializeRequest: body => JsonContent.Create(body),
    deserializeSuccess: async (content, ct) =>
        await content.ReadFromJsonAsync<Post>(ct) ?? throw new Exception("Null"),
    deserializeError: async (content, ct) =>
        await content.ReadFromJsonAsync<ApiError>(ct) ?? new ApiError("Unknown")
);

var message = result switch
{
    OkPost(var post) => $"创建的文章 ID: {post.Id}",
    ErrorPost(ResponseErrorPost(var err, var status, _)) => $"失败: {status}",
    ErrorPost(ExceptionErrorPost(var ex)) => $"异常: {ex.Message}",
};

使用 IHttpClientFactory

在 ASP.NET Core 应用程序中正确使用 HttpClient。

// Program.cs - 注册客户端
builder.Services.AddHttpClient("api", client =>
{
    client.BaseAddress = new Uri("https://api.example.com");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
    client.Timeout = TimeSpan.FromSeconds(30);
});

// UserService.cs - 使用客户端
public class UserService(IHttpClientFactory httpClientFactory)
{
    public async Task<Result<User, HttpError<ApiError>>> GetUserAsync(
        string userId,
        CancellationToken ct = default)
    {
        var client = httpClientFactory.CreateClient("api");

        return await client.GetAsync(
            url: $"/users/{userId}".ToAbsoluteUrl(),
            deserializeSuccess: Deserializers.Json<User>,
            deserializeError: Deserializers.Error,
            cancellationToken: ct
        );
    }
}

使用 Polly 的重试策略

为瞬态故障添加自动重试。

// Program.cs
builder.Services.AddHttpClient("api")
    .AddStandardResilienceHandler(options =>
    {
        options.Retry.MaxRetryAttempts = 3;
        options.Retry.Delay = TimeSpan.FromMilliseconds(500);
        options.Retry.UseJitter = true;
        options.Retry.ShouldHandle = args => ValueTask.FromResult(
            args.Outcome.Exception is not null ||
            args.Outcome.Result?.StatusCode >= HttpStatusCode.InternalServerError
        );
    });

认证处理器

自动为请求添加认证令牌。

public class AuthenticationHandler(ITokenService tokenService) : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        var token = await tokenService.GetAccessTokenAsync(cancellationToken);

        request.Headers.Authorization =
            new AuthenticationHeaderValue("Bearer", token);

        var response = await base.SendAsync(request, cancellationToken);

        // 如果令牌过期则刷新
        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            token = await tokenService.RefreshTokenAsync(cancellationToken);
            request.Headers.Authorization =
                new AuthenticationHeaderValue("Bearer", token);
            response = await base.SendAsync(request, cancellationToken);
        }

        return response;
    }
}

// Program.cs
builder.Services.AddTransient<AuthenticationHandler>();
builder.Services.AddHttpClient("api")
    .AddHttpMessageHandler<AuthenticationHandler>();

状态码特定处理

针对不同的 HTTP 状态码进行不同处理。

var result = await httpClient.GetUserAsync(userId);

var message = result switch
{
    OkUser(var user) => $"找到: {user.Name}",

    // 未找到 - 用户不存在
    ErrorUser(ResponseErrorUser(_, HttpStatusCode.NotFound, _)) =>
        "用户未找到。请检查 ID。",

    // 未授权 - 需要登录
    ErrorUser(ResponseErrorUser(_, HttpStatusCode.Unauthorized, _)) =>
        "请登录以查看此用户。",

    // 禁止访问 - 无权限
    ErrorUser(ResponseErrorUser(_, HttpStatusCode.Forbidden, _)) =>
        "您没有权限查看此用户。",

    // 请求过多
    ErrorUser(ResponseErrorUser(_, HttpStatusCode.TooManyRequests, var response)) =>
    {
        var retryAfter = response.Headers.RetryAfter?.Delta;
        return $"请求过多。请在 {retryAfter?.TotalSeconds ?? 60} 秒后重试。";
    },

    // 服务器错误
    ErrorUser(ResponseErrorUser(var err, var status, _)) when (int)status >= 500 =>
        "服务器出现问题。请稍后重试。",

    // 其他 API 错误
    ErrorUser(ResponseErrorUser(var err, var status, _)) =>
        $"API 错误 {(int)status}: {err.Message}",

    // 网络/超时错误
    ErrorUser(ExceptionErrorUser(TaskCanceledException ex))
        when ex.CancellationToken.IsCancellationRequested =>
        "请求已取消。",

    ErrorUser(ExceptionErrorUser(TaskCanceledException)) =>
        "请求超时。请重试。",

    ErrorUser(ExceptionErrorUser(HttpRequestException)) =>
        "网络错误。请检查您的连接。",

    ErrorUser(ExceptionErrorUser(var ex)) =>
        $"意外错误: {ex.Message}",
};

链式多个请求

链接依赖的 API 调用并正确传播错误。

// 获取用户,然后获取他们的订单,再获取订单详情
public async Task<Result<OrderDetails, HttpError<ApiError>>> GetUserOrderDetailsAsync(
    string userId,
    CancellationToken ct)
{
    // 首先,获取用户
    var userResult = await httpClient.GetUserAsync(userId, ct);

    return await userResult switch
    {
        OkUser(var user) => await GetOrdersForUserAsync(user, ct),
        ErrorUser(var error) => new Result<OrderDetails, HttpError<ApiError>>
            .Error(error),
    };
}

private async Task<Result<OrderDetails, HttpError<ApiError>>> GetOrdersForUserAsync(
    User user,
    CancellationToken ct)
{
    var ordersResult = await httpClient.GetOrdersAsync(user.Id, ct);

    return ordersResult switch
    {
        OkOrders(var orders) => new Result<OrderDetails, HttpError<ApiError>>
            .Ok(new OrderDetails(user, orders)),
        ErrorOrders(var error) => new Result<OrderDetails, HttpError<ApiError>>
            .Error(error),
    };
}

并行请求

并行发起多个独立请求。

public async Task<Dashboard> GetDashboardAsync(string userId, CancellationToken ct)
{
    // 并行启动所有请求
    var userTask = httpClient.GetUserAsync(userId, ct);
    var ordersTask = httpClient.GetOrdersAsync(userId, ct);
    var notificationsTask = httpClient.GetNotificationsAsync(userId, ct);

    // 等待所有请求完成
    await Task.WhenAll(userTask, ordersTask, notificationsTask);

    var userResult = await userTask;
    var ordersResult = await ordersTask;
    var notificationsResult = await notificationsTask;

    // 合并结果
    return (userResult, ordersResult, notificationsResult) switch
    {
        (OkUser(var user), OkOrders(var orders), OkNotifications(var notifications)) =>
            new Dashboard(user, orders, notifications),

        (ErrorUser(var e), _, _) =>
            throw new Exception($"加载用户失败: {e}"),
        (_, ErrorOrders(var e), _) =>
            throw new Exception($"加载订单失败: {e}"),
        (_, _, ErrorNotifications(var e)) =>
            throw new Exception($"加载通知失败: {e}"),
    };
}