{
 "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": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "//全局设置,行运行一次,为后续准备\n",
    "#r \"nuget:System.Net.Http.Json\"\n",
    "#r \"nuget:Microsoft.Net.Http.Headers\"\n",
    "#r \"nuget:Microsoft.Extensions.Http\"\n",
    "#r \"nuget:Microsoft.Extensions.DependencyInjection\"\n",
    "#r \"nuget:Microsoft.Extensions.Logging\" \n",
    "#r \"nuget:Microsoft.Extensions.Logging.Console\"\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.Json;\n",
    "\n",
    "global using Microsoft.Extensions.DependencyInjection;\n",
    "global using Microsoft.Extensions.DependencyInjection.Extensions;\n",
    "global using Microsoft.Extensions.Logging;\n",
    "global using Microsoft.Extensions.Logging.Console;\n",
    "global using Microsoft.Extensions.Http.Logging;\n",
    "\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",
    "global using Polly.Extensions.Http;\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/RetryException\",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": "markdown",
   "metadata": {},
   "source": [
    "### 默认客户端"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "从使用推测,设计 IHttpClientFactory 时,重点应该是使用 “命名客户端” 或 “类型化客户端” 而不是默认客户端。 \n",
    "\n",
    "只有 AddHttpClient() 扩展方法返回 IServiceCollection;其它相关扩展方法( AddHttpClient<HttpClient>())均返回 IHttpClientBuilder,明显针对命名客户端。\n",
    "AddHttpClient()  相当于注册了基本框架;而命名客户端中,名称为空(\"\"或string.Empty)的,相当于默认客户端。\n",
    "\n",
    "有一个 名为  `ConfigureHttpClientDefaults` 的 ServiceCollection 对象的扩展方法,用于配置所有HttpClient实例,并且只在初始化时执行一次。如果只使用一个默认客户端的话,可以使用 ConfigureHttpClientDefaults 和 AddHttpClient() 配合使用,也能达到默认客户端的配置效果。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "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<IHttpClientFactory>();\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<IHttpClientFactory>();\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<HttpClient>(string.Empty)\n",
    "        //这样后续的配置,都是针对 string.empty 的客户端,可以使用全部配置功能\n",
    "        .ConfigureHttpClient(c=>c.BaseAddress = new Uri(webApiBaseUrl))\n",
    "        .AddDefaultLogger();\n",
    "\n",
    "    var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();\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<HttpClient>()\n",
    "        //没有参数时,导致后面配置不起使用\n",
    "        .ConfigureHttpClient(c=>c.BaseAddress = new Uri(webApiBaseUrl))\n",
    "        .AddDefaultLogger();\n",
    "\n",
    "    var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();\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<HttpClient>(),因后续配置中,赋值 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,
   "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",
    "    //只执行一次,而非每次CreateClient,都会执行一次。\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",
    "        Console.WriteLine(\"ConfigureHttpClientDefaults 只执行一次!\");\n",
    "    });\n",
    "\n",
    "    //配置命名客户端\n",
    "    services\n",
    "        .AddHttpClient<HttpClient>(\"client_a\")\n",
    "        .ConfigureHttpClient(hc => \n",
    "        {\n",
    "            hc.DefaultRequestHeaders.Add(\"client_a\", \"client_a\");\n",
    "\n",
    "            //可以覆盖默认配置\n",
    "            //hc.BaseAddress = new Uri(\"http://www.qq.com\");\n",
    "\n",
    "            Console.WriteLine(\"ConfigureHttpClient 每次 CreateClient 执行一次!\");\n",
    "        });\n",
    "\n",
    "    \n",
    "    var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();\n",
    "\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",
    "    //命名客户端\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",
    "    _ = 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": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "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<HttpClient>(string.Empty, (provider, client) => \n",
    "    {\n",
    "        client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "    });\n",
    "\n",
    "    services.AddHttpClient<HttpClient>(clientA, (provider, client) => \n",
    "    {\n",
    "        client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "    });\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",
    "    //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": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "// 类型化客户端 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",
    "// 方式1(不推荐):类型化客户端:直接注入IoC,并从中获取实例。优点是范围可以自己选择。\n",
    "{\n",
    "    Console.WriteLine(\"方式1 -------------------------------------------------------------------\");\n",
    "    var services = new ServiceCollection();\n",
    "    services.AddSingleton<HttpClientServiceA>(b => \n",
    "    { \n",
    "        return new HttpClientServiceA(new HttpClient(){BaseAddress = new Uri(webApiBaseUrl)});\n",
    "    });\n",
    "    services.AddScoped<HttpClientServiceB>(b=> \n",
    "    {\n",
    "        return new HttpClientServiceB(new HttpClient(){BaseAddress = new Uri(webApiBaseUrl)});\n",
    "    });\n",
    "\n",
    "    var builder = services.BuildServiceProvider();\n",
    "    var serverA = builder.GetRequiredService<HttpClientServiceA>();\n",
    "    var serverB = builder.GetRequiredService<HttpClientServiceB>();\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<HttpClientServiceA>()\n",
    "        .ConfigureHttpClient(client=>\n",
    "        {\n",
    "            client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "        });\n",
    "\n",
    "    services\n",
    "        .AddHttpClient<HttpClientServiceB>()\n",
    "        .ConfigureHttpClient(client=>\n",
    "        {\n",
    "            client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "        });\n",
    "\n",
    "    var builder = services.BuildServiceProvider();\n",
    "    var serverA = builder.GetRequiredService<HttpClientServiceA>();\n",
    "    var serverB = builder.GetRequiredService<HttpClientServiceB>();\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<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",
    "    services.AddHttpClient<HttpClientServiceB>(client => \n",
    "    {\n",
    "        client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "        Console.WriteLine(\"HttpClientServiceB => AddHttpClient 执行一次\");\n",
    "    })\n",
    "    .AddTypedClient<HttpClientServiceB>()\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",
    "    Console.WriteLine(\"========================================================================\");\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 管道配置"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "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<HttpResponseMessage> 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",
    "//使用日志中间件\n",
    "{\n",
    "    var services = new ServiceCollection();\n",
    "\n",
    "    //先注册\n",
    "    services.AddTransient<LoggerDelegatingHandler>();\n",
    "\n",
    "    services.AddHttpClient<HttpClient>(string.Empty).ConfigureHttpClient(client =>\n",
    "    {\n",
    "        client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "    })\n",
    "    //配置SocketsHttpHandler\n",
    "    .UseSocketsHttpHandler((handler,provider) =>\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<LoggerDelegatingHandler>()\n",
    "    ;\n",
    "\n",
    "    var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
    "\n",
    "    var client = factory.CreateClient();\n",
    "\n",
    "    var response = await client.GetAsync(\"/api/hello/ping\");\n",
    "    response.EnsureSuccessStatusCode();\n",
    "\n",
    "    var responseString = await response.Content.ReadAsStringAsync();\n",
    "    Console.WriteLine(responseString);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 日志配置"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "默认日志配置,需要先引用 `Microsoft.Extensions.Logging` 和 `Microsoft.Extensions.Logging.Console` 包,进行通用日志配置!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "//通用日志\n",
    "{\n",
    "    ILoggerFactory loggerFactory = LoggerFactory.Create(buider =>\n",
    "    {\n",
    "        buider.AddConsole();\n",
    "    });\n",
    "\n",
    "    ILogger logger = loggerFactory.CreateLogger(\"logger\");\n",
    "    logger.LogInformation(\"直接使用的通用日志!\");\n",
    "}\n",
    "\n",
    "//IoC中使用\n",
    "{\n",
    "    var services = new ServiceCollection();\n",
    "    services.AddLogging(config =>\n",
    "    {\n",
    "        config.SetMinimumLevel(LogLevel.Information);\n",
    "\n",
    "        config.AddConsole();\n",
    "        //config.AddSimpleConsole();\n",
    "        //config.AddSystemdConsole();\n",
    "    });\n",
    "\n",
    "    var serviceProvider = services.BuildServiceProvider();\n",
    "    var loggerFactory = serviceProvider.GetRequiredService<ILoggerFactory>();\n",
    "    var logger = loggerFactory.CreateLogger(\"logger\");\n",
    "    logger.LogInformation(\"IoC中使用日志!\");\n",
    "    logger.LogError(\"IoC中的错误日志!\");\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "####  配置默认日志"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "//配置默认日志(必须有常规日志及级别设置,否则不起使用)\n",
    "{\n",
    "    var services = new ServiceCollection();\n",
    "\n",
    "    // 1、配置通用日志\n",
    "    services.AddLogging(config =>\n",
    "    {\n",
    "        //日志级别\n",
    "        config.SetMinimumLevel(LogLevel.Trace);\n",
    "        //config.SetMinimumLevel(LogLevel.Information);\n",
    "\n",
    "        //日志载体\n",
    "        config.AddConsole();\n",
    "        //config.AddDebug();\n",
    "        //config.AddJsonConsole();\n",
    "        //config.AddSimpleConsole();\n",
    "        //config.AddSystemdConsole();\n",
    "\n",
    "    });\n",
    "    services\n",
    "        .ConfigureHttpClientDefaults(options =>\n",
    "        {\n",
    "            //2、配置通用日志\n",
    "            options.AddDefaultLogger();\n",
    "        })\n",
    "        .AddHttpClient<HttpClient>(String.Empty,c =>\n",
    "        {\n",
    "            c.BaseAddress = new Uri(webApiBaseUrl);\n",
    "            c.DefaultRequestHeaders.Add(\"Authorization\", \"Bearer a.b.c\");\n",
    "        })\n",
    "        //2、或者单独配置此命名客户端日志\n",
    "        .AddDefaultLogger()\n",
    "        ;\n",
    "\n",
    "    var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
    "    var client = factory.CreateClient(String.Empty);\n",
    "    var response = await client.GetAsync(\"api/hello/index\");\n",
    "\n",
    "    response.EnsureSuccessStatusCode();\n",
    "\n",
    "    var content = await response.Content.ReadAsStringAsync();\n",
    "\n",
    "    Console.WriteLine(content);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### 配置自定义日志"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "[博客](https://www.cnblogs.com/MingsonZheng/p/18013332) 可以参考"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "/*  添加自定义日志记录\n",
    "    1、可以指定当 HttpClient 启动请求、接收响应或引发异常时记录的内容和方式。可以同时添加多个自定义记录器(控制台、ETW 记录器),或“包装”和“不包装”记录器。由于其附加性质,可能需要事先显式删除默认的“旧”日志记录。\n",
    "        要添加自定义日志记录,您需要实现 IHttpClientLogger 接口,然后使用 AddLogger 将自定义记录器添加到客户端。请注意,日志记录实现不应引发任何异常,否则可能会中断请求执行\n",
    "    2、请求上下文对象\n",
    "        上下文对象可用于将 LogRequestStart 调用与相应的 LogRequestStop 调用相匹配,以将数据从一个调用传递到另一个调用。 Context 对象由 LogRequestStart 生成,然后传递回 LogRequestStop。这可以是属性包或保存必要数据的任何其他对象。\n",
    "        如果不需要上下文对象,实现可以从 LogRequestStart 返回 null。\n",
    "    3、避免从内容流中读取\n",
    "        例如,如果您打算阅读和记录请求和响应内容,请注意,它可能会对最终用户体验产生不利的副作用并导致错误。例如,请求内容可能在发送之前被消耗,或者巨大的响应内容可能最终被缓冲在内存中。此外,在 .NET 7 之前,访问标头不是线程安全的,可能会导致错误和意外行为。\n",
    "    4、谨慎使用异步日志记录\n",
    "        我们期望同步 IHttpClientLogger 接口适用于绝大多数自定义日志记录用例。出于性能原因,建议不要在日志记录中使用异步。但是,如果严格要求日志记录中的异步访问,您可以实现异步版本 IHttpClientAsyncLogger。它派生自 IHttpClientLogger,因此可以使用相同的 AddLogger API 进行注册。\n",
    "        请注意,在这种情况下,还应该实现日志记录方法的同步对应项,特别是如果该实现是面向 .NET Standard 或 .NET 5+ 的库的一部分。同步对应项是从同步 HttpClient.Send 方法调用的;即使 .NET Standard 表面不包含它们,.NET Standard 库也可以在 .NET 5+ 应用程序中使用,因此最终用户可以访问同步 HttpClient.Send 方法。\n",
    "    5、包装和不包装记录仪:\n",
    "        当您添加记录器时,您可以显式设置wrapHandlersPipeline参数来指定记录器是否将被包装。默认不包装。\n",
    "        在将重试处理程序添加到管道的情况下(例如 Polly 或某些重试的自定义实现),包装和不包装管道之间的区别最为显着。\n",
    "*/\n",
    "\n",
    "// 创建一个简单的控制台日志类\n",
    "public class SimpleConsoleLogger : IHttpClientLogger\n",
    "{\n",
    "    public object? LogRequestStart(HttpRequestMessage request)\n",
    "    {\n",
    "        return null;\n",
    "    }\n",
    "\n",
    "    public void LogRequestStop(object? ctx, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed)\n",
    "    {\n",
    "        Console.WriteLine($\"自定义日志:{request.Method} {request.RequestUri?.AbsoluteUri} - {(int)response.StatusCode} {response.StatusCode} in {elapsed.TotalMilliseconds}ms\");\n",
    "    }\n",
    "\n",
    "    public void LogRequestFailed(object? ctx, HttpRequestMessage request, HttpResponseMessage? response, Exception e, TimeSpan elapsed)\n",
    "    {\n",
    "        Console.WriteLine($\"自定义日志:{request.Method} {request.RequestUri?.AbsoluteUri} - Exception {e.GetType().FullName}: {e.Message}\");\n",
    "    }\n",
    "}\n",
    "\n",
    "//使用\n",
    "{\n",
    "    var services = new ServiceCollection();\n",
    "    //1、先注册日志类\n",
    "    services.AddSingleton<SimpleConsoleLogger>();\n",
    "\n",
    "    services\n",
    "        // 全局配置\n",
    "        .ConfigureHttpClientDefaults(options =>\n",
    "        {\n",
    "        })\n",
    "        // 配置到HttpClient\n",
    "        .AddHttpClient<HttpClient>(String.Empty,c =>\n",
    "        {\n",
    "            c.BaseAddress = new Uri(webApiBaseUrl);\n",
    "        })\n",
    "        //可选:取消默认日志记录\n",
    "        .RemoveAllLoggers()\n",
    "        //2、配置到HttpClient\n",
    "        .AddLogger<SimpleConsoleLogger>()\n",
    "        ;\n",
    "\n",
    "    var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
    "    var client = factory.CreateClient(String.Empty);\n",
    "    var response = await client.GetAsync(\"api/hello/index\");\n",
    "\n",
    "    response.EnsureSuccessStatusCode();\n",
    "\n",
    "    var content = await response.Content.ReadAsStringAsync();\n",
    "\n",
    "    Console.WriteLine($\"API 影响内容:{content}\");\n",
    "}\n",
    "\n",
    "// 使用上下文的日志类\n",
    "public class RequestIdLogger : IHttpClientLogger\n",
    "{\n",
    "    private readonly ILogger _log;\n",
    "\n",
    "    public RequestIdLogger(ILogger<RequestIdLogger> log)\n",
    "    {\n",
    "        _log = log;\n",
    "    }\n",
    "\n",
    "    private static readonly Action<ILogger, Guid, string?, Exception?> _requestStart = LoggerMessage.Define<Guid, string?>\n",
    "    (\n",
    "        LogLevel.Information,\n",
    "        EventIds.RequestStart,\n",
    "        \"Request Id={RequestId} ({Host}) started\"\n",
    "    );\n",
    "\n",
    "    private static readonly Action<ILogger, Guid, double, Exception?> _requestStop = LoggerMessage.Define<Guid, double>\n",
    "    (\n",
    "        LogLevel.Information,\n",
    "        EventIds.RequestStop,\n",
    "        \"Request Id={RequestId} succeeded in {elapsed}ms\"\n",
    "    );\n",
    "\n",
    "    private static readonly Action<ILogger, Guid, Exception?> _requestFailed = LoggerMessage.Define<Guid>\n",
    "    (\n",
    "        LogLevel.Error,\n",
    "        EventIds.RequestFailed,\n",
    "        \"Request Id={RequestId} FAILED\"\n",
    "    );\n",
    "\n",
    "    public object? LogRequestStart(HttpRequestMessage request)\n",
    "    {\n",
    "        var ctx = new Context(Guid.NewGuid());\n",
    "        _requestStart(_log, ctx.RequestId, request.RequestUri?.Host, null);\n",
    "        return ctx;\n",
    "    }\n",
    "\n",
    "    public void LogRequestStop(object? ctx, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed)\n",
    "    {\n",
    "        _requestStop(_log, ((Context)ctx!).RequestId, elapsed.TotalMilliseconds, null);\n",
    "    }\n",
    "\n",
    "    public void LogRequestFailed(object? ctx, HttpRequestMessage request, HttpResponseMessage? response, Exception e, TimeSpan elapsed)\n",
    "    {\n",
    "        _requestFailed(_log, ((Context)ctx!).RequestId, null);\n",
    "    }\n",
    "\n",
    "    public static class EventIds\n",
    "    {\n",
    "        public static readonly EventId RequestStart = new(1, \"RequestStart\");\n",
    "        public static readonly EventId RequestStop = new(2, \"RequestStop\");\n",
    "        public static readonly EventId RequestFailed = new(3, \"RequestFailed\");\n",
    "    }\n",
    "\n",
    "    record Context(Guid RequestId);\n",
    "}\n",
    "\n",
    "//使用\n",
    "{\n",
    "    var services = new ServiceCollection();\n",
    "\n",
    "    services.AddLogging(config =>\n",
    "    {\n",
    "        config.SetMinimumLevel(LogLevel.Trace);\n",
    "        config.AddConsole();\n",
    "    });\n",
    "\n",
    "    //1、先注册日志类\n",
    "    services.AddSingleton<RequestIdLogger>();\n",
    "\n",
    "    services\n",
    "        // 全局配置\n",
    "        .ConfigureHttpClientDefaults(options =>\n",
    "        {\n",
    "        })\n",
    "        // 配置到HttpClient\n",
    "        .AddHttpClient<HttpClient>(String.Empty,c =>\n",
    "        {\n",
    "            c.BaseAddress = new Uri(webApiBaseUrl);\n",
    "        })\n",
    "        //可选:取消默认日志记录\n",
    "        .RemoveAllLoggers()\n",
    "        //2、配置到HttpClient\n",
    "        .AddLogger<RequestIdLogger>()\n",
    "        ;\n",
    "\n",
    "    var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
    "    var client = factory.CreateClient(String.Empty);\n",
    "    var response = await client.GetAsync(\"api/hello/get\");\n",
    "\n",
    "    response.EnsureSuccessStatusCode();\n",
    "\n",
    "    var content = await response.Content.ReadAsStringAsync();\n",
    "\n",
    "    Console.WriteLine($\"API 影响内容:{content}\");\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7 工厂 + Polly V8"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "IFactoryHttpClient 与 Polly配合,可轻松实现重试、熔断、降级、限流等功能,本文只是简略的给出常用的使用方法,详情会写在 Polly学习项目中。[Polly 官方参考](https://github.com/App-vNext/Polly/wiki/Polly-and-HttpClientFactory)\n",
    "使用步骤:\n",
    "1. 引用 Polly v8 和 Microsoft.Extensions.Http.Polly 包\n",
    "2. 配置命名客户端\n",
    "3. 使用 AddTransientHttpErrorPolicy 快捷方法,配置策略\n",
    "4. 使用其它方式配置,并且可以使用多策略、注册策略、上下文等功能"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 基础应用"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用快捷方法AddTransientHttpErrorPolicy,进行常用功能使用。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "/*\n",
    "    便捷应用:AddTransientHttpErrorPolicy() 方法,添加常用瞬时错误重试策略\n",
    "*/\n",
    "{\n",
    "    var services = new ServiceCollection();\n",
    "\n",
    "    services.AddHttpClient(string.Empty)\n",
    "        //配置默认命名客户端\n",
    "        .ConfigureHttpClient(client => \n",
    "        {\n",
    "            client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "        })\n",
    "        //设置Policy错误处理快捷扩展方法\n",
    "        .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync\n",
    "        (\n",
    "            new[]\n",
    "            {\n",
    "                TimeSpan.FromSeconds(1),\n",
    "                TimeSpan.FromSeconds(2),\n",
    "                TimeSpan.FromSeconds(4),\n",
    "            }\n",
    "        ))\n",
    "        //可以多次调用:设置多个策略\n",
    "        .AddTransientHttpErrorPolicy(builder => builder.RetryAsync(1));\n",
    "    \n",
    "    var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
    "    var content = await factory.CreateClient().GetStringAsync(\"/api/polly8/RandomException\");\n",
    "\n",
    "    Console.WriteLine($\"响应内容:{content}\");\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 使用通过传统 Polly 语法配置的任何策略"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用 AddPolicyHandler 方法及其重载也可用于接受任何 IAsyncPolicy<HttpResponseMessage> ,因此可以定义和应用任何类型的策略:可以指定要处理的内容和处理方式。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "/*\n",
    "    传统方式配置Polly策略\n",
    "*/\n",
    "//创建策略\n",
    "{\n",
    "    var services = new ServiceCollection();\n",
    "\n",
    "    //重试策略\n",
    "    var retryePolicy = Policy\n",
    "        .Handle<HttpRequestException>()\n",
    "        .OrResult<HttpResponseMessage>(response => \n",
    "        {\n",
    "            return response.StatusCode == System.Net.HttpStatusCode.Created;\n",
    "        })\n",
    "        .WaitAndRetryAsync(new TimeSpan[]{TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(2)});\n",
    "    //调用\n",
    "    services\n",
    "        .AddHttpClient(string.Empty)\n",
    "        .AddPolicyHandler(retryePolicy);\n",
    "\n",
    "    //超时策略\n",
    "    var timeoutPolicy = Policy.TimeoutAsync<HttpResponseMessage>(10);\n",
    "    services\n",
    "        .AddHttpClient(\"timeoutPolicy\")\n",
    "        .AddPolicyHandler(timeoutPolicy);\n",
    "    \n",
    "    /* 普通策略转换\n",
    "        所有通过 HttpClient 的调用都返回 HttpResponseMessage 因此配置的策略必须是 IAsyncPolicy<HttpResponseMessage> \n",
    "        通过简单、便捷的 AsAsyncPolicy<HttpResponseMessage>()方法,将非通用策略 IAsyncPolicy 转换为 IAsyncPolicy<HttpResponseMessage>   \n",
    "    */\n",
    "    var timeoutPolicy2 = Policy.TimeoutAsync(2);\n",
    "\n",
    "    services\n",
    "        .AddHttpClient(\"timeoutPolicy2\")\n",
    "        //AsAsyncPolicy转换通用策略\n",
    "        .AddPolicyHandler(timeoutPolicy2.AsAsyncPolicy<HttpResponseMessage>());\n",
    "}\n",
    "\n",
    "//示例\n",
    "{\n",
    "    //创建策略\n",
    "    var policy = Policy.RateLimitAsync<HttpResponseMessage>(3,TimeSpan.FromSeconds(10));\n",
    "\n",
    "    //使用\n",
    "    var services = new ServiceCollection();\n",
    "\n",
    "    services.AddHttpClient(string.Empty)\n",
    "        .ConfigureHttpClient(client => \n",
    "        {\n",
    "            client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "        })\n",
    "        .AddTransientHttpErrorPolicy\n",
    "        (\n",
    "            builder => builder.WaitAndRetryAsync\n",
    "            (\n",
    "                new[]\n",
    "                {\n",
    "                    TimeSpan.FromSeconds(1),\n",
    "                    TimeSpan.FromSeconds(2),\n",
    "                    TimeSpan.FromSeconds(4)\n",
    "                }\n",
    "            )\n",
    "        )\n",
    "        .AddPolicyHandler(policy);\n",
    "    try\n",
    "    {\n",
    "        var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
    "        var content = await factory.CreateClient().GetStringAsync(\"/api/polly8/RandomException\");\n",
    "        Console.WriteLine($\"响应内容:{content}\");\n",
    "    }\n",
    "    catch(Exception ex)\n",
    "    {\n",
    "        Console.WriteLine($\"未处理的异常:{ex.Message}\");\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 应用多个策略"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "    var services = new ServiceCollection();\n",
    "\n",
    "    services.AddHttpClient(string.Empty)\n",
    "    .ConfigureHttpClient(client => \n",
    "    {\n",
    "        client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "    })\n",
    "    .AddTransientHttpErrorPolicy(builder => builder.WaitAndRetryAsync\n",
    "    (\n",
    "        new[]\n",
    "        {\n",
    "            TimeSpan.FromSeconds(1),\n",
    "            TimeSpan.FromSeconds(2),\n",
    "            TimeSpan.FromSeconds(3),\n",
    "        }\n",
    "    ))\n",
    "    //断路器\n",
    "    .AddTransientHttpErrorPolicy(builder => builder.CircuitBreakerAsync(\n",
    "        handledEventsAllowedBeforeBreaking: 3,\n",
    "        durationOfBreak: TimeSpan.FromSeconds(30)\n",
    "    ));\n",
    "\n",
    "    try\n",
    "    {\n",
    "        var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();\n",
    "        var content = await factory.CreateClient().GetStringAsync(\"/api/polly8/RandomException\");\n",
    "        Console.WriteLine(content);\n",
    "    }\n",
    "    catch(Exception ex)\n",
    "    {\n",
    "\n",
    "        Console.WriteLine(\"API异常:\"+ex.Message);\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 动态选择策略"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "//实质是AddPolicyHandler中选择一个策略\n",
    "{\n",
    "    var retryPolicy = Polly.Extensions.Http.HttpPolicyExtensions\n",
    "        .HandleTransientHttpError()\n",
    "        .WaitAndRetryAsync(new[]\n",
    "        {\n",
    "            TimeSpan.FromSeconds(1),\n",
    "            TimeSpan.FromSeconds(2),\n",
    "            TimeSpan.FromSeconds(4)\n",
    "        });\n",
    "    var noOpPolicy = Policy.NoOpAsync().AsAsyncPolicy<HttpResponseMessage>();\n",
    "\n",
    "    var services = new ServiceCollection();\n",
    "    services.AddHttpClient(string.Empty, client =>\n",
    "    {\n",
    "        client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "    })\n",
    "    // 根据请求方法,选择策略\n",
    "    .AddPolicyHandler(request => request.Method == HttpMethod.Get ? retryPolicy : noOpPolicy);\n",
    "\n",
    "    var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();\n",
    "    var client1 = factory.CreateClient(string.Empty);\n",
    "    var content1 = await client1.GetStringAsync(\"/api/hello/get\");\n",
    "    Console.WriteLine(content1);\n",
    "\n",
    "    var client2 = factory.CreateClient(string.Empty);\n",
    "    var response2 = await client2.PostAsync(\"/api/hello/post\",null);\n",
    "    var content2 = await response2.Content.ReadAsStringAsync();\n",
    "    Console.WriteLine(content2);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 从注册表中选择策略"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "{\n",
    "    var registry = new PolicyRegistry()\n",
    "    {\n",
    "        { \"defaultretrystrategy\", HttpPolicyExtensions.HandleTransientHttpError().WaitAndRetryAsync(new TimeSpan[] { TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(3)}) },\n",
    "        { \"defaultcircuitbreaker\", HttpPolicyExtensions.HandleTransientHttpError().CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)) },\n",
    "    };\n",
    "\n",
    "    var services = new ServiceCollection();\n",
    "    services.AddPolicyRegistry(registry);\n",
    "\n",
    "    services.AddHttpClient(\"a\", client => { client.BaseAddress = new Uri(webApiBaseUrl); })\n",
    "        .AddPolicyHandlerFromRegistry(\"defaultretrystrategy\")\n",
    "        //.AddPolicyHandlerFromRegistry(\"defaultcircuitbreaker\")\n",
    "        ;\n",
    "\n",
    "    services.AddHttpClient(\"b\", client => { client.BaseAddress = new Uri(webApiBaseUrl); })\n",
    "        //.AddPolicyHandlerFromRegistry(\"defaultretrystrategy\")\n",
    "        .AddPolicyHandlerFromRegistry(\"defaultcircuitbreaker\")\n",
    "        ;\n",
    "\n",
    "    var factory = services.BuildServiceProvider().GetService<IHttpClientFactory>();\n",
    "    var clientA = factory.CreateClient(\"a\");\n",
    "    var clientB = factory.CreateClient(\"b\");\n",
    "\n",
    "    try\n",
    "    {\n",
    "        var resultA = await clientA.GetStringAsync(\"/api/polly8/exception\");\n",
    "    }\n",
    "    catch (Exception ex)\n",
    "    {\n",
    "        Console.WriteLine(ex.Message);\n",
    "    }\n",
    "    \n",
    "    var resultB = await clientB.GetStringAsync(\"/api/polly8/hello\");\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 8、综合管理:工厂 + 类型化客户端 + 请求管道 + Polly(默认使用 连接池和IoC容器)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 综合示例1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "/* 综合示例1\n",
    "   工厂 + 类型化客户端 + 管道 + Polly + 日志(自定义) \n",
    "*/\n",
    "\n",
    "//类型化客户端\n",
    "public class HelloApiService \n",
    "{\n",
    "    public HttpClient Client { get; set; }\n",
    "\n",
    "    public HelloApiService(HttpClient httpClient)\n",
    "    {\n",
    "        Client = httpClient;\n",
    "    }\n",
    "\n",
    "    public async Task<string> Ping()\n",
    "    {\n",
    "        var content = await Client.GetStringAsync(\"/api/Hello/Ping\");\n",
    "        return content;\n",
    "    }\n",
    "\n",
    "    public async Task<string> Index()\n",
    "    {\n",
    "        var content = await Client.GetStringAsync(\"/api/Hello/Index\");\n",
    "        return content;\n",
    "    }\n",
    "\n",
    "    public async Task<string> Get()\n",
    "    {\n",
    "        var content = await Client.GetStringAsync(\"/api/Hello/Get\");\n",
    "        return content;\n",
    "    }\n",
    "\n",
    "    public async Task<string> Post()\n",
    "    {\n",
    "        var response = await Client.PostAsync(\"/api/Hello/Post\", null);\n",
    "        var content = await response.Content.ReadAsStringAsync();\n",
    "        return content;\n",
    "    }\n",
    "}\n",
    "\n",
    "//类开型客户端\n",
    "public class Polly8ApiService \n",
    "{\n",
    "\n",
    "    public HttpClient Client { get; set; }\n",
    "\n",
    "    public Polly8ApiService(HttpClient httpClient)\n",
    "    {\n",
    "        Client = httpClient;\n",
    "    } \n",
    "\n",
    "    public async Task<string> Hello()\n",
    "    {\n",
    "        var content = await Client.GetStringAsync(\"/api/Polly8/Hello\");\n",
    "        return content;\n",
    "    }\n",
    "\n",
    "    public async Task<string> Exception()\n",
    "    {\n",
    "        var response = await Client.GetAsync(\"/api/Polly8/Exception\");\n",
    "        response.EnsureSuccessStatusCode();\n",
    "        var content = await response.Content.ReadAsStringAsync();\n",
    "        return  content;\n",
    "    }\n",
    "\n",
    "    public async Task<string> RetryException()\n",
    "    {\n",
    "        var response = await Client.GetAsync(\"/api/Polly8/RetryException\");\n",
    "        response.EnsureSuccessStatusCode();\n",
    "        var content = await response.Content.ReadAsStringAsync();\n",
    "        return  content;\n",
    "    }\n",
    "\n",
    "    public async Task<string> RandomException()\n",
    "    {\n",
    "        var response = await Client.GetAsync(\"/api/Polly8/RandomException\");\n",
    "        response.EnsureSuccessStatusCode();\n",
    "        var content = await response.Content.ReadAsStringAsync();\n",
    "        return  content;\n",
    "    }\n",
    "\n",
    "    public async Task<string> ToggleException()\n",
    "    {\n",
    "        var response = await Client.GetAsync(\"/api/Polly8/ToggleException?toggleId=\"+Guid.NewGuid().ToString());\n",
    "        response.EnsureSuccessStatusCode();\n",
    "        var content = await response.Content.ReadAsStringAsync();\n",
    "        return  content;\n",
    "    }\n",
    "}\n",
    "\n",
    "//Token管理中间件\n",
    "public class TokenDelegatingHandler : DelegatingHandler \n",
    "{\n",
    "    protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)\n",
    "    {\n",
    "        Console.WriteLine(\"TokenDelegatingHandler -> Send -> Added Token\");\n",
    "\n",
    "        if (!request.Headers.Contains(Microsoft.Net.Http.Headers.HeaderNames.Authorization)) \n",
    "        {\n",
    "            Console.WriteLine(\"没有 Token, TokenDelegatingHandler 添加之\");\n",
    "            request.Headers.Add(Microsoft.Net.Http.Headers.HeaderNames.Authorization, \"Bearer \" + \"a.b.c\");\n",
    "        }\n",
    "        else\n",
    "        {\n",
    "            Console.WriteLine($\"已有Token, {request.Headers.Authorization}\");\n",
    "        }\n",
    "\n",
    "        HttpResponseMessage response = base.Send(request, cancellationToken);\n",
    "\n",
    "        Console.WriteLine(\"TokenDelegatingHandler -> Send -> After\");\n",
    "\n",
    "        return response;\n",
    "    }\n",
    "\n",
    "    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n",
    "    {\n",
    "        Console.WriteLine(\"TokenDelegatingHandler -> SendAsync -> Before\");\n",
    "\n",
    "        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);\n",
    "\n",
    "        Console.WriteLine(\"TokenDelegatingHandler -> SendAsync -> After\");\n",
    "\n",
    "        return response;\n",
    "    }\n",
    "}\n",
    "\n",
    "//自定义日志\n",
    "public class CustomLogger : IHttpClientLogger\n",
    "{\n",
    "    public object? LogRequestStart(HttpRequestMessage request)\n",
    "    {\n",
    "        return null;\n",
    "    }\n",
    "\n",
    "    public void LogRequestStop(object? ctx, HttpRequestMessage request, HttpResponseMessage response, TimeSpan elapsed)\n",
    "    {\n",
    "        Console.WriteLine($\"自定义日志:{request.Method} {request.RequestUri?.AbsoluteUri} - {(int)response.StatusCode} {response.StatusCode} in {elapsed.TotalMilliseconds}ms\");\n",
    "    }\n",
    "\n",
    "    public void LogRequestFailed(object? ctx, HttpRequestMessage request, HttpResponseMessage? response, Exception e, TimeSpan elapsed)\n",
    "    {\n",
    "        Console.WriteLine($\"自定义日志:{request.Method} {request.RequestUri?.AbsoluteUri} - Exception {e.GetType().FullName}: {e.Message}\");\n",
    "    }\n",
    "}\n",
    "\n",
    "//polly策略\n",
    "var policy = Policy\n",
    "    .Handle<HttpRequestException>()\n",
    "    .OrResult<HttpResponseMessage>(message => message.StatusCode != System.Net.HttpStatusCode.OK)\n",
    "    .WaitAndRetryAsync(new TimeSpan[]{TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2),TimeSpan.FromSeconds(4),});\n",
    "\n",
    "//使用\n",
    "{\n",
    "    var services = new ServiceCollection();\n",
    "\n",
    "    //注册基础类型\n",
    "    services\n",
    "        //注册日志类\n",
    "        .AddTransient<CustomLogger>()\n",
    "        .AddScoped<TokenDelegatingHandler>()\n",
    "        ;\n",
    "\n",
    "    //基础配置\n",
    "    services\n",
    "        // 基础日志配置(默认日志)\n",
    "        .AddLogging(builder => \n",
    "        {\n",
    "            //日志级别\n",
    "            builder.SetMinimumLevel(LogLevel.Trace);\n",
    "\n",
    "            //控制台日志\n",
    "            builder.AddConsole();\n",
    "        })\n",
    "        //全局配置\n",
    "        .ConfigureHttpClientDefaults(clientBuilder =>\n",
    "        {\n",
    "            clientBuilder.AddDefaultLogger();\n",
    "            clientBuilder.ConfigureHttpClient(client => \n",
    "            {\n",
    "                client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "            });\n",
    "        });\n",
    "\n",
    "        //默认命名客户端\n",
    "        services.AddHttpClient<HttpClient>(string.Empty, config => \n",
    "        {\n",
    "            config.DefaultRequestHeaders.Add(\"X-Custom-Demo\", \"true\");\n",
    "        })\n",
    "        //配置客户端\n",
    "        .ConfigureHttpClient(client => \n",
    "        {\n",
    "            //client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "            client.Timeout = TimeSpan.FromSeconds(10);\n",
    "        })\n",
    "        //添加类型化客户端\n",
    "        .AddTypedClient<HelloApiService>()\n",
    "        //添加自定义管道\n",
    "        .AddHttpMessageHandler<TokenDelegatingHandler>()\n",
    "        //添加默认日志:全局配置已添加\n",
    "        //.AddDefaultLogger()\n",
    "        //添加自定义日志\n",
    "        .AddLogger<CustomLogger>()\n",
    "        //日志转发头(所有请求头)\n",
    "        .RedactLoggedHeaders( headerName => true)\n",
    "\n",
    "        //配置SocketsHttpHandler\n",
    "        .UseSocketsHttpHandler(config =>\n",
    "        {\n",
    "            //配置连接池等\n",
    "            config.Configure((handler,provider) => \n",
    "            {\n",
    "                handler.AllowAutoRedirect = true;\n",
    "                handler.PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30);\n",
    "                handler.PooledConnectionLifetime = TimeSpan.FromSeconds(30);\n",
    "                handler.UseProxy = false;\n",
    "                handler.UseCookies = true;\n",
    "            });\n",
    "        })\n",
    "        //设置生命周期\n",
    "        .SetHandlerLifetime(TimeSpan.FromSeconds(30))\n",
    "        //Polly策略配置\n",
    "        .AddPolicyHandler(policy)\n",
    "        //便捷配置\n",
    "        .AddTransientHttpErrorPolicy(builder => builder.CircuitBreakerAsync<HttpResponseMessage>(11, TimeSpan.FromSeconds(30)))\n",
    "        ;\n",
    "\n",
    "    //自定义\n",
    "    services.AddHttpClient<HttpClient>(\"ClientA\", config => \n",
    "        {\n",
    "            config.DefaultRequestHeaders.Add(\"X-Custom-Demo\", \"ClientA\");\n",
    "        })\n",
    "        //配置客户端\n",
    "        .ConfigureHttpClient(client => \n",
    "        {\n",
    "            //client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "            client.Timeout = TimeSpan.FromSeconds(10);\n",
    "        })\n",
    "        //添加类型化客户端\n",
    "        .AddTypedClient<Polly8ApiService>()\n",
    "        //添加自定义管道\n",
    "        .AddHttpMessageHandler<TokenDelegatingHandler>()\n",
    "        //添加默认日志:全局配置已添加\n",
    "        //.AddDefaultLogger()\n",
    "        //添加自定义日志\n",
    "        .AddLogger<CustomLogger>()\n",
    "        //日志转发头(所有请求头)\n",
    "        .RedactLoggedHeaders( headerName => true)\n",
    "        //配置SocketsHttpHandler\n",
    "        .UseSocketsHttpHandler(config =>\n",
    "        {\n",
    "            //配置连接池等\n",
    "            config.Configure((handler,provider) => \n",
    "            {\n",
    "                handler.AllowAutoRedirect = true;\n",
    "                handler.PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30);\n",
    "                handler.PooledConnectionLifetime = TimeSpan.FromSeconds(30);\n",
    "                handler.UseProxy = false;\n",
    "                handler.UseCookies = true;\n",
    "            });\n",
    "        })\n",
    "        //设置生命周期\n",
    "        .SetHandlerLifetime(TimeSpan.FromSeconds(30))\n",
    "        //Polly策略配置\n",
    "        .AddPolicyHandler(policy)\n",
    "        //便捷配置\n",
    "        .AddTransientHttpErrorPolicy(builder => builder.CircuitBreakerAsync<HttpResponseMessage>(11, TimeSpan.FromSeconds(30)))\n",
    "        ;\n",
    "\n",
    "    var factory = services.BuildServiceProvider().GetRequiredService<IHttpClientFactory>();\n",
    "\n",
    "    var defaultClient = factory.CreateClient();\n",
    "    var defaultContent = await defaultClient.GetStringAsync(\"api/hello/ping\");\n",
    "    Console.WriteLine(defaultContent);\n",
    "\n",
    "    var clientA = factory.CreateClient();\n",
    "    var contentA = await clientA.GetStringAsync(\"api/polly8/hello\");\n",
    "    Console.WriteLine(contentA);\n",
    "\n",
    "    //类型化客户端\n",
    "    HelloApiService helloApiService = services.BuildServiceProvider().GetRequiredService<HelloApiService>();\n",
    "    Console.WriteLine(await helloApiService.Ping());\n",
    "    Console.WriteLine(await helloApiService.Index());\n",
    "    Console.WriteLine(await helloApiService.Get());\n",
    "    Console.WriteLine(await helloApiService.Post());\n",
    "\n",
    "    Polly8ApiService polly8ApiService = services.BuildServiceProvider().GetRequiredService<Polly8ApiService>();\n",
    "    Console.WriteLine(await polly8ApiService.Hello());\n",
    "\n",
    "}\n"
   ]
  }
 ],
 "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
}