用「点外卖」的例子讲透HttpClient

想象你要点外卖

传统方式(像打电话订餐):

csharp

scss 复制代码
// 每次都要:找餐厅电话→打电话→等接通→点餐→等确认→挂电话
// 想再点一杯饮料?重新打一遍电话!
public void 传统点餐()
{
    // 第一次:点主食
    找电话号码("肯德基");
    打电话("我要一个汉堡");
    等待接通();
    说明需求();
    等待确认();
    挂断电话();
    
    // 第二次:点饮料(又要重复所有步骤)
    找电话号码("肯德基");
    打电话("再加一杯可乐");
    等待接通();
    说明需求();
    等待确认();
    挂断电话();
    
    // 第三次:查订单状态(还得再打)
    找电话号码("肯德基");
    打电话("我的餐到哪了");
    // ... 效率极低!
}

使用HttpClient(像用外卖APP):

csharp

dart 复制代码
// 就像在美团APP里操作
HttpClient 我的外卖APP = new HttpClient();

// 一次设置,多次使用
我的外卖APP.BaseAddress = new Uri("https://api.kfc.com/");

async Task 智能点餐()
{
    // 点主食(像在APP里选商品)
    var 汉堡订单 = await 我的外卖APP.PostAsync("order", 汉堡数据);
    
    // 加饮料(直接在订单里添加,不用重新打开)
    var 饮料订单 = await 我的外卖APP.PostAsync("order/add", 可乐数据);
    
    // 查进度(刷新一下就行)
    var 订单状态 = await 我的外卖APP.GetAsync("order/status");
    
    // 所有操作都在同一个APP里完成,不用反复"打电话"
}

HttpClient的核心功能(像外卖APP的特性)

1. 连接复用 - 不用反复登录

csharp

scss 复制代码
// ❌ 传统:每次都要重新登录
public void 传统方式()
{
    // 点餐
    打开APP();
    登录();
    点餐();
    退出APP();
    
    // 查订单(又要重新登录)
    打开APP();
    登录();
    查订单();
    退出APP();
}

// ✅ HttpClient:一次登录,持续使用
public class 我的外卖服务
{
    private HttpClient 外卖APP = new HttpClient();
    
    public 我的外卖服务()
    {
        // 只需要登录一次
        外卖APP.BaseAddress = new Uri("https://外卖平台.com/");
        外卖APP.DefaultRequestHeaders.Add("Authorization", "我的登录凭证");
    }
    
    public async Task 点餐() => await 外卖APP.PostAsync(...);
    public async Task 查订单() => await 外卖APP.GetAsync(...);
    public async Task 取消订单() => await 外卖APP.DeleteAsync(...);
    // 所有操作都用同一个登录状态
}

2. 同时处理多个请求 - 并行点多家餐厅

csharp

csharp 复制代码
public async Task 点周末大餐()
{
    // 同时向多家餐厅下单
    var 肯德基任务 = 我的外卖APP.PostAsync("kfc/order", 炸鸡数据);
    var 麦当劳任务 = 我的外卖APP.PostAsync("mcdonald/order", 汉堡数据);
    var 奶茶店任务 = 我的外卖APP.PostAsync("milktea/order", 奶茶数据);
    
    // 等待所有餐厅接单(并行等待,效率高)
    await Task.WhenAll(肯德基任务, 麦当劳任务, 奶茶店任务);
    
    Console.WriteLine("所有订单已提交!");
}

3. 发送各种类型的数据 - 不只是文字

csharp

csharp 复制代码
public async Task 提交订单()
{
    // 1. 发送JSON数据(像填写详细的配送信息)
    var 订单信息 = new 
    {
        商品 = "香辣鸡腿堡套餐",
        数量 = 1,
        地址 = "北京市朝阳区xxx",
        备注 = "不要辣椒,快点送"
    };
    
    var json内容 = new StringContent(
        JsonSerializer.Serialize(订单信息),
        Encoding.UTF8,
        "application/json");  // 告诉服务器这是JSON格式
    
    await 我的外卖APP.PostAsync("order", json内容);
    
    // 2. 发送表单数据(像填写简单问卷)
    var 表单数据 = new FormUrlEncodedContent(new[]
    {
        new KeyValuePair<string, string>("rating", "5"),
        new KeyValuePair<string, string>("comment", "很好吃")
    });
    
    await 我的外卖APP.PostAsync("feedback", 表单数据);
    
    // 3. 上传文件(像发送发票照片)
    using var 照片流 = File.OpenRead("发票.jpg");
    var 文件内容 = new StreamContent(照片流);
    
    var 多部分内容 = new MultipartFormDataContent
    {
        { new StringContent("12345"), "orderId" },
        { 文件内容, "invoice", "发票.jpg" }
    };
    
    await 我的外卖APP.PostAsync("upload/invoice", 多部分内容);
}

完整的外卖APP示例

csharp

