Skip to content

HTTP扩展

.NETLicense

GameFrameX.Foundation.Http.Extension 是一个为HttpClient提供扩展方法的基础设施库,提供了统一的GET和POST请求接口,简化HTTP请求操作,支持多种数据格式和响应类型。

🎯 核心特性

  • GET请求扩展 - 提供多种GET请求方法,支持字符串、字节数组、流等响应格式
  • POST请求扩展 - 支持JSON、表单、文件等多种POST请求方式
  • 类型安全 - 泛型支持,确保数据类型安全
  • 灵活配置 - 支持自定义请求头、超时时间、序列化选项
  • 多种响应格式 - 支持字符串、字节数组、流等多种响应格式
  • 文件上传 - 支持单文件和Multipart表单文件上传
  • 异步支持 - 全面支持异步操作和取消令牌
  • 错误处理 - 完善的参数验证和异常处理

📦 安装

bash
# 通过 NuGet 包管理器安装
Install-Package GameFrameX.Foundation.Http.Extension

# 或通过 .NET CLI 安装
dotnet add package GameFrameX.Foundation.Http.Extension

🚀 快速开始

基本使用

csharp
using GameFrameX.Foundation.Http.Extension;

// 创建HttpClient实例
using var httpClient = new HttpClient();

// GET请求获取字符串
string response = await httpClient.GetToStringAsync<string>("https://api.example.com/users");
Console.WriteLine(response);

// POST JSON数据
var userData = new { Name = "张三", Age = 25 };
string postResponse = await httpClient.PostJsonToStringAsync("https://api.example.com/users", userData);
Console.WriteLine(postResponse);

带请求头和超时的请求

csharp
// 自定义请求头
var headers = new Dictionary<string, string>
{
    ["Authorization"] = "Bearer your-token",
    ["User-Agent"] = "MyApp/1.0"
};

// GET请求带请求头和超时
string response = await httpClient.GetToStringAsync<string>(
    "https://api.example.com/protected", 
    headers, 
    timeout: 30);

// POST请求带请求头和超时
string postResponse = await httpClient.PostJsonToStringAsync(
    "https://api.example.com/data", 
    userData, 
    headers, 
    timeout: 30);

📋 详细使用指南

1. GET请求扩展方法

获取字符串响应

csharp
// 基本GET请求
string response1 = await httpClient.GetToStringAsync<string>("https://api.example.com/data");

// 带请求头和超时的GET请求
var headers = new Dictionary<string, string>
{
    ["Accept"] = "application/json",
    ["Authorization"] = "Bearer token"
};
string response2 = await httpClient.GetToStringAsync<string>(
    "https://api.example.com/data", 
    headers, 
    timeout: 30);

获取字节数组响应

csharp
// 基本GET请求获取字节数组
byte[] data1 = await httpClient.GetToByteArrayAsync<byte[]>("https://api.example.com/file");

// 带请求头的GET请求获取字节数组
byte[] data2 = await httpClient.GetToByteArrayAsync<byte[]>(
    "https://api.example.com/file", 
    headers, 
    timeout: 60);

获取流响应

csharp
// 基本GET请求获取流
using Stream stream1 = await httpClient.GetToStreamAsync<Stream>("https://api.example.com/download");

// 带请求头的GET请求获取流
using Stream stream2 = await httpClient.GetToStreamAsync<Stream>(
    "https://api.example.com/download", 
    headers, 
    timeout: 120);

2. POST请求扩展方法

JSON数据POST请求

csharp
// 定义数据模型
public class UserInfo
{
    public string Name { get; set; }
    public int Age { get; set; }
    public string Email { get; set; }
}

var user = new UserInfo 
{ 
    Name = "张三", 
    Age = 25, 
    Email = "zhangsan@example.com" 
};

// 基本JSON POST请求
string response1 = await httpClient.PostJsonToStringAsync("https://api.example.com/users", user);

// 带自定义序列化选项的POST请求
var jsonOptions = new JsonSerializerOptions
{
    PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
    WriteIndented = true
};
string response2 = await httpClient.PostJsonToStringAsync(
    "https://api.example.com/users", 
    user, 
    jsonOptions);

// 带请求头和超时的POST请求
string response3 = await httpClient.PostJsonToStringAsync(
    "https://api.example.com/users", 
    user, 
    headers, 
    timeout: 30);

// 完整配置的POST请求
string response4 = await httpClient.PostJsonToStringAsync(
    "https://api.example.com/users", 
    user, 
    headers, 
    jsonOptions, 
    timeout: 30);

获取不同格式的POST响应

