{ "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>();\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>();\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>(\"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>();\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 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>();\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 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(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();\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();\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 }