如何让Refit将自定义泛型响应反序列化为父类HttpResponseMessage?
解决Refit中自定义泛型响应并获取ProblemDetails的方案
方案1:直接利用Refit内置ApiResponse<T>的RawResponse属性
你之前可能忽略了ApiResponse<T>自带的RawResponse属性,它就是原始的HttpResponseMessage,可以直接用它读取失败响应中的ProblemDetails,不需要更换返回类型。
示例代码:
// 定义Refit接口 public interface IMyApiClient { [Get("/api/some-resource")] Task<ApiResponse<SomeResponseDTO>> GetSomeResourceAsync(); } // 调用处理 var response = await apiClient.GetSomeResourceAsync(); if (!response.IsSuccessStatusCode) { // 从原始响应中读取ProblemDetails var problemDetails = await response.RawResponse.Content.ReadFromJsonAsync<ProblemDetails>(); // 处理错误逻辑 } else { var data = response.Content; // 处理成功数据 }
方案2:自定义泛型响应类型+自定义ResponseBuilder
如果一定要用自定义的泛型响应类型,不要继承HttpResponseMessage,而是包装它,再通过Refit的IResponseBuilder接口实现响应的构建逻辑。
步骤1:定义自定义响应类型
public class CustomApiResponse<T> { public T? Content { get; } public HttpResponseMessage RawResponse { get; } public bool IsSuccess => RawResponse.IsSuccessStatusCode; public CustomApiResponse(HttpResponseMessage rawResponse, T? content = default) { RawResponse = rawResponse; Content = content; } // 封装读取ProblemDetails的方法,简化调用 public async Task<ProblemDetails?> GetProblemDetailsAsync() { if (IsSuccess) return null; return await RawResponse.Content.ReadFromJsonAsync<ProblemDetails>(); } }
步骤2:实现自定义ResponseBuilder
public class CustomResponseBuilder : IResponseBuilder { public async Task<object> BuildResponseAsync(HttpResponseMessage response, Type returnType, CancellationToken cancellationToken) { // 判断是否是我们的CustomApiResponse<T>类型 if (returnType.IsGenericType && returnType.GetGenericTypeDefinition() == typeof(CustomApiResponse<>)) { var content = default(object); if (response.IsSuccessStatusCode) { var targetContentType = returnType.GetGenericArguments()[0]; content = await response.Content.ReadFromJsonAsync(targetContentType, cancellationToken: cancellationToken); } // 反射创建CustomApiResponse实例 var constructor = returnType.GetConstructor(new[] { typeof(HttpResponseMessage), returnType.GetGenericArguments()[0] }); return constructor!.Invoke(new[] { response, content }); } // 其他类型交给默认处理逻辑 return await DefaultResponseBuilder.Instance.BuildResponseAsync(response, returnType, cancellationToken); } }
步骤3:注册Refit时指定自定义ResponseBuilder
services.AddRefitClient<IMyApiClient>(new RefitSettings { ResponseBuilder = new CustomResponseBuilder() });
调用示例
public interface IMyApiClient { [Get("/api/some-resource")] Task<CustomApiResponse<SomeResponseDTO>> GetSomeResourceAsync(); } // 调用处理 var response = await apiClient.GetSomeResourceAsync(); if (!response.IsSuccess) { var problemDetails = await response.GetProblemDetailsAsync(); // 处理错误 } else { var data = response.Content; // 处理数据 }
方案3:给HttpResponseMessage添加扩展方法简化处理
如果不想修改接口返回类型,仍用HttpResponseMessage,可以封装扩展方法来简化泛型转换和ProblemDetails读取:
public static class HttpResponseMessageExtensions { public static async Task<(T? Content, ProblemDetails? Problem)> ParseApiResponseAsync<T>(this HttpResponseMessage response) { if (response.IsSuccessStatusCode) { var content = await response.Content.ReadFromJsonAsync<T>(); return (content, null); } else { var problem = await response.Content.ReadFromJsonAsync<ProblemDetails>(); return (default, problem); } } }
调用示例:
public interface IMyApiClient { [Get("/api/some-resource")] Task<HttpResponseMessage> GetSomeResourceAsync(); } // 调用处理 var response = await apiClient.GetSomeResourceAsync(); var (data, problem) = await response.ParseApiResponseAsync<SomeResponseDTO>(); if (problem != null) { // 处理错误 } else { // 处理数据 }
为什么子类化HttpResponseMessage不可行
Refit会把接口方法的返回类型当作反序列化目标类型,而HttpResponseMessage本身是包含HTTP响应元数据的类,无法被JSON反序列化。所以当你返回继承自它的CustomAPIResponse<SomeResponseDTO>时,Refit会尝试把响应体JSON反序列化为这个自定义类型,自然会抛出异常。正确思路是包装而非继承HttpResponseMessage,或者利用内置ApiResponse的现有属性。
内容的提问来源于stack exchange,提问作者Martin
相关产品推荐
相关产品推荐