csharp
// 获取字节数组响应
byte[] responseBytes = await httpClient.PostJsonToByteArrayAsync(
    "https://api.example.com/data", 
    user);

// 获取流响应
using Stream responseStream = await httpClient.PostJsonToStreamAsync(
    "https://api.example.com/data", 
    user);

表单数据POST请求

csharp
// 表单数据
var formData = new Dictionary<string, string>
{
    ["username"] = "zhangsan",
    ["password"] = "123456",
    ["email"] = "zhangsan@example.com"
};

// 基本表单POST请求
string response1 = await httpClient.PostFormToStringAsync(
    "https://api.example.com/login", 
    formData);

// 带请求头和超时的表单POST请求
string response2 = await httpClient.PostFormToStringAsync(
    "https://api.example.com/login", 
    formData, 
    headers, 
    timeout: 30);

3. 文件上传

单文件上传

csharp
// 基本文件上传
string response1 = await httpClient.PostFileToStringAsync(
    "https://api.example.com/upload", 
    @"C:\temp\document.pdf");

// 带请求头和超时的文件上传
string response2 = await httpClient.PostFileToStringAsync(
    "https://api.example.com/upload", 
    @"C:\temp\document.pdf", 
    headers, 
    timeout: 300);

Multipart表单文件上传

csharp
// 额外的表单数据
var additionalData = new Dictionary<string, string>
{
    ["description"] = "用户头像",
    ["category"] = "avatar"
};

// Multipart文件上传
string response = await httpClient.PostMultipartFileToStringAsync(
    "https://api.example.com/upload", 
    "file",                    // 文件字段名
    @"C:\temp\avatar.jpg",     // 文件路径
    additionalData);           // 额外表单数据

🎨 高级用法

1. 自定义JSON序列化配置

csharp
public static class CustomJsonOptions
{
    public static readonly JsonSerializerOptions CamelCase = new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        WriteIndented = true,
        DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
    };
    
    public static readonly JsonSerializerOptions SnakeCase = new JsonSerializerOptions
    {
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
        WriteIndented = false
    };
}

// 使用自定义序列化选项
string response = await httpClient.PostJsonToStringAsync(
    "https://api.example.com/data", 
    userData, 
    CustomJsonOptions.CamelCase);

2. 批量请求处理

csharp
public class BatchRequestProcessor
{
    private readonly HttpClient httpClient;
    
    public BatchRequestProcessor(HttpClient httpClient)
    {
        this.httpClient = httpClient;
    }
    
    public async Task<List<string>> ProcessBatchGetRequests(List<string> urls)
    {
        var tasks = urls.Select(url => 
            httpClient.GetToStringAsync<string>(url)).ToList();
        
        return (await Task.WhenAll(tasks)).ToList();
    }
    
    public async Task<List<string>> ProcessBatchPostRequests<T>(
        string baseUrl, 
        List<T> dataList)
    {
        var tasks = dataList.Select(data => 
            httpClient.PostJsonToStringAsync(baseUrl, data)).ToList();
        
        return (await Task.WhenAll(tasks)).ToList();
    }
}

3. 重试机制

csharp
public static class HttpClientRetryExtensions
{
    public static async Task<string> GetWithRetryAsync<T>(
        this HttpClient httpClient, 
        string url, 
        int maxRetries = 3, 
        TimeSpan delay = default)
    {
        if (delay == default) delay = TimeSpan.FromSeconds(1);
        
        for (int i = 0; i < maxRetries; i++)
        {
            try
            {
                return await httpClient.GetToStringAsync<T>(url);
            }
            catch (HttpRequestException) when (i < maxRetries - 1)
            {
                await Task.Delay(delay);
                delay = TimeSpan.FromMilliseconds(delay.TotalMilliseconds * 2); // 指数退避
            }
        }
        
        throw new InvalidOperationException($"请求失败,已重试 {maxRetries} 次");
    }
}

4. 响应缓存

csharp
public class CachedHttpClient
{
    private readonly HttpClient httpClient;
    private readonly MemoryCache cache;
    
    public CachedHttpClient(HttpClient httpClient)
    {
        this.httpClient = httpClient;
        this.cache = new MemoryCache(new MemoryCacheOptions
        {
            SizeLimit = 100
        });
    }
    
    public async Task<string> GetWithCacheAsync<T>(
        string url, 
        TimeSpan? expiration = null)
    {
        if (cache.TryGetValue(url, out string cachedResponse))
        {
            return cachedResponse;
        }
        
        var response = await httpClient.GetToStringAsync<T>(url);
        
        var cacheOptions = new MemoryCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = expiration ?? TimeSpan.FromMinutes(5),
            Size = 1
        };
        
        cache.Set(url, response, cacheOptions);
        return response;
    }
}