csharp 复制代码
public class 智能外卖管家
{
    private readonly HttpClient 我的APP;
    
    public 智能外卖管家()
    {
        我的APP = new HttpClient
        {
            BaseAddress = new Uri("https://api.food-delivery.com/v1/"),
            Timeout = TimeSpan.FromSeconds(30)  // 30秒没响应就取消
        };
        
        // 设置APP的通用配置
        我的APP.DefaultRequestHeaders.Add("User-Agent", "MyFoodApp/1.0");
        我的APP.DefaultRequestHeaders.Add("X-API-Key", "my_secret_key");
    }
    
    // 搜索餐厅
    public async Task<List<餐厅>> 搜索餐厅(string 关键词, string 地址)
    {
        string url = $"restaurants?keyword={关键词}&address={地址}";
        var 响应 = await 我的APP.GetAsync(url);
        
        响应.EnsureSuccessStatusCode();  // 如果失败就抛出异常
        
        return await 响应.Content.ReadFromJsonAsync<List<餐厅>>();
    }
    
    // 下单
    public async Task<订单结果> 下单(订单 新订单)
    {
        var 内容 = new StringContent(
            JsonSerializer.Serialize(新订单),
            Encoding.UTF8,
            "application/json");
        
        var 响应 = await 我的APP.PostAsync("orders", 内容);
        
        if (!响应.IsSuccessStatusCode)
        {
            // 处理失败情况
            var 错误信息 = await 响应.Content.ReadAsStringAsync();
            throw new Exception($"下单失败:{错误信息}");
        }
        
        return await 响应.Content.ReadFromJsonAsync<订单结果>();
    }
    
    // 跟踪订单
    public async Task<配送状态> 查看配送状态(string 订单号)
    {
        var 响应 = await 我的APP.GetAsync($"orders/{订单号}/tracking");
        
        if (响应.StatusCode == HttpStatusCode.NotFound)
        {
            throw new Exception("订单不存在");
        }
        
        响应.EnsureSuccessStatusCode();
        return await 响应.Content.ReadFromJsonAsync<配送状态>();
    }
    
    // 批量操作:收藏多家餐厅
    public async Task 批量收藏餐厅(List<string> 餐厅ID列表)
    {
        var 任务列表 = new List<Task>();
        
        foreach (var 餐厅ID in 餐厅ID列表)
        {
            var 任务 = 我的APP.PostAsync($"restaurants/{餐厅ID}/favorite", null);
            任务列表.Add(任务);
        }
        
        // 同时发送所有收藏请求
        await Task.WhenAll(任务列表);
        Console.WriteLine("所有餐厅已收藏!");
    }
}

实际使用场景

场景1:获取天气来决定点什么

csharp

csharp 复制代码
public async Task 智能推荐()
{
    // 先查天气
    var 天气响应 = await 我的外卖APP.GetAsync("https://api.weather.com/北京");
    var 天气数据 = await 天气响应.Content.ReadFromJsonAsync<天气信息>();
    
    // 根据天气推荐
    string 推荐餐品;
    if (天气数据.温度 < 10)
        推荐餐品 = "火锅";
    else if (天气数据.温度 > 30)
        推荐餐品 = "冷面";
    else
        推荐餐品 = "便当";
    
    // 搜索推荐餐品
    var 餐厅列表 = await 搜索餐厅(推荐餐品, "我家地址");
    Console.WriteLine($"根据天气推荐:{推荐餐品}");
}

场景2:处理超时和重试

csharp

csharp 复制代码
public async Task<订单结果> 稳定下单(订单 新订单)
{
    int 重试次数 = 0;
    int 最大重试次数 = 3;
    
    while (重试次数 < 最大重试次数)
    {
        try
        {
            // 尝试下单
            return await 下单(新订单);
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.RequestTimeout)
        {
            // 超时了,等一会再试
            重试次数++;
            Console.WriteLine($"请求超时,第{重试次数}次重试...");
            
            if (重试次数 >= 最大重试次数)
                throw new Exception("多次尝试失败,请检查网络");
                
            await Task.Delay(1000 * 重试次数);  // 等待时间递增
        }
        catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.TooManyRequests)
        {
            // 服务器说请求太多了,等5秒再试
            Console.WriteLine("服务器忙,等待5秒...");
            await Task.Delay(5000);
        }
    }
    
    throw new Exception("下单失败");
}

HttpClient的最佳实践

✅ 正确:像长期使用一个外卖APP

csharp

csharp 复制代码
// 在整个应用生命周期中使用同一个HttpClient实例
public class 全局外卖服务
{
    // 静态的,整个应用只创建一个
    private static readonly HttpClient 我的长期APP = new HttpClient
    {
        BaseAddress = new Uri("https://api.food.com/"),
        Timeout = TimeSpan.FromSeconds(30)
    };
    
    // 所有方法都使用这个实例
    public async Task 点餐() => await 我的长期APP.PostAsync(...);
    public async Task 查订单() => await 我的长期APP.GetAsync(...);
}

