You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HttpClientStudy/Docs/1.3.0.基础使用.管理客户端.ipynb

1097 lines
36 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
},
"source": [
"# HttpClient 初始化与生命周期管理"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"HttpClient 旨在实例化一次,并在应用程序的整个生命周期内重复使用。\n",
"\n",
"为实现复用HttpClient类库默认使用连接池和请求管道可以手动管理(连接池、配置管道、使用Polly); 结合IoC容器、工厂模式(提供了IHttpClientFactory类库)、复原库Polly,可以更加方便、完善的使用,这也是推荐的方法。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 0、初始化与全局设置"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"data": {
"text/html": [
"<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>Microsoft.Extensions.DependencyInjection, 8.0.0</span></li><li><span>Microsoft.Extensions.Http, 8.0.0</span></li><li><span>Microsoft.Extensions.Http.Polly, 8.0.7</span></li><li><span>Polly, 8.4.1</span></li><li><span>Refit, 7.1.2</span></li><li><span>Refit.HttpClientFactory, 7.1.2</span></li><li><span>System.Net.Http.Json, 8.0.0</span></li></ul></div></div>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"配置文件根目录e:\\王高峰\\我的项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
"启动WebApi项目\n",
"程序[e:\\王高峰\\我的项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.exe]已在新的命令行窗口执行。如果未出现新命令行窗口,可能是程序错误造成窗口闪现!\n"
]
}
],
"source": [
"//全局设置,行运行一次,为后续准备\n",
"#r \"nuget:System.Net.Http.Json\"\n",
"#r \"nuget:Microsoft.Extensions.Http\"\n",
"#r \"nuget:Microsoft.Extensions.DependencyInjection\"\n",
"#r \"nuget:Polly\"\n",
"#r \"nuget:Microsoft.Extensions.Http.Polly\"\n",
"#r \"nuget:Refit\" \n",
"#r \"nuget:Refit.HttpClientFactory\"\n",
"#r \"./Publish/HttpClientStudy.Core/HttpClientStudy.Core.dll\"\n",
"\n",
"global using System;\n",
"global using System.IO;\n",
"global using System.IO.Enumeration;\n",
"global using System.Buffers;\n",
"\n",
"global using System.Collections;\n",
"global using System.Collections.Concurrent;\n",
"global using System.Linq;\n",
"global using System.Linq.Expressions;\n",
"global using System.Threading;\n",
"global using System.Threading.Tasks;\n",
"\n",
"global using System.Net.Http;\n",
"global using System.Net.Http.Headers;\n",
"\n",
"global using Microsoft.Extensions.DependencyInjection;\n",
"global using Microsoft.Extensions.DependencyInjection.Extensions;\n",
"\n",
"global using Polly;\n",
"global using Polly.NoOp;\n",
"global using Polly.Simmy;\n",
"global using Polly.Retry;\n",
"global using Polly.Hedging;\n",
"global using Polly.Timeout;\n",
"global using Polly.Bulkhead;\n",
"global using Polly.Fallback;\n",
"global using Polly.RateLimit;\n",
"global using Polly.CircuitBreaker;\n",
"global using Polly.Utilities;\n",
"global using Polly.Extensions;\n",
"global using Polly.Wrap;\n",
"global using Polly.Registry;\n",
"global using Polly.Telemetry;\n",
"\n",
"global using Refit;\n",
"//global using Refit.HttpClientFactory;\n",
"\n",
"global using HttpClientStudy.Config;\n",
"global using HttpClientStudy.Core;\n",
"global using HttpClientStudy.Core.Utilities;\n",
"\n",
"//全局变量\n",
"var webApiBaseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n",
"var workDir = Environment.CurrentDirectory;\n",
"var fullPath = System.IO.Path.GetFullPath(\"./Publish/HttpClientStudy.WebApp/HttpClientStudy.WebApp.exe\", workDir);\n",
"//fullPath.Display();\n",
"\n",
"//启动已发布的WebApi项目\n",
"Console.WriteLine(\"启动WebApi项目\");\n",
"var startMessage = AppUtility.RunWebApiExeFile(fullPath);\n",
"Console.WriteLine(startMessage);\n"
]
},
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"source": [
"## 1、手动管理直接实例化-强烈不推荐"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"下面这种每次使用都实例化的用法是最常见、最`不推荐的`\n",
"\n",
"因为HttpClient刚推出时不成熟及微软官方文档的示例代码是这种用法再加上这种是最简单方便的使用方法就造成很多人使用这种用法。\n",
"这种方法有如下缺点:\n",
" 1. 每次使用都实例化,造成性能开销大、容易内存泄露;\n",
" 2. 并发量大、请求频繁时:网络端口会被耗尽 `Using包HttpClient,也只是在应用进程中释放了HttpClient实例但http请求/响应是跨操作系统和网络的,而系统及网络问题在进程之上,不是进程所能处理的。`\n",
" \n",
"优点:\n",
" 1. 使用简单,好学易用;\n",
" 2. 并发量小且请求不频繁时,问题不大;"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"/*\n",
" 每次请求都实例化:并发量大、请求频繁进会耗尽套接字端口\n",
"*/\n",
"{ \n",
" var baseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n",
"\n",
" using(var client = new HttpClient())\n",
" {\n",
" //发送请求\n",
" var response = await client.GetAsync(baseUrl);\n",
" response.EnsureSuccessStatusCode();\n",
" }\n",
"\n",
" //显示句柄\n",
" var displayValue = display($\"第 1 次请求,成功!\");\n",
"\n",
" for(int i=0;i<10;i++)\n",
" {\n",
" using(var client = new HttpClient())\n",
" {\n",
" var response = await client.GetAsync(baseUrl);\n",
" response.EnsureSuccessStatusCode();\n",
" displayValue.Update($\"第 {i+1} 次/ 共 10 次请求,成功!\");\n",
" }\n",
" }\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2、手动管理静态类或单例"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"相比于直接new,实现了HttpClient的重用`不推荐的`\n",
"\n",
"缺点:\n",
" 1. 不够灵活、优雅:特别是有多个系列的请求时;\n",
" \n",
"优点:\n",
" 1. 复用 HttpClient\n",
" 2. 实现了HttpClient的重用减少创建和销毁的开销"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"/*\n",
" 静态类/属性\n",
"*/\n",
"\n",
"public class HttpClientHelper\n",
"{\n",
" public readonly static HttpClient StaticClient;\n",
"\n",
" static HttpClientHelper()\n",
" {\n",
" SocketsHttpHandler handler = new SocketsHttpHandler()\n",
" {\n",
" PooledConnectionLifetime = TimeSpan.FromSeconds(30),\n",
" };\n",
"\n",
" StaticClient = new HttpClient(handler);\n",
"\n",
" //统一设置:请求头等\n",
"\n",
" //统一错误处理\n",
"\n",
" //当然这里也可以设置Pipline不过这里就不演示了\n",
" } \n",
"\n",
" public static async Task<HttpResponseMessage> GetAsync(string url)\n",
" {\n",
" return await StaticClient.GetAsync(url);\n",
" }\n",
"\n",
" public static async Task<string> GetStringAsync(string url)\n",
" {\n",
" var response = await StaticClient.GetAsync(url);\n",
" response.EnsureSuccessStatusCode();\n",
" return await response.Content.ReadAsStringAsync();\n",
" }\n",
"\n",
" public static async Task<HttpResponseMessage> PostAsync(string url, HttpContent content)\n",
" {\n",
" return await StaticClient.PostAsync(url, content);\n",
" }\n",
"}\n",
"\n",
"{ //调用静态类\n",
" var baseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n",
" var response = await HttpClientHelper.GetAsync(baseUrl+\"/api/Config/GetApiConfig\");\n",
" var content = await response.Content.ReadAsStringAsync();\n",
" Console.WriteLine(content);\n",
"\n",
" var response2 = await HttpClientHelper.GetStringAsync(baseUrl+\"/api/Normal/GetAllAccounts\");\n",
" Console.WriteLine(response2);\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"/*\n",
" 单例实现1:\n",
" 1. 私有构造函数,防止外部实例化\n",
" 2. 使用静态只读变量存储类的实例,由.Net框架保证实例不变且线程安全\n",
" 3. 密封类,拒绝继承,保证不被子类破坏\n",
"*/\n",
"\n",
"// 使用Lazy<T>实现单例\n",
"public sealed class HttpClientSingleton \n",
"{\n",
" // 私有静态变量,用于存储类的实例\n",
" private static readonly HttpClientSingleton instance = new HttpClientSingleton();\n",
" \n",
" //公共静态属性,用于获取类的实例\n",
" public static HttpClientSingleton Instance\n",
" {\n",
" get\n",
" {\n",
" return instance;\n",
" }\n",
" }\n",
"\n",
" private readonly HttpClient Client;\n",
"\n",
" //私有构造函数,防止外部实例化\n",
" private HttpClientSingleton() \n",
" {\n",
" SocketsHttpHandler handler = new SocketsHttpHandler()\n",
" {\n",
" PooledConnectionLifetime = TimeSpan.FromSeconds(30),\n",
" };\n",
"\n",
" Client = new HttpClient(handler);\n",
"\n",
" //统一设置:请求头等\n",
"\n",
" //统一错误处理\n",
"\n",
" //可以使用IoC容器来管理\n",
"\n",
" //当然这里也可以设置Pipline不过这里就不演示了\n",
" Console.WriteLine(\"HttpClientSingleton 初始化一次\");\n",
" }\n",
"\n",
" public async Task<HttpResponseMessage> GetAsync(string url)\n",
" {\n",
" return await Client.GetAsync(url);\n",
" }\n",
"\n",
" public async Task<string> GetStringAsync(string url)\n",
" {\n",
" var response = await Client.GetAsync(url);\n",
" response.EnsureSuccessStatusCode();\n",
" return await response.Content.ReadAsStringAsync();\n",
" }\n",
"}\n",
"\n",
"{ //调用示例\n",
"\n",
" var baseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n",
" var response = await HttpClientSingleton.Instance.GetAsync(baseUrl+\"/api/Config/GetApiConfig\");\n",
" var content = await response.Content.ReadAsStringAsync();\n",
" Console.WriteLine(content);\n",
"\n",
" var response2 = await HttpClientSingleton.Instance.GetStringAsync(baseUrl+\"/api/Normal/GetAllAccounts\");\n",
" Console.WriteLine(response2);\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"/*\n",
" 单例实现2:\n",
" 1. 私有构造函数,防止外部实例化\n",
" 2. 使用Lazy<T>, 延迟实例化, 由.Net 框架保证线程安全\n",
" 3. 密封类,拒绝继承,保证不被子类破坏\n",
"*/\n",
"\n",
"// 由于静态初始化器是由 .NET 运行时在后台处理的,因此它是线程安全的,不需要额外的锁定机制。\n",
"public sealed class HttpClientSingleton2\n",
"{\n",
" private static readonly Lazy<HttpClient> _httpClientLazy = new Lazy<HttpClient>(() =>\n",
" {\n",
" SocketsHttpHandler handler = new SocketsHttpHandler()\n",
" {\n",
" PooledConnectionLifetime = TimeSpan.FromSeconds(30)\n",
" };\n",
"\n",
" var client = new HttpClient(handler)\n",
" {\n",
" // 可以在这里配置HttpClient的实例例如设置超时时间、基地址等\n",
" //Timeout = TimeSpan.FromSeconds(30),\n",
" //BaseAddress = new Uri(\"https://api.example.com/\"),\n",
" };\n",
"\n",
"\n",
" //统一设置:请求头等\n",
"\n",
" //统一错误处理\n",
"\n",
" //可以使用IoC容器来管理\n",
"\n",
" //当然这里也可以设置Pipline不过这里就不演示了\n",
" Console.WriteLine(\"HttpClientSingleton2 初始化一次\");\n",
"\n",
" return client;\n",
" });\n",
"\n",
" public static HttpClient Instance => _httpClientLazy.Value;\n",
"\n",
" // 私有构造函数,防止外部实例化\n",
" private HttpClientSingleton2() { } \n",
"\n",
" public async Task<HttpResponseMessage> GetAsync(string url)\n",
" {\n",
" return await _httpClientLazy.Value.GetAsync(url);\n",
" }\n",
"\n",
" public async Task<string> GetStringAsync(string url)\n",
" {\n",
" var response = await _httpClientLazy.Value.GetAsync(url);\n",
" response.EnsureSuccessStatusCode();\n",
" return await response.Content.ReadAsStringAsync();\n",
" }\n",
"}\n",
"\n",
"{ //调用示例\n",
"\n",
" var baseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n",
" var response = await HttpClientSingleton2.Instance.GetAsync(baseUrl+\"/api/Config/GetApiConfig\");\n",
" var content = await response.Content.ReadAsStringAsync();\n",
" Console.WriteLine(content);\n",
"\n",
" var response2 = await HttpClientSingleton2.Instance.GetStringAsync(baseUrl+\"/api/Normal/GetAllAccounts\");\n",
" Console.WriteLine(response2);\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3、手动管理多工具类(每类请求对应一种工具类或单例类)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"把不同类别的请求分成不同的工具类,业务类直接封装成工具类的方法。类似类型化的客户端。 简单使用的话,`比较推荐`\n",
"\n",
"优点:\n",
" 1. 复用HttpClient\n",
" 2. 可以灵活的进行统一配置\n",
" 3. 不同类别不同工具类,方便定制\n",
" 4. 业务直接封装成工具类方法,调用方便、快捷\n",
"\n",
"缺点:\n",
" 1. 工具类比较多,需要手动维护\n",
" 2. 工具类方法比较多且和业务直接相关,需要手动维护\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"// 百度服务类\n",
"public sealed class BaiduService \n",
"{\n",
" private readonly HttpClient _httpClient;\n",
" public BaiduService()\n",
" {\n",
" //初始化httpClient\n",
" var baseHander = new SocketsHttpHandler() \n",
" { \n",
" MaxConnectionsPerServer = 1000 \n",
" };\n",
"\n",
" _httpClient = new HttpClient(baseHander)\n",
" {\n",
" Timeout = TimeSpan.FromSeconds(10),\n",
" BaseAddress = new Uri(\"http://www.baidu.com\"),\n",
" };\n",
" }\n",
"\n",
" ///// <summary>\n",
" /// 获取百度首页长度\n",
" /// </summary>\n",
" public async Task<int> GetIndexLengthAsync(string url)\n",
" {\n",
" var response = await _httpClient.GetAsync(url);\n",
" response.EnsureSuccessStatusCode();\n",
" var result = await response.Content.ReadAsStringAsync();\n",
" return result.Length;\n",
" }\n",
"}\n",
"//调用示例\n",
"{\n",
" var service = new BaiduService();\n",
" var result = await service.GetIndexLengthAsync(\"/\");\n",
" Console.WriteLine(result);\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"// 本机服务类\n",
"// 百度服务类\n",
"public sealed class LocalService \n",
"{\n",
" private readonly HttpClient _httpClient;\n",
" public LocalService()\n",
" {\n",
" //初始化httpClient\n",
" var baseHander = new SocketsHttpHandler() \n",
" { \n",
" MaxConnectionsPerServer = 1000 \n",
" };\n",
"\n",
" _httpClient = new HttpClient(baseHander)\n",
" {\n",
" Timeout = TimeSpan.FromSeconds(10),\n",
" BaseAddress = new Uri(WebApiConfigManager.GetWebApiConfig().BaseUrl),\n",
" };\n",
" }\n",
"\n",
" ///// <summary>\n",
" /// 获取百度首页长度\n",
" /// </summary>\n",
" public async Task<string> GetIndexAsync(string url)\n",
" {\n",
" var response = await _httpClient.GetAsync(url);\n",
" response.EnsureSuccessStatusCode();\n",
" var result = await response.Content.ReadAsStringAsync();\n",
" return result;\n",
" }\n",
"}\n",
"//调用示例\n",
"{\n",
" var service2 = new LocalService();\n",
" var result = await service2.GetIndexAsync(\"/api/Simple/GetAccount\");\n",
" Console.WriteLine(result);\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4、手动管理可复原(Polly)请求"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"#r \"nuget:Polly\"\n",
"#r \"nuget:Microsoft.Extensions.Http.Polly\"\n",
"\n",
"using Polly;\n",
"using Polly.Simmy;\n",
"using Polly.Retry;\n",
"using Polly.Extensions;\n",
"{\n",
" var pipleLine = new ResiliencePipelineBuilder()\n",
" .AddRetry(new RetryStrategyOptions()\n",
" {\n",
" ShouldHandle = new PredicateBuilder().Handle<Exception>(),\n",
" MaxRetryAttempts = 3, // Retry up to 3 times\n",
" OnRetry = args =>\n",
" {\n",
" // Due to how we have defined ShouldHandle, this delegate is called only if an exception occurred.\n",
" // Note the ! sign (null-forgiving operator) at the end of the command.\n",
" var exception = args.Outcome.Exception!; // The Exception property is nullable\n",
" Console.WriteLine(\"内部重试\");\n",
" return default;\n",
" }\n",
" })\n",
" .Build();\n",
"\n",
" var BaseUrl = WebApiConfigManager.GetWebApiConfig().BaseUrl;\n",
" HttpClient client = new HttpClient(new SocketsHttpHandler(){})\n",
" {\n",
" BaseAddress = new Uri(BaseUrl),\n",
" };\n",
"\n",
" try\n",
" {\n",
" await pipleLine.ExecuteAsync(async (inneerToken)=>\n",
" {\n",
" var response = await client.GetAsync(\"api/Polly8/Retry_Exception\",inneerToken);\n",
" response.EnsureSuccessStatusCode();\n",
" });\n",
" }\n",
" catch(Exception ex)\n",
" {\n",
" Console.WriteLine(ex.Message);\n",
" }\n",
" finally\n",
" {\n",
"\n",
" }\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5、IoC容器管理"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 直接注册IoC"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"/*\n",
" 注意:\n",
" 1、直接IoC管理只能一个不太方便\n",
" 2、可使用.NET 8+ 的 KeyedService, 可以管理多个。 老版只能用服务集合,勉强能用;\n",
" 3、把HttpClient 放在多个类中,分别注册使用;不过这样,不如直接使用类型化客户端;\n",
"*/\n",
"{ // 直接使用\n",
" var services = new ServiceCollection();\n",
" services.AddSingleton<HttpClient>(new HttpClient()\n",
" {\n",
" //BaseAddress = new Uri(\"https://localhost:5001/\"),\n",
" Timeout = TimeSpan.FromSeconds(10),\n",
" });\n",
"\n",
" var client = services.BuildServiceProvider().GetRequiredService<HttpClient>();\n",
"\n",
" var resp = await client.GetAsync(\"https://www.baidu.com\");\n",
" resp.EnsureSuccessStatusCode();\n",
"\n",
" var content = await resp.Content.ReadAsStringAsync();\n",
" Console.WriteLine(content.Length);\n",
"}\n",
"\n",
"{ // KeyService: .Net 8+ 才支持的功能\n",
" var services = new ServiceCollection();\n",
" services\n",
" .AddKeyedSingleton<HttpClient>(\"HttpClientA\",new HttpClient()\n",
" {\n",
" BaseAddress = new Uri(\"https://www.baidu.com/\"),\n",
" Timeout = TimeSpan.FromSeconds(10),\n",
" })\n",
" .AddKeyedSingleton<HttpClient>(\"HttpClientB\", new HttpClient()\n",
" {\n",
" BaseAddress = new Uri(\"https://www.qq.com/\"),\n",
" Timeout = TimeSpan.FromSeconds(2),\n",
" });\n",
"\n",
" var clientA = services.BuildServiceProvider().GetRequiredKeyedService<HttpClient>(\"HttpClientA\");\n",
" var responseA = await clientA.GetAsync(\"/\");\n",
" responseA.EnsureSuccessStatusCode();\n",
" var contentA = await responseA.Content.ReadAsStringAsync();\n",
" Console.WriteLine(contentA.Length);\n",
"\n",
" var clientB = services.BuildServiceProvider().GetRequiredKeyedService<HttpClient>(\"HttpClientB\");\n",
"\n",
" var responseB = await clientB.GetAsync(\"/\");\n",
" responseB.EnsureSuccessStatusCode();\n",
"\n",
" var contentB= await responseB.Content.ReadAsStringAsync();\n",
" Console.WriteLine(contentB.Length);\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### HttpClient 多服务类"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"// IoC 多个HttpClient服务类\n",
"\n",
"public class HttpClientServerA\n",
"{\n",
" public static HttpClient Client = new HttpClient()\n",
" {\n",
" BaseAddress = new Uri(\"https://www.baidu.com/\"),\n",
" Timeout = TimeSpan.FromSeconds(2),\n",
" };\n",
"\n",
" public int GetBaiduIndexLength()\n",
" {\n",
" var requestMessage = new HttpRequestMessage(HttpMethod.Get, \"/\");\n",
"\n",
" var response = Client.Send(requestMessage);\n",
"\n",
" response.EnsureSuccessStatusCode();\n",
"\n",
" var s = response.Content.ReadAsStream();\n",
" return (int)s.Length;\n",
" }\n",
"}\n",
"\n",
"public class HttpClientServerB\n",
"{\n",
" public static HttpClient Client = new HttpClient()\n",
" {\n",
" BaseAddress = new Uri(\"https://www.qq.com/\"),\n",
" Timeout = TimeSpan.FromSeconds(2),\n",
" };\n",
"\n",
" public int GetBaiduIndexLength()\n",
" {\n",
" var requestMessage = new HttpRequestMessage(HttpMethod.Get, \"/\");\n",
"\n",
" var response = Client.Send(requestMessage);\n",
"\n",
" response.EnsureSuccessStatusCode();\n",
"\n",
" var s = response.Content.ReadAsStream();\n",
" return (int)s.Length;\n",
" }\n",
"}\n",
"\n",
"{\n",
" var services = new ServiceCollection();\n",
" services.AddScoped<HttpClientServerA>();\n",
" services.AddScoped<HttpClientServerB>();\n",
"\n",
" var provider = services.BuildServiceProvider();\n",
"\n",
" var clientA = provider.GetService<HttpClientServerA>();\n",
" var sumA = clientA.GetBaiduIndexLength();\n",
" Console.WriteLine($\"A: {sumA}\");\n",
"\n",
" var clientB = provider.GetService<HttpClientServerB>();\n",
" var sumB = clientB.GetBaiduIndexLength();\n",
" Console.WriteLine($\"A: {sumB}\");\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 6、客户端工厂管理IHttpClientFactory(需要结合IoC) `强力推荐`"
]
},
{
"cell_type": "markdown",
"metadata": {
"vscode": {
"languageId": "polyglot-notebook"
}
},
"source": [
"使用 IHttpClientFactory 创建和管理 `短期HttpClient` 是官方强力推荐的方式。特别是使用IoC或是 ASP.NET中后台调用其它接口的情况。\n",
"\n",
"IHttpClientFactory 综合使用了 HttpClient的多种特性HttpClient的生命周期、HttpClient的配置、HttpClient的拦截器、HttpClient的缓存、HttpClient的依赖注入、Polly等等。\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"//所有HttpClient配置\n",
"{\n",
" var services = new ServiceCollection();\n",
" //添加一个委托用于配置所有HttpClient实例。只执行一次。\n",
" services.ConfigureHttpClientDefaults(builder => \n",
" {\n",
" //builder.UseSocketsHttpHandler();\n",
" //builder.SetHandlerLifetime(TimeSpan.FromMinutes(5));\n",
" \n",
" builder.ConfigureHttpClient(hc =>\n",
" {\n",
" hc.BaseAddress = new Uri(webApiBaseUrl);\n",
" });\n",
" });\n",
"\n",
" var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();\n",
"\n",
" var client = factory.CreateClient();\n",
" var response = await client.GetAsync(\"/api/hello/ping\");\n",
" var data = await response.Content.ReadAsStringAsync();\n",
" Console.WriteLine(data);\n",
"}\n",
"\n",
"/*\n",
" 默认 HttpClient\n",
" 1、配置默认HttpClient时AddHttpClient(Action)的Action不会被执行ConfigureHttpClient()也不执行。原因暂时不明\n",
" 2、想配置默认HttpClient可以使用ConfigureHttpClientDefaults方法但此方法针对所有HttpClient实例(包括命名HttpClient)\n",
"*/\n",
"{\n",
" var services = new ServiceCollection();\n",
"\n",
" //默认HttpClient\n",
" services.AddHttpClient<HttpClient>();\n",
"\n",
" //这种形式,不生效\n",
" // services.AddHttpClient<HttpClient>((provider, client) => \n",
" // {\n",
" // client.BaseAddress = new Uri(webApiBaseUrl);\n",
" // });\n",
"\n",
" //这种形式ConfigureHttpClient 也不生效\n",
" // services.AddHttpClient<HttpClient>()\n",
" // .ConfigureHttpClient(config=>\n",
" // {\n",
" // config.BaseAddress = new Uri(webApiBaseUrl);\n",
" // });\n",
"\n",
" var provider = services.BuildServiceProvider();\n",
"\n",
" var factory = provider.GetRequiredService<IHttpClientFactory>();\n",
"\n",
" var response = await factory.CreateClient().GetAsync(webApiBaseUrl+\"/api/hello/index\");\n",
" var content = await response.Content.ReadAsStringAsync();\n",
" \n",
" Console.WriteLine(content);\n",
"}\n",
"\n",
"//命名 HttpClient\n",
"{\n",
" var clientA =\"httpClientA\";\n",
" var clientB =\"httpClientB\";\n",
"\n",
" var services = new ServiceCollection();\n",
" services.AddHttpClient<HttpClient>(clientA, (provider, client) => \n",
" {\n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" });\n",
" services.AddHttpClient<HttpClient>(clientB, (provider, client) => \n",
" {\n",
" client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;\n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" })\n",
" .ConfigureHttpClient(client=>\n",
" {\n",
" client.Timeout = TimeSpan.FromSeconds(1);\n",
" client.DefaultRequestVersion = new Version(1, 1);\n",
" });\n",
"\n",
" var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();\n",
"\n",
" var responseA = await factory.CreateClient(clientA).GetAsync(\"/api/hello/ping\");\n",
" var contentA = await responseA.Content.ReadAsStringAsync();\n",
" Console.WriteLine(contentA);\n",
"\n",
" var responseB = await factory.CreateClient(clientB).PostAsync(\"/api/hello/post\",null);\n",
" var contentB = await responseB.Content.ReadAsStringAsync();\n",
" Console.WriteLine(contentB);\n",
"}\n",
"\n",
"// 类型化客户端 HttpClient\n",
"public class HttpClientServiceA\n",
"{\n",
" public HttpClient Client { get; }\n",
" public HttpClientServiceA(HttpClient client)\n",
" {\n",
" Client = client;\n",
" Console.WriteLine(\"HttpClientServiceA => 构造函数执行一次\");\n",
" }\n",
"\n",
" public async Task<string> GetIndexAsync()\n",
" {\n",
" var response = await Client.GetAsync(\"/api/hello/index\");\n",
" var content = await response.Content.ReadAsStringAsync();\n",
" return content;\n",
" }\n",
"}\n",
"\n",
"public class HttpClientServiceB\n",
"{\n",
" public HttpClient Client { get; }\n",
" public HttpClientServiceB(HttpClient client)\n",
" {\n",
" Client = client;\n",
" Console.WriteLine(\"HttpClientServiceB => 构造函数执行一次\");\n",
" }\n",
"\n",
" public async Task<string> PingAsync()\n",
" {\n",
" var response = await Client.GetAsync(\"/api/hello/Ping\");\n",
" var content = await response.Content.ReadAsStringAsync();\n",
" return content;\n",
" }\n",
"}\n",
"\n",
"{\n",
" var services = new ServiceCollection();\n",
" services.AddHttpClient<HttpClientServiceA>(client => \n",
" {\n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" Console.WriteLine(\"HttpClientServiceA => AddHttpClient 执行一次\");\n",
" })\n",
" .ConfigureHttpClient(client=>\n",
" {\n",
" client.Timeout = TimeSpan.FromSeconds(1);\n",
" Console.WriteLine(\"HttpClientServiceA => ConfigureHttpClient 执行一次\");\n",
" });\n",
"\n",
" services.AddHttpClient<HttpClientServiceB>(client => \n",
" {\n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" Console.WriteLine(\"HttpClientServiceB => AddHttpClient 执行一次\");\n",
" })\n",
" .ConfigureHttpClient(client=>\n",
" {\n",
" client.Timeout = TimeSpan.FromSeconds(2);\n",
" Console.WriteLine(\"HttpClientServiceB => ConfigureHttpClient 执行一次\");\n",
" });\n",
"\n",
" var builder = services.BuildServiceProvider();\n",
"\n",
" var serviceA = builder.GetRequiredService<HttpClientServiceA>();\n",
" var serviceB = builder.GetRequiredService<HttpClientServiceB>();\n",
" //每获取一次类型化客户端,都会执行一交。\n",
" var serviceB2 = builder.GetRequiredService<HttpClientServiceB>();\n",
"\n",
" var dataA = await serviceA.GetIndexAsync();\n",
" Console.WriteLine(dataA);\n",
"\n",
" var dataB = await serviceB.PingAsync();\n",
" Console.WriteLine(dataB);\n",
"\n",
" var dataB2 = await serviceB2.PingAsync();\n",
" Console.WriteLine(dataB2);\n",
"}\n",
"\n",
"// 类型化客户端2\n",
"{\n",
" var services = new ServiceCollection();\n",
" services.AddHttpClient<HttpClientServiceA>(client => \n",
" {\n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" Console.WriteLine(\"HttpClientServiceA => AddHttpClient 执行一次\");\n",
" })\n",
" .AddTypedClient<HttpClientServiceA>()\n",
" .ConfigureHttpClient(client=>\n",
" {\n",
" client.Timeout = TimeSpan.FromSeconds(1);\n",
" Console.WriteLine(\"HttpClientServiceA => ConfigureHttpClient 执行一次\");\n",
" });\n",
"}\n",
"\n",
"//todo: 生成式 HttpClient: Refit库\n",
"\n",
"/*\n",
" IFactoryHttpClient + Polly8\n",
" 1、引用库 Polly 和 Microsoft.Extensions.Http.Polly\n",
"*/\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 7、综合管理工厂 + 类型化客户端 请求管道 + Polly(默认使用 连接池和IoC容器)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".NET (C#)",
"language": "C#",
"name": ".net-csharp"
},
"language_info": {
"name": "python"
},
"polyglot_notebook": {
"kernelInfo": {
"defaultKernelName": "csharp",
"items": [
{
"aliases": [],
"name": "csharp"
}
]
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}