C#封装HttpClient:HTTP请求处理最佳实践
在现代的.NET应用程序开发中,与外部服务进行HTTP通信是一项常见需求。HttpClient
作为.NET框架中处理HTTP请求的核心组件,为我们提供了强大而灵活的API。然而,直接使用原生的HttpClient
可能会导致代码重复、错误处理不完善等问题。为了提高代码的可维护性和可测试性,我们通常会对HttpClient
进行封装。本文将介绍一个完整的HttpRequest
类封装实现,并深入探讨HTTP请求处理的最佳实践。
一、完整的HttpRequest类实现
首先,让我们来看一下完整的HttpRequest
类实现代码:
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
public class Response
{
public bool Success { get; set; }
public string Message { get; set; }
public object Data { get; set; }
public HttpStatusCode StatusCode { get; set; }
}
public static class JsonConverterExtensions
{
public static readonly JsonSerializerOptions SerializerSettings = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
IgnoreNullValues = true,
WriteIndented = false
};
}
public class HttpRequest : IDisposable
{
private readonly HttpClient client;
private bool disposed = false;
public HttpRequest(HttpClient client)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
client?.Dispose();
}
disposed = true;
}
}
public async Task<Response> GetAsync(string resource)
{
try
{
var response = await client.GetAsync(resource);
return await ProcessResponseAsync(response);
}
catch (HttpRequestException ex)
{
return HandleException(ex);
}
catch (Exception ex)
{
return HandleUnexpectedException(ex);
}
}
public async Task<Response> PostAsync(string resource, object body)
{
try
{
var content = CreateJsonContent(body);
var response = await client.PostAsync(resource, content);
return await ProcessResponseAsync(response);
}
catch (HttpRequestException ex)
{
return HandleException(ex);
}
catch (Exception ex)
{
return HandleUnexpectedException(ex);
}
}
public async Task<Response> PutAsync(string resource, object body)
{
try
{
var content = CreateJsonContent(body);
var response = await client.PutAsync(resource, content);
return await ProcessResponseAsync(response);
}
catch (HttpRequestException ex)
{
return HandleException(ex);
}
catch (Exception ex)
{
return HandleUnexpectedException(ex);
}
}
public async Task<Response> DeleteAsync(string resource)
{
try
{
var response = await client.DeleteAsync(resource);
return await ProcessResponseAsync(response);
}
catch (HttpRequestException ex)
{
return HandleException(ex);
}
catch (Exception ex)
{
return HandleUnexpectedException(ex);
}
}
public HttpRequest WithBaseAddress(string baseAddress)
{
if (!string.IsNullOrEmpty(baseAddress))
{
client.BaseAddress = new Uri(baseAddress);
}
return this;
}
public HttpRequest WithTimeout(TimeSpan timeout)
{
client.Timeout = timeout;
return this;
}
public HttpRequest WithHeader(string name, string value)
{
if (!client.DefaultRequestHeaders.Contains(name))
{
client.DefaultRequestHeaders.Add(name, value);
}
return this;
}
public HttpRequest WithHeaders(IDictionary<string, string> headers)
{
if (headers != null)
{
foreach (var header in headers)
{
WithHeader(header.Key, header.Value);
}
}
return this;
}
public HttpRequest WithAuthorization(string scheme, string parameter)
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme, parameter);
return this;
}
public HttpRequest WithBearerToken(string token)
{
return WithAuthorization("Bearer", token);
}
private StringContent CreateJsonContent(object body)
{
if (body == null)
{
return new StringContent("{}", Encoding.UTF8, "application/json");
}
var json = JsonSerializer.Serialize(body, JsonConverterExtensions.SerializerSettings);
return new StringContent(json, Encoding.UTF8, "application/json");
}
private async Task<Response> ProcessResponseAsync(HttpResponseMessage response)
{
var responseContent = await response.Content.ReadAsStringAsync();
try
{
// 尝试解析JSON响应
var responseObject = JsonSerializer.Deserialize<Response>(responseContent, JsonConverterExtensions.SerializerSettings);
if (responseObject != null)
{
responseObject.StatusCode = response.StatusCode;
return responseObject;
}
}
catch (JsonException)
{
// 如果JSON解析失败,创建一个基于HTTP状态码的响应
}
// 对于非JSON响应或解析失败的情况
return new Response
{
Success = response.IsSuccessStatusCode,
Message = response.ReasonPhrase,
StatusCode = response.StatusCode,
Data = responseContent
};
}
private Response HandleException(HttpRequestException ex)
{
return new Response
{
Success = false,
Message = $"HTTP请求错误: {ex.Message}",
StatusCode = ex.StatusCode ?? HttpStatusCode.InternalServerError,
Data = ex
};
}
private Response HandleUnexpectedException(Exception ex)
{
return new Response
{
Success = false,
Message = $"处理请求时发生意外错误: {ex.Message}",
StatusCode = HttpStatusCode.InternalServerError,
Data = ex
};
}
}
二、设计思路与实现要点
1. 依赖注入与生命周期管理
这个封装类采用了依赖注入模式,通过构造函数接收一个HttpClient
实例。这样做有几个重要好处:
- 遵循单一职责原则,
HttpRequest
类专注于HTTP请求处理 - 便于单元测试,可以轻松注入模拟的
HttpClient
- 利用.NET的
IHttpClientFactory
进行正确的HttpClient
生命周期管理,避免资源泄漏
同时,类实现了IDisposable
接口,确保在不再需要时正确释放HttpClient
资源。
2. 流畅接口设计
为了提供更友好的API体验,封装类实现了流畅接口模式:
var response = await new HttpRequest(httpClient)
.WithBaseAddress("https://api.example.com")
.WithBearerToken("your-token-here")
.WithHeader("X-Custom-Header", "value")
.PostAsync("/resource", new { Key = "value" });
这种链式调用方式使代码更加简洁易读,同时保持了良好的可扩展性。
3. 统一的错误处理
在每个HTTP方法中,我们都实现了统一的异常处理机制:
- 捕获
HttpRequestException
处理HTTP特定错误 - 捕获其他异常处理意外错误
- 将所有错误转换为统一的
Response
对象 - 保留原始异常信息以便调试
这种统一的错误处理方式使上层调用代码更加简洁,无需重复处理各种异常情况。
4. 灵活的响应处理
ProcessResponseAsync
方法负责处理HTTP响应,它尝试将响应内容解析为JSON格式的Response
对象:
- 如果解析成功,返回包含完整信息的
Response
对象 - 如果解析失败,创建一个基于HTTP状态码的
Response
对象 - 始终保留原始响应内容和状态码信息
这种设计使封装类能够处理各种类型的HTTP响应,同时提供一致的返回格式。
三、实际使用示例
下面是一个使用这个封装类的完整示例:
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class Program
{
public static async Task Main()
{
try
{
// 创建HttpClient实例(实际应用中建议使用IHttpClientFactory)
using var httpClient = new HttpClient();
// 创建请求实例并配置
var request = new HttpRequest(httpClient)
.WithBaseAddress("https://api.example.com")
.WithBearerToken("your-auth-token");
// 发送GET请求
var getResponse = await request.GetAsync("/api/users");
Console.WriteLine($"GET请求结果: {getResponse.Success}, 状态码: {getResponse.StatusCode}");
// 发送POST请求
var postData = new { Name = "John Doe", Email = "john@example.com" };
var postResponse = await request.PostAsync("/api/users", postData);
Console.WriteLine($"POST请求结果: {postResponse.Success}, 状态码: {postResponse.StatusCode}");
// 发送PUT请求
var putData = new { Id = 1, Name = "Jane Doe" };
var putResponse = await request.PutAsync("/api/users/1", putData);
Console.WriteLine($"PUT请求结果: {putResponse.Success}, 状态码: {putResponse.StatusCode}");
// 发送DELETE请求
var deleteResponse = await request.DeleteAsync("/api/users/1");
Console.WriteLine($"DELETE请求结果: {deleteResponse.Success}, 状态码: {deleteResponse.StatusCode}");
}
catch (Exception ex)
{
Console.WriteLine($"发生未处理的异常: {ex.Message}");
}
}
}
四、HttpClient使用最佳实践
在使用HttpClient
和这个封装类时,还需要注意以下最佳实践:
-
- 使用IHttpClientFactory:在ASP.NET Core应用中,始终使用
IHttpClientFactory
创建HttpClient
实例,避免直接实例化HttpClient
。
- 使用IHttpClientFactory:在ASP.NET Core应用中,始终使用
-
- 设置合理的超时时间:默认情况下,
HttpClient
的超时时间是100秒,根据实际需求调整这个值,防止长时间阻塞。
- 设置合理的超时时间:默认情况下,
-
- 处理取消请求:考虑实现请求取消机制,通过
CancellationToken
参数传递取消令牌。
- 处理取消请求:考虑实现请求取消机制,通过
-
- 处理重试逻辑:对于临时性网络错误,考虑实现重试机制。可以使用Polly等库来简化重试策略的实现。
-
- 监控HTTP请求性能:记录HTTP请求的执行时间、成功率等指标,便于性能分析和问题排查。
通过这个完整的HttpRequest
类封装,我们可以更加高效、安全地处理HTTP通信,同时保持代码的整洁和可维护性。希望这篇文章对你理解C#中的HTTP请求处理有所帮助。
这个实现提供了完整的HTTP请求功能,包括GET、POST、PUT、DELETE方法,以及灵活的请求配置和统一的响应处理。博客中详细解释了设计思路、实现要点和最佳实践。如果你需要进一步调整代码或博客内容,请随时告诉我。