❌ 错误:像每次点餐都下载一个新APP

csharp

csharp 复制代码
public void 错误的点餐方式()
{
    for (int i = 0; i < 10; i++)
    {
        // 错误!每次都要创建新的HttpClient
        var 临时APP = new HttpClient();
        await 临时APP.GetAsync("https://api.food.com/order");
        // 忘记释放,会导致资源泄露!
        // 就像手机里装了10个相同的外卖APP
    }
}

✅ 更好:使用IHttpClientFactory(像官方应用商店)

csharp

typescript 复制代码
// 在Startup.cs中配置
services.AddHttpClient("外卖服务", client =>
{
    client.BaseAddress = new Uri("https://api.food.com/");
    client.DefaultRequestHeaders.Add("Accept", "application/json");
});

// 使用时获取
public class 订单服务
{
    private readonly IHttpClientFactory _app商店;
    
    public 订单服务(IHttpClientFactory app商店)
    {
        _app商店 = app商店;
    }
    
    public async Task 处理订单()
    {
        // 从"应用商店"获取一个配置好的HttpClient
        var 我的APP = _app商店.CreateClient("外卖服务");
        
        // 使用它
        await 我的APP.PostAsync("orders", ...);
        
        // 不用操心释放,工厂会管理生命周期
    }
}

常见问题解决方案

问题1:网络不稳定怎么办?

csharp

csharp 复制代码
public async Task<string> 稳定获取菜单()
{
    try
    {
        // 设置合理的超时时间
        我的APP.Timeout = TimeSpan.FromSeconds(15);
        
        // 使用CancellationToken可以中途取消
        var 取消令牌源 = new CancellationTokenSource();
        
        // 如果5秒还没响应,就取消
        取消令牌源.CancelAfter(TimeSpan.FromSeconds(5));
        
        var 响应 = await 我的APP.GetAsync("menu", 取消令牌源.Token);
        return await 响应.Content.ReadAsStringAsync();
    }
    catch (TaskCanceledException)
    {
        return "请求超时,请稍后重试";
    }
    catch (HttpRequestException)
    {
        return "网络连接失败,请检查网络";
    }
}

问题2:需要添加认证信息?

csharp

csharp 复制代码
public void 设置登录状态(string 令牌)
{
    // 方法1:添加到默认请求头
    我的APP.DefaultRequestHeaders.Authorization = 
        new AuthenticationHeaderValue("Bearer", 令牌);
    
    // 方法2:每次请求单独添加
    var 请求 = new HttpRequestMessage(HttpMethod.Get, "user/profile");
    请求.Headers.Authorization = new AuthenticationHeaderValue("Bearer", 令牌);
    
    // 方法3:使用HttpClientHandler
    var handler = new HttpClientHandler
    {
        UseCookies = true,
        CookieContainer = new CookieContainer()
    };
    handler.CookieContainer.Add(new Uri("https://api.food.com"), 
                               new Cookie("session", 令牌));
}

总结比喻

HttpClient = 你的智能手机外卖APP

  1. 一次安装,长期使用 - 不用每次点餐都重新下载APP
  2. 保持登录状态 - 登录一次,后续操作都不用再登录
  3. 并行操作 - 可以同时浏览餐厅、查看订单、联系客服
  4. 处理各种数据 - 能发文字、图片、文件,就像APP能发消息、传照片
  5. 智能重试 - 网络不好时会自动重试,就像APP的"重新加载"
  6. 统一管理 - 所有外卖操作都在一个APP里完成

核心思想转变:

从「每次都要重新打电话联系餐厅」

变成「有一个智能外卖APP,所有餐厅、所有操作都在里面完成」

HttpClient让你的程序能像使用外卖APP一样,高效、稳定地与各种网络服务通信!

相关推荐
C_心欲无痕2 小时前
nodejs - pnpm解决幽灵依赖
前端·缓存·npm·node.js
二等饼干~za8986682 小时前
GEO优化---关键词搜索排名源码开发思路分享
大数据·前端·网络·数据库·django
韩曙亮2 小时前
【Web APIs】移动端轮播图案例 ( 轮播图自动播放 | 设置无缝衔接滑动 | 手指滑动轮播图 | 完整代码示例 )
前端·javascript·css·html·轮播图·移动端·web apis
犬大犬小2 小时前
Web 渗透:如何绕过403 Forbidden? Part I
前端·安全性测试·web 安全
AI前端老薛3 小时前
面试:了解闭包吗?
前端
xu_duo_i3 小时前
vue3+element-plus图片上传,前端压缩(纯函数,无插件)
前端·javascript·vue.js
林恒smileZAZ3 小时前
在 Web 前端实现流式 TTS 播放
前端
睡不着的可乐3 小时前
前端优化:requestAnimationFrame vs setInterval 性能对比与实战
前端
C_心欲无痕3 小时前
nodejs - npm serve
前端·npm·node.js