{
 "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": "code",
   "execution_count": 1,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>Microsoft.Extensions.DependencyInjection, 8.0.0</span></li><li><span>System.Net.Http.Json, 8.0.0</span></li></ul></div></div>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "启动WebApi项目\r\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "d:\\软件项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.exe"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "程序[d:\\软件项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.exe]已在新的命令行窗口执行。如果未出现新命令行窗口,可能是程序错误造成窗口闪现!\r\n"
     ]
    }
   ],
   "source": [
    "//全局设置,行运行一次,为后续准备\n",
    "#r \"nuget:System.Net.Http.Json\"\n",
    "#r \"nuget:Microsoft.Extensions.DependencyInjection\"\n",
    "#r \"./Publish/HttpClientStudy.Core/HttpClientStudy.Core.dll\"\n",
    "\n",
    "global using System.Net.Http;\n",
    "global using System.Net.Http.Headers;\n",
    "global using HttpClientStudy.Config;\n",
    "global using HttpClientStudy.Core;\n",
    "global using HttpClientStudy.Core.Utilities;\n",
    "\n",
    "//启动已发布的WebApi项目\n",
    "Console.WriteLine(\"启动WebApi项目\");\n",
    "var workDir = Environment.CurrentDirectory;\n",
    "var fullPath = System.IO.Path.GetFullPath(\"./Publish/HttpClientStudy.WebApp/HttpClientStudy.WebApp.exe\", workDir);\n",
    "fullPath.Display();\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": 2,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "配置文件根目录:d:\\软件项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\r\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "第 10 次/ 共 10 次请求,成功!"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "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": 3,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "配置文件根目录:d:\\软件项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
      "{\"data\":{\"host\":\"localhost\",\"port\":5189,\"scheme\":\"http\",\"pathBase\":\"\",\"baseUrl\":\"http://localhost:5189\",\"webAppMutexName\":\"HttpClientStudy.WebApp\"},\"code\":1,\"message\":\"成功\"}\n",
      "{\"data\":[{\"id\":1,\"name\":\"管理员01\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":2,\"name\":\"管理员02\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":3,\"name\":\"管理员03\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":4,\"name\":\"管理员04\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":5,\"name\":\"管理员05\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":6,\"name\":\"管理员06\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":7,\"name\":\"管理员07\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":8,\"name\":\"管理员08\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":9,\"name\":\"管理员09\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":10,\"name\":\"管理员10\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":11,\"name\":\"开发01\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":12,\"name\":\"开发01\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":13,\"name\":\"开发03\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":14,\"name\":\"开发04\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":15,\"name\":\"开发05\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":16,\"name\":\"开发06\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":17,\"name\":\"开发07\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":18,\"name\":\"开发08\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":19,\"name\":\"开发09\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":20,\"name\":\"开发10\",\"password\":\"123456\",\"role\":\"Dev\"}],\"code\":1,\"message\":\"成功\"}\n"
     ]
    }
   ],
   "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": 4,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "配置文件根目录:d:\\软件项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
      "HttpClientSingleton 初始化一次\n",
      "{\"data\":{\"host\":\"localhost\",\"port\":5189,\"scheme\":\"http\",\"pathBase\":\"\",\"baseUrl\":\"http://localhost:5189\",\"webAppMutexName\":\"HttpClientStudy.WebApp\"},\"code\":1,\"message\":\"成功\"}\n",
      "{\"data\":[{\"id\":1,\"name\":\"管理员01\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":2,\"name\":\"管理员02\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":3,\"name\":\"管理员03\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":4,\"name\":\"管理员04\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":5,\"name\":\"管理员05\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":6,\"name\":\"管理员06\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":7,\"name\":\"管理员07\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":8,\"name\":\"管理员08\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":9,\"name\":\"管理员09\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":10,\"name\":\"管理员10\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":11,\"name\":\"开发01\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":12,\"name\":\"开发01\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":13,\"name\":\"开发03\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":14,\"name\":\"开发04\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":15,\"name\":\"开发05\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":16,\"name\":\"开发06\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":17,\"name\":\"开发07\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":18,\"name\":\"开发08\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":19,\"name\":\"开发09\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":20,\"name\":\"开发10\",\"password\":\"123456\",\"role\":\"Dev\"}],\"code\":1,\"message\":\"成功\"}\n"
     ]
    }
   ],
   "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": 5,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "配置文件根目录:d:\\软件项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
      "HttpClientSingleton2 初始化一次\n",
      "{\"data\":{\"host\":\"localhost\",\"port\":5189,\"scheme\":\"http\",\"pathBase\":\"\",\"baseUrl\":\"http://localhost:5189\",\"webAppMutexName\":\"HttpClientStudy.WebApp\"},\"code\":1,\"message\":\"成功\"}\n",
      "{\"data\":[{\"id\":1,\"name\":\"管理员01\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":2,\"name\":\"管理员02\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":3,\"name\":\"管理员03\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":4,\"name\":\"管理员04\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":5,\"name\":\"管理员05\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":6,\"name\":\"管理员06\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":7,\"name\":\"管理员07\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":8,\"name\":\"管理员08\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":9,\"name\":\"管理员09\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":10,\"name\":\"管理员10\",\"password\":\"123456\",\"role\":\"Admin\"},{\"id\":11,\"name\":\"开发01\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":12,\"name\":\"开发01\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":13,\"name\":\"开发03\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":14,\"name\":\"开发04\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":15,\"name\":\"开发05\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":16,\"name\":\"开发06\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":17,\"name\":\"开发07\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":18,\"name\":\"开发08\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":19,\"name\":\"开发09\",\"password\":\"123456\",\"role\":\"Dev\"},{\"id\":20,\"name\":\"开发10\",\"password\":\"123456\",\"role\":\"Dev\"}],\"code\":1,\"message\":\"成功\"}\n"
     ]
    }
   ],
   "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": 6,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "401173\r\n"
     ]
    }
   ],
   "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": 8,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "配置文件根目录:d:\\软件项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
      "{\"data\":\"操作成功\",\"code\":1,\"message\":\"成功\"}\n"
     ]
    }
   ],
   "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": 24,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div><div></div><div></div><div><strong>Installed Packages</strong><ul><li><span>Microsoft.Extensions.Http.Polly, 8.0.7</span></li><li><span>Polly, 8.4.1</span></li></ul></div></div>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "配置文件根目录:d:\\软件项目\\学习项目\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
      "内部重试\n",
      "内部重试\n",
      "内部重试\n",
      "Response status code does not indicate success: 500 (Internal Server Error).\n"
     ]
    }
   ],
   "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",
    "    \n",
    "})\n",
    "{\n",
    "    BaseAddress = new Uri(BaseUrl),\n",
    "};\n",
    "\n",
    "try\n",
    "{\n",
    "    await pipleLine.ExecuteAsync(async (inneerToken)=>\n",
    "    {\n",
    "        var response = await client.GetAsync(\"api/Polly8/Retry_Exception\",inneerToken);\n",
    "        response.EnsureSuccessStatusCode();\n",
    "    });\n",
    "}\n",
    "catch(Exception ex)\n",
    "{\n",
    "    Console.WriteLine(ex.Message);\n",
    "}\n",
    "finally\n",
    "{\n",
    "\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 5、IoC容器管理:类型化的客户端"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 6、客户端工厂管理:IHttpClientFactory(需要结合IoC)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 7、综合管理:工厂 + 类型化客户端 + 请求管道 + Polly(默认使用 连接池和IoC容器)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (C#)",
   "language": "C#",
   "name": ".net-csharp"
  },
  "language_info": {
   "name": "python"
  },
  "polyglot_notebook": {
   "kernelInfo": {
    "defaultKernelName": "csharp",
    "items": [
     {
      "aliases": [],
      "name": "csharp"
     }
    ]
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}