💡 最佳实践

1. HttpClient生命周期管理

csharp
// 推荐:使用IHttpClientFactory
public class ApiService
{
    private readonly HttpClient httpClient;
    
    public ApiService(IHttpClientFactory httpClientFactory)
    {
        httpClient = httpClientFactory.CreateClient("ApiClient");
    }
    
    public async Task<string> GetDataAsync()
    {
        return await httpClient.GetToStringAsync<string>("https://api.example.com/data");
    }
}

// 在Startup.cs或Program.cs中注册
services.AddHttpClient("ApiClient", client =>
{
    client.BaseAddress = new Uri("https://api.example.com/");
    client.DefaultRequestHeaders.Add("User-Agent", "MyApp/1.0");
});

2. 统一错误处理

csharp
public class ApiClient
{
    private readonly HttpClient httpClient;
    private readonly ILogger<ApiClient> logger;
    
    public ApiClient(HttpClient httpClient, ILogger<ApiClient> logger)
    {
        this.httpClient = httpClient;
        this.logger = logger;
    }
    
    public async Task<T> GetAsync<T>(string url) where T : class
    {
        try
        {
            var response = await httpClient.GetToStringAsync<T>(url);
            return JsonSerializer.Deserialize<T>(response);
        }
        catch (HttpRequestException ex)
        {
            logger.LogError(ex, "HTTP请求失败: {Url}", url);
            throw new ApiException($"请求失败: {ex.Message}", ex);
        }
        catch (TaskCanceledException ex)
        {
            logger.LogError(ex, "请求超时: {Url}", url);
            throw new ApiException("请求超时", ex);
        }
        catch (JsonException ex)
        {
            logger.LogError(ex, "JSON反序列化失败: {Url}", url);
            throw new ApiException("数据格式错误", ex);
        }
    }
}

public class ApiException : Exception
{
    public ApiException(string message) : base(message) { }
    public ApiException(string message, Exception innerException) : base(message, innerException) { }
}

3. 配置管理

csharp
public class ApiConfiguration
{
    public string BaseUrl { get; set; }
    public int TimeoutSeconds { get; set; } = 30;
    public Dictionary<string, string> DefaultHeaders { get; set; } = new();
}

public class ConfiguredApiClient
{
    private readonly HttpClient httpClient;
    private readonly ApiConfiguration config;
    
    public ConfiguredApiClient(HttpClient httpClient, IOptions<ApiConfiguration> config)
    {
        this.httpClient = httpClient;
        this.config = config.Value;
        
        // 应用配置
        httpClient.BaseAddress = new Uri(this.config.BaseUrl);
        httpClient.Timeout = TimeSpan.FromSeconds(this.config.TimeoutSeconds);
        
        foreach (var header in this.config.DefaultHeaders)
        {
            httpClient.DefaultRequestHeaders.Add(header.Key, header.Value);
        }
    }
    
    public async Task<string> GetAsync(string endpoint)
    {
        return await httpClient.GetToStringAsync<string>(endpoint);
    }
}

4. 请求/响应日志记录

csharp
public class LoggingHttpClient
{
    private readonly HttpClient httpClient;
    private readonly ILogger<LoggingHttpClient> logger;
    
    public LoggingHttpClient(HttpClient httpClient, ILogger<LoggingHttpClient> logger)
    {
        this.httpClient = httpClient;
        this.logger = logger;
    }
    
    public async Task<string> GetWithLoggingAsync<T>(string url)
    {
        var stopwatch = Stopwatch.StartNew();
        
        logger.LogInformation("开始GET请求: {Url}", url);
        
        try
        {
            var response = await httpClient.GetToStringAsync<T>(url);
            
            stopwatch.Stop();
            logger.LogInformation("GET请求成功: {Url}, 耗时: {ElapsedMs}ms, 响应长度: {Length}", 
                url, stopwatch.ElapsedMilliseconds, response.Length);
            
            return response;
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            logger.LogError(ex, "GET请求失败: {Url}, 耗时: {ElapsedMs}ms", 
                url, stopwatch.ElapsedMilliseconds);
            throw;
        }
    }
}

🔧 扩展功能

1. 自定义响应处理器

csharp
public static class HttpClientResponseExtensions
{
    public static async Task<ApiResponse<T>> GetApiResponseAsync<T>(
        this HttpClient httpClient, 
        string url) where T : class
    {
        try
        {
            var response = await httpClient.GetToStringAsync<T>(url);
            var apiResponse = JsonSerializer.Deserialize<ApiResponse<T>>(response);
            return apiResponse;
        }
        catch (Exception ex)
        {
            return new ApiResponse<T>
            {
                Success = false,
                Message = ex.Message,
                Data = null
            };
        }
    }
}

