diff --git a/Docs/1.3.0.基础使用.管理客户端.ipynb b/Docs/1.3.0.基础使用.管理客户端.ipynb
index c2694db..5554b89 100644
--- a/Docs/1.3.0.基础使用.管理客户端.ipynb
+++ b/Docs/1.3.0.基础使用.管理客户端.ipynb
@@ -32,7 +32,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
@@ -44,26 +44,7 @@
"languageId": "polyglot-notebook"
}
},
- "outputs": [
- {
- "data": {
- "text/html": [
- "
Installed Packages- Microsoft.Extensions.DependencyInjection, 8.0.0
- Microsoft.Extensions.Http, 8.0.0
- Microsoft.Extensions.Http.Polly, 8.0.7
- Polly, 8.4.1
- Refit, 7.1.2
- Refit.HttpClientFactory, 7.1.2
- System.Net.Http.Json, 8.0.0
"
- ]
- },
- "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"
- ]
- }
- ],
+ "outputs": [],
"source": [
"//全局设置,行运行一次,为后续准备\n",
"#r \"nuget:System.Net.Http.Json\"\n",
@@ -855,6 +836,159 @@
"IHttpClientFactory 综合使用了 HttpClient的多种特性:HttpClient的生命周期、HttpClient的配置、HttpClient的拦截器、HttpClient的缓存、HttpClient的依赖注入、Polly等等。\n"
]
},
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 默认客户端"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "从使用推测,设计 IHttpClientFactory 时,重点应该是使用 “命名客户端” 或 “类型化客户端” 而不是默认客户端。 \n",
+ "\n",
+ "只有 AddHttpClient() 扩展方法返回 IServiceCollection;其它相关扩展方法( AddHttpClient())均返回 IHttpClientBuilder,明显针对命名客户端。\n",
+ "AddHttpClient() 相当于注册了基本框架;而命名客户端中,名称为空(\"\"或string.Empty)的,相当于默认客户端。\n",
+ "\n",
+ "有一个 名为 `ConfigureHttpClientDefaults` 的 ServiceCollection 对象的扩展方法,用于配置所有HttpClient实例,并且只在初始化时执行一次。如果只使用一个默认客户端的话,可以使用 ConfigureHttpClientDefaults 和 AddHttpClient() 配合使用,也能达到默认客户端的配置效果。 "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "//方式1:默认客户端\n",
+ "{ \n",
+ " var services = new ServiceCollection();\n",
+ " /*\n",
+ " AddHttpClient() 返回 ServiceCollection,可以继续添加其他客户端。\n",
+ " 其它方法则返回IHttpClientBuilder,后结配置的扩展方法,只能针对当前前端那个命名命令端。\n",
+ " */\n",
+ " services.AddHttpClient();\n",
+ "\n",
+ " var factory = services.BuildServiceProvider().GetRequiredService();\n",
+ "\n",
+ " var client = factory.CreateClient();\n",
+ " //或者\n",
+ " var client2 = factory.CreateClient(\"\");\n",
+ " //或者 内部都是使用CreateClient(string.Empty),表示默认客户端。\n",
+ " var client3 = factory.CreateClient(string.Empty);\n",
+ "\n",
+ " var response = await client.GetAsync(webApiBaseUrl + \"/api/hello/index\");\n",
+ " response.EnsureSuccessStatusCode();\n",
+ " var data = await response.Content.ReadAsStringAsync();\n",
+ " data.Display();\n",
+ "}\n",
+ "\n",
+ "//方式2:默认客户端 + 默认配置\n",
+ "{ \n",
+ " var services = new ServiceCollection();\n",
+ "\n",
+ " //默认客户端\n",
+ " services.AddHttpClient();\n",
+ "\n",
+ " //配置所有客户端\n",
+ " services.ConfigureHttpClientDefaults(builder => \n",
+ " {\n",
+ " //配置构建器\n",
+ " //builder.AddDefaultLogger();\n",
+ "\n",
+ " //配置客户端\n",
+ " builder.ConfigureHttpClient(c=>\n",
+ " {\n",
+ " c.BaseAddress = new Uri(webApiBaseUrl);\n",
+ " });\n",
+ " });\n",
+ "\n",
+ " var factory = services.BuildServiceProvider().GetRequiredService();\n",
+ " var client = factory.CreateClient();\n",
+ " var response = await client.GetAsync(\"/api/hello/ping\");\n",
+ " response.EnsureSuccessStatusCode();\n",
+ " var data = await response.Content.ReadAsStringAsync();\n",
+ " data.Display();\n",
+ "}\n",
+ "\n",
+ "//方式3(推荐):默认客户端:直接使用名称为 string.empty 的命名客户端\n",
+ "{\n",
+ " var services = new ServiceCollection();\n",
+ "\n",
+ " //默认客户端\n",
+ " services\n",
+ " .AddHttpClient(string.Empty)\n",
+ " //这样后续的配置,都是针对 string.empty 的客户端,可以使用全部配置功能\n",
+ " .ConfigureHttpClient(c=>c.BaseAddress = new Uri(webApiBaseUrl))\n",
+ " .AddDefaultLogger();\n",
+ "\n",
+ " var factory = services.BuildServiceProvider().GetRequiredService();\n",
+ " var client = factory.CreateClient();\n",
+ " var response = await client.GetAsync(\"/api/hello/ping\");\n",
+ " response.EnsureSuccessStatusCode();\n",
+ " var data = await response.Content.ReadAsStringAsync();\n",
+ " data.Display();\n",
+ "}\n",
+ "\n",
+ "//错误用法\n",
+ "{\n",
+ " var services = new ServiceCollection();\n",
+ "\n",
+ " //默认客户端\n",
+ " services\n",
+ " //没有参数时,导致后面配置不起使用;\n",
+ " //参数必须为 空字符串或string.Empty,后续的配置才能起使用\n",
+ "\n",
+ " .AddHttpClient()\n",
+ " //没有参数时,导致后面配置不起使用\n",
+ " .ConfigureHttpClient(c=>c.BaseAddress = new Uri(webApiBaseUrl))\n",
+ " .AddDefaultLogger();\n",
+ "\n",
+ " var factory = services.BuildServiceProvider().GetRequiredService();\n",
+ " var client = factory.CreateClient();\n",
+ "\n",
+ " try\n",
+ " {\n",
+ " var response = await client.GetAsync(\"/api/hello/ping\");\n",
+ " response.EnsureSuccessStatusCode();\n",
+ " var data = await response.Content.ReadAsStringAsync();\n",
+ " data.Display();\n",
+ " }\n",
+ " catch(InvalidOperationException ex)\n",
+ " {\n",
+ " Console.WriteLine($\"没有参数的配置:AddHttpClient(),因后续配置中,赋值 BaseAddress 不起使用,出现异常:{Environment.NewLine}{ex.Message}\");\n",
+ " }\n",
+ " catch(Exception ex)\n",
+ " {\n",
+ "\n",
+ " Console.WriteLine(ex.Message);\n",
+ " }\n",
+ " finally\n",
+ " {\n",
+ " client.Dispose();\n",
+ " }\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 默认全局配置"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "ConfigureHttpClientDefaults 扩展方法,添加一个委托,用于配置所有HttpClient实例。只执行一次。"
+ ]
+ },
{
"cell_type": "code",
"execution_count": null,
@@ -871,10 +1005,11 @@
},
"outputs": [],
"source": [
- "//所有HttpClient配置\n",
+ "//全局配置:所有HttpClient配置\n",
"{\n",
" var services = new ServiceCollection();\n",
- " //添加一个委托,用于配置所有HttpClient实例。只执行一次。\n",
+ " //添加一个委托,用于配置所有HttpClient实例。\n",
+ " //只执行一次,而非每次CreateClient,都会执行一次。\n",
" services.ConfigureHttpClientDefaults(builder => \n",
" {\n",
" //builder.UseSocketsHttpHandler();\n",
@@ -884,60 +1019,83 @@
" {\n",
" hc.BaseAddress = new Uri(webApiBaseUrl);\n",
" });\n",
- " });\n",
- "\n",
- " var factory = services.BuildServiceProvider().GetRequiredService();\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",
+ " Console.WriteLine(\"ConfigureHttpClientDefaults 只执行一次!\");\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",
+ " services\n",
+ " .AddHttpClient(\"client_a\")\n",
+ " .ConfigureHttpClient(hc => \n",
+ " {\n",
+ " hc.DefaultRequestHeaders.Add(\"client_a\", \"client_a\");\n",
"\n",
- " //默认HttpClient\n",
- " services.AddHttpClient();\n",
+ " //可以覆盖默认配置\n",
+ " //hc.BaseAddress = new Uri(\"http://www.qq.com\");\n",
"\n",
- " //这种形式,不生效\n",
- " // services.AddHttpClient((provider, client) => \n",
- " // {\n",
- " // client.BaseAddress = new Uri(webApiBaseUrl);\n",
- " // });\n",
+ " Console.WriteLine(\"ConfigureHttpClient 每次 CreateClient 执行一次!\");\n",
+ " });\n",
"\n",
- " //这种形式,ConfigureHttpClient 也不生效\n",
- " // services.AddHttpClient()\n",
- " // .ConfigureHttpClient(config=>\n",
- " // {\n",
- " // config.BaseAddress = new Uri(webApiBaseUrl);\n",
- " // });\n",
+ " \n",
+ " var factory = services.BuildServiceProvider().GetRequiredService();\n",
"\n",
- " var provider = services.BuildServiceProvider();\n",
+ " //默认客户端\n",
+ " var defaultClient = factory.CreateClient();\n",
+ " var defaultResponse = await defaultClient.GetAsync(\"/api/hello/ping\");\n",
+ " var defaultData = await defaultResponse.Content.ReadAsStringAsync();\n",
+ " Console.WriteLine(defaultData);\n",
"\n",
- " var factory = provider.GetRequiredService();\n",
+ " //命名客户端\n",
+ " var namedClient = factory.CreateClient(\"client_a\");\n",
+ " var namedResponse = await namedClient.GetAsync(\"/api/hello/get\");\n",
+ " var namedData = await namedResponse.Content.ReadAsStringAsync();\n",
+ " Console.WriteLine(namedData);\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",
+ " _ = factory.CreateClient(\"client_a\");\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 命名客户端(推荐用法)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "命名客户端,应该是官方推荐的方法。名称为空字符串或string.Empty时,可以为是默认命名客户端,factory.CreateClient()创建的就是这个默认客户端(或者factory.CreateClient(\"\"))。"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [],
+ "source": [
+ "//命名客户端\n",
"{\n",
" var clientA =\"httpClientA\";\n",
" var clientB =\"httpClientB\";\n",
"\n",
" var services = new ServiceCollection();\n",
+ "\n",
+ " services.AddHttpClient(string.Empty, (provider, client) => \n",
+ " {\n",
+ " client.BaseAddress = new Uri(webApiBaseUrl);\n",
+ " });\n",
+ "\n",
" services.AddHttpClient(clientA, (provider, client) => \n",
" {\n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" });\n",
+ "\n",
" services.AddHttpClient(clientB, (provider, client) => \n",
" {\n",
" client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;\n",
@@ -951,15 +1109,57 @@
"\n",
" var factory = services.BuildServiceProvider().GetRequiredService();\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",
+ " //name=string.Empty\n",
+ " var defaultClient = factory.CreateClient();\n",
+ " var defaultResponse = await defaultClient.GetAsync(\"/api/hello/ping\");\n",
+ " var defaultData = await defaultResponse.Content.ReadAsStringAsync();\n",
+ " Console.WriteLine(defaultData);\n",
+ "\n",
+ " //name=clientA\n",
+ " var httpClient_a = factory.CreateClient(clientA);\n",
+ " var responseA = await httpClient_a.GetAsync(\"/api/hello/ping\");\n",
+ " var dataA = await responseA.Content.ReadAsStringAsync();\n",
+ " dataA.Display();\n",
+ "\n",
+ " //name=clientB\n",
+ " var httpClient_B = factory.CreateClient(clientB);\n",
+ " var responseB = await httpClient_B.GetAsync(\"/api/hello/ping\");\n",
+ " var dataB = await responseB.Content.ReadAsStringAsync();\n",
+ " dataB.Display();\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 类型化客户端 (推荐)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "source": [
+ "类型化的客户端,两种基本使用方式:\n",
+ "1、可以单独使用(直接IoC容器)\n",
+ "2、与IFactoryHttpClient配合使用(依赖注入),目的是:从统一的工厂配置中获取客户端,作为 HttpClient 类型的实参,传给类型化客户端的构造函数。\n",
+ " 换名话说:从工厂获取HttpClient实例,设置为 类型化客户端类的 HttpClient,在其内部使用。"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [],
+ "source": [
"// 类型化客户端 HttpClient\n",
"public class HttpClientServiceA\n",
"{\n",
@@ -995,13 +1195,73 @@
" }\n",
"}\n",
"\n",
+ "// 方式1(不推荐):类型化客户端:直接注入IoC,并从中获取实例。优点是范围可以自己选择。\n",
"{\n",
+ " Console.WriteLine(\"方式1 -------------------------------------------------------------------\");\n",
+ " var services = new ServiceCollection();\n",
+ " services.AddSingleton(b => \n",
+ " { \n",
+ " return new HttpClientServiceA(new HttpClient(){BaseAddress = new Uri(webApiBaseUrl)});\n",
+ " });\n",
+ " services.AddScoped(b=> \n",
+ " {\n",
+ " return new HttpClientServiceB(new HttpClient(){BaseAddress = new Uri(webApiBaseUrl)});\n",
+ " });\n",
+ "\n",
+ " var builder = services.BuildServiceProvider();\n",
+ " var serverA = builder.GetRequiredService();\n",
+ " var serverB = builder.GetRequiredService();\n",
+ "\n",
+ " var dataA = await serverA.GetIndexAsync();\n",
+ " Console.WriteLine(dataA);\n",
+ "\n",
+ " var dataB = await serverB.PingAsync();\n",
+ " Console.WriteLine(dataB);\n",
+ "\n",
+ " Console.WriteLine(\"========================================================================\");\n",
+ "}\n",
+ "\n",
+ "// 方式2:类型化客户端:AddHttpClient<>() 设置\n",
+ "{\n",
+ " Console.WriteLine(\"方式2 -------------------------------------------------------------------\");\n",
+ " var services = new ServiceCollection();\n",
+ " services\n",
+ " .AddHttpClient()\n",
+ " .ConfigureHttpClient(client=>\n",
+ " {\n",
+ " client.BaseAddress = new Uri(webApiBaseUrl);\n",
+ " });\n",
+ "\n",
+ " services\n",
+ " .AddHttpClient()\n",
+ " .ConfigureHttpClient(client=>\n",
+ " {\n",
+ " client.BaseAddress = new Uri(webApiBaseUrl);\n",
+ " });\n",
+ "\n",
+ " var builder = services.BuildServiceProvider();\n",
+ " var serverA = builder.GetRequiredService();\n",
+ " var serverB = builder.GetRequiredService();\n",
+ "\n",
+ " var dataA = await serverA.GetIndexAsync();\n",
+ " Console.WriteLine(dataA);\n",
+ "\n",
+ " var dataB = await serverB.PingAsync();\n",
+ " Console.WriteLine(dataB);\n",
+ "\n",
+ " Console.WriteLine(\"========================================================================\");\n",
+ "}\n",
+ "\n",
+ "// 方式3:类型化客户端:结合工厂,由工厂从统一配置中提供类型化客户端中使用的HttpClient实例。\n",
+ "{\n",
+ " Console.WriteLine(\"方式3 -------------------------------------------------------------------\");\n",
" var services = new ServiceCollection();\n",
" services.AddHttpClient(client => \n",
" {\n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" Console.WriteLine(\"HttpClientServiceA => AddHttpClient 执行一次\");\n",
" })\n",
+ " .AddTypedClient()\n",
" .ConfigureHttpClient(client=>\n",
" {\n",
" client.Timeout = TimeSpan.FromSeconds(1);\n",
@@ -1013,6 +1273,7 @@
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
" Console.WriteLine(\"HttpClientServiceB => AddHttpClient 执行一次\");\n",
" })\n",
+ " .AddTypedClient()\n",
" .ConfigureHttpClient(client=>\n",
" {\n",
" client.Timeout = TimeSpan.FromSeconds(2);\n",
@@ -1034,39 +1295,122 @@
"\n",
" var dataB2 = await serviceB2.PingAsync();\n",
" Console.WriteLine(dataB2);\n",
+ "\n",
+ " Console.WriteLine(\"========================================================================\");\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 管道配置"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 150,
+ "metadata": {
+ "vscode": {
+ "languageId": "polyglot-notebook"
+ }
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "LoggerDelegatingHandler -> SendAsync -> Before\n",
+ "LoggerDelegatingHandler -> SendAsync -> After\n",
+ "Pong\n"
+ ]
+ }
+ ],
+ "source": [
+ "//管道配置\n",
+ "\n",
+ "//日志中间件(管道类)\n",
+ "public class LoggerDelegatingHandler : DelegatingHandler\n",
+ "{\n",
+ " protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)\n",
+ " {\n",
+ " Console.WriteLine(\"LoggerDelegatingHandler -> Send -> Before\");\n",
+ "\n",
+ " HttpResponseMessage response = base.Send(request, cancellationToken);\n",
+ "\n",
+ " Console.WriteLine(\"LoggerDelegatingHandler -> Send -> After\");\n",
+ "\n",
+ " return response;\n",
+ " }\n",
+ "\n",
+ " protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n",
+ " {\n",
+ " Console.WriteLine(\"LoggerDelegatingHandler -> SendAsync -> Before\");\n",
+ "\n",
+ " HttpResponseMessage response = await base.SendAsync(request, cancellationToken);\n",
+ "\n",
+ " Console.WriteLine(\"LoggerDelegatingHandler -> SendAsync -> After\");\n",
+ "\n",
+ " return response;\n",
+ " }\n",
"}\n",
"\n",
- "// 类型化客户端2\n",
+ "//使用日志中间件\n",
"{\n",
" var services = new ServiceCollection();\n",
- " services.AddHttpClient(client => \n",
+ "\n",
+ " //先注册\n",
+ " services.AddTransient();\n",
+ "\n",
+ " services.AddHttpClient(string.Empty).ConfigureHttpClient(client =>\n",
" {\n",
" client.BaseAddress = new Uri(webApiBaseUrl);\n",
- " Console.WriteLine(\"HttpClientServiceA => AddHttpClient 执行一次\");\n",
" })\n",
- " .AddTypedClient()\n",
- " .ConfigureHttpClient(client=>\n",
+ " //配置SocketsHttpHandler\n",
+ " .UseSocketsHttpHandler((handler,provider) =>\n",
" {\n",
- " client.Timeout = TimeSpan.FromSeconds(1);\n",
- " Console.WriteLine(\"HttpClientServiceA => ConfigureHttpClient 执行一次\");\n",
- " });\n",
- "}\n",
+ " handler.ConnectTimeout = TimeSpan.FromSeconds(10);\n",
+ " handler.MaxConnectionsPerServer = 100;\n",
+ " handler.UseProxy = false;\n",
+ " handler.UseCookies = true;\n",
+ " handler.EnableMultipleHttp2Connections = true;\n",
+ " handler.SslOptions.RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true;\n",
+ " })\n",
+ " //使用前先在AddTransient范围注册\n",
+ " //.AddHttpMessageHandler()\n",
+ " .AddHttpMessageHandler();\n",
"\n",
- "//todo: 生成式 HttpClient: Refit库\n",
+ " var factory = services.BuildServiceProvider().GetService();\n",
"\n",
- "/*\n",
- " IFactoryHttpClient + Polly8\n",
- " 1、引用库 Polly 和 Microsoft.Extensions.Http.Polly\n",
- "*/\n",
+ " var client = factory.CreateClient();\n",
+ "\n",
+ " var response = await client.GetAsync(\"/api/hello/ping\");\n",
+ " response.EnsureSuccessStatusCode();\n",
"\n",
- "\n"
+ " var responseString = await response.Content.ReadAsStringAsync();\n",
+ " Console.WriteLine(responseString);\n",
+ "}"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### 日志配置"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## 7 工厂 + Polly V8"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
- "## 7、综合管理:工厂 + 类型化客户端 + 请求管道 + Polly(默认使用 连接池和IoC容器)"
+ "## 8、综合管理:工厂 + 类型化客户端 + 请求管道 + Polly(默认使用 连接池和IoC容器)"
]
}
],