如何使用Task<IResult>将HTTP请求封装为Success或Failure结果
实现方案:基于泛型Result类的API返回结果封装
核心思路是用可区分联合(Discriminated Union)风格的泛型Result类型封装成功、失败两种状态,避免空值返回和非预期异常泄露,完美适配HTTP调用的故障处理需求。
1 定义基础Result类型
首先实现泛型和非泛型两类Result结构,覆盖带返回数据、仅需操作状态两种业务场景:
// 泛型Result:带返回数据的场景 public abstract class Result<T> { public bool IsSuccess { get; protected set; } public T? Data { get; protected set; } public string? ErrorMessage { get; protected set; } public Exception? InnerException { get; protected set; } // 快速构建成功结果 public static Result<T> Success(T data) => new SuccessResult<T>(data); // 快速构建失败结果 public static Result<T> Failure(string errorMessage, Exception? ex = null) => new FailureResult<T>(errorMessage, ex); } public class SuccessResult<T> : Result<T> { public SuccessResult(T data) { IsSuccess = true; Data = data; ErrorMessage = null; InnerException = null; } } public class FailureResult<T> : Result<T> { public FailureResult(string errorMessage, Exception? ex = null) { IsSuccess = false; Data = default; ErrorMessage = errorMessage; InnerException = ex; } } // 非泛型Result:不需要返回数据、仅需判断操作是否成功的场景 public abstract class Result { public bool IsSuccess { get; protected set; } public string? ErrorMessage { get; protected set; } public Exception? InnerException { get; protected set; } public static Result Success() => new SuccessResult(); public static Result Failure(string errorMessage, Exception? ex = null) => new FailureResult(errorMessage, ex); } public class SuccessResult : Result { public SuccessResult() { IsSuccess = true; ErrorMessage = null; InnerException = null; } } public class FailureResult : Result { public FailureResult(string errorMessage, Exception? ex = null) { IsSuccess = false; ErrorMessage = errorMessage; InnerException = ex; } }
2 封装HTTP调用逻辑,返回Task<Result>
给HttpClient写扩展方法,自动处理状态码校验、响应序列化、全链路异常捕获,直接返回封装好的Result类型:
public static async Task<Result<T>> SendRequestAsync<T>(this HttpClient client, HttpRequestMessage request, CancellationToken cancellationToken = default) { try { using var response = await client.SendAsync(request, cancellationToken); // 处理HTTP错误状态码 if (!response.IsSuccessStatusCode) { var errorContent = await response.Content.ReadAsStringAsync(cancellationToken); return Result<T>.Failure($"HTTP请求失败,状态码:{(int)response.StatusCode},错误详情:{errorContent}"); } // 序列化返回结果 var content = await response.Content.ReadAsStringAsync(cancellationToken); var data = System.Text.Json.JsonSerializer.Deserialize<T>(content); if (data == null) { return Result<T>.Failure("返回结果序列化失败,数据为空"); } return Result<T>.Success(data); } catch (HttpRequestException ex) { return Result<T>.Failure($"HTTP网络异常:{ex.Message}", ex); } catch (TaskCanceledException ex) { return Result<T>.Failure("请求超时或被手动取消", ex); } catch (System.Text.Json.JsonException ex) { return Result<T>.Failure($"返回结果序列化异常:{ex.Message}", ex); } catch (Exception ex) { return Result<T>.Failure($"请求发生未知异常:{ex.Message}", ex); } }
3 调用示例
// 构造HTTP请求 var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/user/info"); var httpClient = new HttpClient(); // 调用封装后的请求方法 var result = await httpClient.SendRequestAsync<UserInfoModel>(request); // 分支处理逻辑 if (result.IsSuccess) { // 正常业务逻辑,直接使用result.Data Console.WriteLine($"用户昵称:{result.Data.NickName}"); } else { // 故障处理逻辑,可直接取错误信息和原始异常打日志、返回前端等 _logger.LogError(result.InnerException, "获取用户信息失败:{ErrorMsg}", result.ErrorMessage); }
可选优化点
- 给Result类添加
Match方法,强制调用方必须处理成功、失败两种分支,避免遗漏状态判断:public async Task<TOut> Match<TOut>(Func<T, Task<TOut>> onSuccess, Func<string, Exception?, Task<TOut>> onFailure) { return IsSuccess ? await onSuccess(Data!) : await onFailure(ErrorMessage!, InnerException); } - 可在FailureResult中扩展错误码字段,适配业务错误分类、多语言错误提示的需求
- 可添加隐式类型转换,省略手动调用Success/Failure方法的冗余代码
内容的提问来源于stack exchange,提问作者Just J
相关产品推荐
相关产品推荐

