{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    }
   },
   "source": [
    "# HttpClient 处理响应数据"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "source": [
    "## 1、初始化及全局设置"
   ]
  },
  {
   "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": [
      "配置文件根目录:c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
      "启动WebApi项目\n",
      "程序[c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.exe]已在新的命令行窗口执行。如果未出现新命令行窗口,可能是程序错误造成窗口闪现!\n"
     ]
    }
   ],
   "source": [
    "//初始化,只执行一次\n",
    "\n",
    "// 引用nuget包和类库文件\n",
    "#r \"./Publish/HttpClientStudy.Model/HttpClientStudy.Model.dll\"\n",
    "#r \"./Publish/HttpClientStudy.Core/HttpClientStudy.Core.dll\"\n",
    "\n",
    "//全局引用\n",
    "global using System;\n",
    "global using System.Collections;\n",
    "global using System.Linq;\n",
    "global using System.Linq.Expressions;\n",
    "global using System.Threading;\n",
    "global using System.Threading.Tasks;\n",
    "global using System.Net.Http;\n",
    "global using System.Net.Mime;\n",
    "global using System.Net.Http.Json;\n",
    "\n",
    "global using Microsoft.Extensions.DependencyInjection;\n",
    "global using Microsoft.Extensions.DependencyInjection.Extensions;\n",
    "\n",
    "global using HttpClientStudy.Config;\n",
    "global using HttpClientStudy.Model;\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",
    "\n",
    "//全局共享静态 HttpClient 对象\n",
    "public static HttpClient SharedClient = new HttpClient(new SocketsHttpHandler(){ PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30)})\n",
    "{\n",
    "    BaseAddress = new Uri(WebApiConfigManager.GetWebApiConfig().BaseUrl),\n",
    "};\n",
    "\n",
    "//启动已发布的WebApi项目\n",
    "{\n",
    "    Console.WriteLine(\"启动WebApi项目\");\n",
    "    var startMessage = AppUtility.RunWebApiExeFile(fullPath);\n",
    "    Console.WriteLine(startMessage);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 2、处理响应状态"
   ]
  },
  {
   "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": [
      "响应码正常:{\"data\":{\"id\":1,\"name\":\"管理员01\",\"password\":\"123456\",\"role\":\"Admin\"},\"code\":1,\"message\":\"成功\"}\n",
      "响应码异常:状态码 BadRequest\n",
      "响应正常:内容为 HttpClientStudy.Model.BaseResult`1[HttpClientStudy.Model.Account]\n",
      "请求异常:Response status code does not indicate success: 400 (Bad Request).\n",
      "请求异常:Response status code does not indicate success: 400 (Bad Request).\n"
     ]
    }
   ],
   "source": [
    "//判断响应码:正常\n",
    "{\n",
    "    var response = await SharedClient.GetAsync(\"api/Normal/GetAccount?id=1\");\n",
    "    if(response.StatusCode == System.Net.HttpStatusCode.OK)\n",
    "    {\n",
    "        var content = await response.Content.ReadAsStringAsync();\n",
    "        Console.WriteLine($\"响应码正常:{content}\");\n",
    "    }\n",
    "}\n",
    "\n",
    "//判断响应码:非正常\n",
    "{\n",
    "    var response = await SharedClient.GetAsync(\"api/Normal/GetAccount?id=b\");\n",
    "    if(response.StatusCode != System.Net.HttpStatusCode.OK)\n",
    "    {\n",
    "        Console.WriteLine($\"响应码异常:状态码 {response.StatusCode}\");\n",
    "    }\n",
    "}\n",
    "\n",
    "//确保正确响应:正常\n",
    "{\n",
    "    var response = await SharedClient.GetAsync(\"api/Normal/GetAccount?id=1\");\n",
    "\n",
    "    //确保异常\n",
    "    response.EnsureSuccessStatusCode();\n",
    "\n",
    "    var result = await response.Content.ReadFromJsonAsync<BaseResult<Account>>();\n",
    "    //result.Display();\n",
    "    Console.WriteLine($\"响应正常:内容为 {result}\");\n",
    "}\n",
    "\n",
    "//确保正确响应:异常\n",
    "{\n",
    "    try \n",
    "    {\n",
    "        var response = await SharedClient.GetAsync(\"api/Normal/GetAccount?id=c\");\n",
    "        \n",
    "        //确保异常\n",
    "        response.EnsureSuccessStatusCode();\n",
    "        //result.Display();\n",
    "\n",
    "        var result = await response.Content.ReadFromJsonAsync<BaseResult<Account>>();\n",
    "        Console.WriteLine($\"响应正常:内容为 {result}\");\n",
    "    }\n",
    "    catch(Exception e)\n",
    "    {\n",
    "        Console.WriteLine($\"请求异常:{e.Message}\");\n",
    "    } \n",
    "}\n",
    "\n",
    "//使用 ry catch 捕获所有异常\n",
    "{\n",
    "    try \n",
    "    {\n",
    "        var result = await SharedClient.GetFromJsonAsync<BaseResult<Account>>(\"api/Normal/GetAccount?id=a\");\n",
    "        //result.Display();\n",
    "        Console.WriteLine($\"响应正常:内容为 {result}\");\n",
    "    }\n",
    "    catch(Exception e)\n",
    "    {\n",
    "        Console.WriteLine($\"请求异常:{e.Message}\");\n",
    "    }\n",
    "    finally\n",
    "    {\n",
    "        //收发业务\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3、处理异常响应"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.1 try catch"
   ]
  },
  {
   "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": [
      "接口异常:Response status code does not indicate success: 400 (Bad Request).\r\n"
     ]
    }
   ],
   "source": [
    "//try catch 常规异常处理\n",
    "{\n",
    "    try \n",
    "    {\n",
    "        var response = await SharedClient.GetAsync(\"api/Normal/GetAccount?id=c\");\n",
    "        \n",
    "        //确保异常\n",
    "        response.EnsureSuccessStatusCode();\n",
    "\n",
    "        var result = await response.Content.ReadFromJsonAsync<BaseResult<Account>>();\n",
    "        Console.WriteLine($\"响应正常:内容为 {result}\");\n",
    "    }\n",
    "    catch(Exception e)\n",
    "    {\n",
    "        Console.WriteLine($\"接口异常:{e.Message}\");\n",
    "    }\n",
    "    finally\n",
    "    {\n",
    "        //清理\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "source": [
    "### 3.2 管道统一处理"
   ]
  },
  {
   "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": [
      "ExceptionDelegatingHandler -> SendAsync -> Before\n",
      "中间件中,接口调用异常:Response status code does not indicate success: 400 (Bad Request).\n",
      "ExceptionDelegatingHandler -> Send -> After\n",
      "接口异常:状态码 BadRequest\n"
     ]
    }
   ],
   "source": [
    "//异常处理管理中间件\n",
    "public class ExceptionDelegatingHandler : DelegatingHandler \n",
    "{\n",
    "    protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)\n",
    "    {\n",
    "        Console.WriteLine(\"ExceptionDelegatingHandler -> Send -> Added Token\");\n",
    "\n",
    "        HttpResponseMessage response = new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError);\n",
    "        try \n",
    "        {\n",
    "            response = base.Send(request, cancellationToken);\n",
    "            response.EnsureSuccessStatusCode();\n",
    "        }\n",
    "        catch(Exception ex)\n",
    "        {\n",
    "            //统一异常处理,当然也可以分类别处理\n",
    "            Console.WriteLine($\"中间件中,接口调用异常:{ex.Message}\");\n",
    "        }\n",
    "        finally\n",
    "        {\n",
    "            Console.WriteLine(\"ExceptionDelegatingHandler -> Send -> After\");\n",
    "        }\n",
    "\n",
    "        return response;\n",
    "    }\n",
    "\n",
    "    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)\n",
    "    {\n",
    "        Console.WriteLine(\"ExceptionDelegatingHandler -> SendAsync -> Before\");\n",
    "\n",
    "        HttpResponseMessage response = new HttpResponseMessage(System.Net.HttpStatusCode.InternalServerError);\n",
    "        try \n",
    "        {\n",
    "            response = await base.SendAsync(request, cancellationToken);\n",
    "            //可以根据状态码,分别进行处理\n",
    "            response.EnsureSuccessStatusCode();\n",
    "        }\n",
    "        catch(Exception ex)\n",
    "        {\n",
    "            //统一异常处理,当然也可以分类别处理\n",
    "            //可以重试等操作\n",
    "            Console.WriteLine($\"中间件中,接口调用异常:{ex.Message}\");\n",
    "        }\n",
    "        finally\n",
    "        {\n",
    "            Console.WriteLine(\"ExceptionDelegatingHandler -> Send -> After\");\n",
    "        }\n",
    "\n",
    "        return response;\n",
    "    }\n",
    "}\n",
    "\n",
    "//使用异常管道,发送请求\n",
    "{\n",
    "    //使用管道中间件,统一处理\n",
    "    ExceptionDelegatingHandler exceptionHandler = new ExceptionDelegatingHandler()\n",
    "    {\n",
    "        InnerHandler = new  SocketsHttpHandler()\n",
    "    };\n",
    "\n",
    "    HttpClient clientWithExceptionHandler = new HttpClient(exceptionHandler)\n",
    "    {\n",
    "        BaseAddress = new Uri(webApiBaseUrl),\n",
    "    };\n",
    "\n",
    "    //发送请求\n",
    "    var response = await clientWithExceptionHandler.GetAsync(\"api/Normal/GetAccount?id=c\");\n",
    "    if(response.StatusCode == System.Net.HttpStatusCode.OK)\n",
    "    {\n",
    "        var result = await response.Content.ReadFromJsonAsync<BaseResult<Account>>();\n",
    "        Console.WriteLine($\"响应正常:内容为 {result}\");\n",
    "    }\n",
    "    else\n",
    "    {\n",
    "         Console.WriteLine($\"接口异常:状态码 {response.StatusCode}\");\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.3 类型化客户端统一处理"
   ]
  },
  {
   "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": [
      "远程调用异常:Response status code does not indicate success: 404 (Not Found).\r\n"
     ]
    }
   ],
   "source": [
    "//类型化客户端\n",
    "public class HelloApiService \n",
    "{\n",
    "    public HttpClient Client { get; set; }\n",
    "\n",
    "    public HelloApiService(HttpClient httpClient)\n",
    "    {\n",
    "        Client = httpClient;\n",
    "    }\n",
    "\n",
    "    //处理异常:也可以结合AOP,进行统一拦截处理\n",
    "    public async Task<string> Ping()\n",
    "    {\n",
    "        try \n",
    "        {\n",
    "            var content = await Client.GetStringAsync(\"/api/Hello/Ping2\");\n",
    "            return content;\n",
    "        }\n",
    "        catch(Exception ex)\n",
    "        {\n",
    "            return $\"远程调用异常:{ex.Message}\";\n",
    "        }\n",
    "    }\n",
    "}\n",
    "\n",
    "//使用\n",
    "{\n",
    "    //注册类型化客户端\n",
    "    var services = new ServiceCollection();\n",
    "    services.AddHttpClient<HelloApiService>(client => \n",
    "    {\n",
    "        client.BaseAddress = new Uri(webApiBaseUrl);\n",
    "    })\n",
    "    .ConfigureHttpClient(client=>\n",
    "    {\n",
    "        client.Timeout = TimeSpan.FromSeconds(1);\n",
    "    });\n",
    "    \n",
    "    //使用类型化客户端,进行远程调用\n",
    "    var apiService = services.BuildServiceProvider().GetService<HelloApiService>();\n",
    "    var s = await apiService.Ping();\n",
    "    Console.WriteLine(s);\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.4 Polly库(重试、降级、熔断等,可结合类型化客户端和工厂模式)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [],
   "source": [
    "//Polly进行异常处理\n",
    "#r \"nuget:Polly,8.5.1\"\n",
    "#r \"nuget:Microsoft.Extensions.Http.Polly,8.0.12\"\n",
    "using Polly;\n",
    "var services = new ServiceCollection();\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",
    "Console.WriteLine($\"响应内容:{content}\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 4、处理响应数据"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.1 接收响应头数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "dotnet_interactive": {
     "language": "csharp"
    },
    "polyglot_notebook": {
     "kernelName": "csharp"
    },
    "vscode": {
     "languageId": "polyglot-notebook"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "响应头:\n",
      "Date = Fri, 17 Jan 2025 02:32:12 GMT\n",
      "Server = Kestrel\n",
      "Transfer-Encoding = chunked\n",
      "X-WebApi-UseTime = 0\n"
     ]
    }
   ],
   "source": [
    "//响应头信息\n",
    "{\n",
    "    var response = await SharedClient.GetAsync(\"api/Normal/GetAccount?id=1\");\n",
    "\n",
    "    Console.WriteLine(\"响应头:\");\n",
    "    foreach(var header in response.Headers)\n",
    "    {\n",
    "        var headerValues = string.Join(\",\", header.Value);\n",
    "        Console.WriteLine($\"{header.Key} = {headerValues}\");\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4.2 接收响应体数据"
   ]
  },
  {
   "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": [
      "响应体数据:{\"data\":{\"id\":1,\"name\":\"管理员01\",\"password\":\"123456\",\"role\":\"Admin\"},\"code\":1,\"message\":\"成功\"}\r\n"
     ]
    }
   ],
   "source": [
    "//响应体数据(json为例)\n",
    "{\n",
    "    var response = await SharedClient.GetAsync(\"api/Normal/GetAccount?id=1\");\n",
    "    //获取响应体内容\n",
    "    var content = await response.Content.ReadAsStringAsync();\n",
    "    Console.WriteLine($\"响应体数据:{content}\"); \n",
    "}"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".NET (C#)",
   "language": "C#",
   "name": ".net-csharp"
  },
  "language_info": {
   "file_extension": ".cs",
   "mimetype": "text/x-csharp",
   "name": "C#",
   "pygments_lexer": "csharp",
   "version": "12.0"
  },
  "polyglot_notebook": {
   "kernelInfo": {
    "defaultKernelName": "csharp",
    "items": [
     {
      "aliases": [],
      "name": "csharp"
     }
    ]
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}