public class ApiResponse<T>
{
    public bool Success { get; set; }
    public string Message { get; set; }
    public T Data { get; set; }
}

2. 请求拦截器

csharp
public class InterceptorHttpClient
{
    private readonly HttpClient httpClient;
    private readonly List<Func<HttpRequestMessage, Task>> requestInterceptors;
    private readonly List<Func<HttpResponseMessage, Task>> responseInterceptors;
    
    public InterceptorHttpClient(HttpClient httpClient)
    {
        this.httpClient = httpClient;
        this.requestInterceptors = new List<Func<HttpRequestMessage, Task>>();
        this.responseInterceptors = new List<Func<HttpResponseMessage, Task>>();
    }
    
    public void AddRequestInterceptor(Func<HttpRequestMessage, Task> interceptor)
    {
        requestInterceptors.Add(interceptor);
    }
    
    public void AddResponseInterceptor(Func<HttpResponseMessage, Task> interceptor)
    {
        responseInterceptors.Add(interceptor);
    }
    
    public async Task<string> GetWithInterceptorsAsync(string url)
    {
        var request = new HttpRequestMessage(HttpMethod.Get, url);
        
        // 执行请求拦截器
        foreach (var interceptor in requestInterceptors)
        {
            await interceptor(request);
        }
        
        var response = await httpClient.SendAsync(request);
        
        // 执行响应拦截器
        foreach (var interceptor in responseInterceptors)
        {
            await interceptor(response);
        }
        
        return await response.Content.ReadAsStringAsync();
    }
}

3. 并发限制

csharp
public class ThrottledHttpClient
{
    private readonly HttpClient httpClient;
    private readonly SemaphoreSlim semaphore;
    
    public ThrottledHttpClient(HttpClient httpClient, int maxConcurrency = 10)
    {
        this.httpClient = httpClient;
        this.semaphore = new SemaphoreSlim(maxConcurrency, maxConcurrency);
    }
    
    public async Task<string> GetWithThrottleAsync<T>(string url)
    {
        await semaphore.WaitAsync();
        try
        {
            return await httpClient.GetToStringAsync<T>(url);
        }
        finally
        {
            semaphore.Release();
        }
    }
}

🔍 故障排除

常见问题

1. 超时问题

问题: 请求经常超时 解决方案: 适当增加超时时间,使用重试机制

csharp
// 增加超时时间
string response = await httpClient.GetToStringAsync<string>(url, headers, timeout: 120);

// 使用重试机制
string response = await httpClient.GetWithRetryAsync<string>(url, maxRetries: 3);

2. 内存泄漏问题

问题: HttpClient使用不当导致内存泄漏 解决方案: 使用IHttpClientFactory或正确管理HttpClient生命周期

csharp
// 推荐:使用IHttpClientFactory
services.AddHttpClient<ApiService>();

// 或者:正确使用using语句
using var httpClient = new HttpClient();

3. 序列化问题

问题: JSON序列化/反序列化失败 解决方案: 检查数据模型和序列化选项

csharp
// 使用自定义序列化选项
var options = new JsonSerializerOptions
{
    PropertyNameCaseInsensitive = true,
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};

string response = await httpClient.PostJsonToStringAsync(url, data, options);

调试技巧

csharp
// 启用详细日志
public static class HttpClientDebugExtensions
{
    public static async Task<string> GetWithDebugAsync<T>(
        this HttpClient httpClient, 
        string url, 
        ILogger logger = null)
    {
        logger?.LogDebug("发送GET请求到: {Url}", url);
        
        var stopwatch = Stopwatch.StartNew();
        try
        {
            var response = await httpClient.GetToStringAsync<T>(url);
            stopwatch.Stop();
            
            logger?.LogDebug("GET请求成功: {Url}, 耗时: {ElapsedMs}ms, 响应长度: {Length}", 
                url, stopwatch.ElapsedMilliseconds, response.Length);
            
            return response;
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            logger?.LogError(ex, "GET请求失败: {Url}, 耗时: {ElapsedMs}ms", 
                url, stopwatch.ElapsedMilliseconds);
            throw;
        }
    }
}

📄 许可证

本项目采用 Apache 许可证(版本 2.0)进行分发和使用。详细信息请参阅项目根目录中的 LICENSE 文件。

🤝 贡献

欢迎提交 Issue 和 Pull Request 来帮助改进这个项目。

📞 支持

如果您在使用过程中遇到问题,请通过以下方式获取帮助:


GameFrameX.Foundation.Http.Extension - 让HTTP请求更简单、更统一!