{ "cells": [ { "cell_type": "markdown", "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" } }, "source": [ "# HttpClient 处理错误与异常" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "根据客户端的创建方法不周,有不同的错误与异常处理方式。推荐类型化客户端、工厂和Polly库中统一处理!" ] }, { "cell_type": "markdown", "metadata": { "dotnet_interactive": { "language": "csharp" }, "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "source": [ "## 初始化" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [ { "data": { "text/markdown": [ "## 初始化\n", "这是全局共用文件,包括Nuget包引用、全局类库引用、全局文件引用、全局命名空间引用、全局变量、全局方法、全局类定义等功能。\n", "\n", "在业务笔记中引用,执行其它单元格之前先执行一次。" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
Installed Packages
" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "配置文件根目录:c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n", "配置文件根目录: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", "已启动WebApi项目,保持窗口打开状态!\n", "初始化完成!\n" ] } ], "source": [ "#!import ./Ini.ipynb" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 常规方法" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "直接实例化客户端时,可以用`Try Catch`或者`EnsureSuccessStatusCode方法`简单处理, 这种是最不推荐的方式!\n", "\n", "推荐使用后面介绍的 Pipeline 管道方式" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ try catch" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "远程请求失败,响应码 InternalServerError\r\n" ] } ], "source": [ "/*\n", " try catch 处理异常\n", "*/\n", "try\n", "{\n", " var requestUri = new Uri(webApiBaseUri, \"/api/ErrorDemo/Error500\");\n", "\n", " using(var client = new HttpClient())\n", " {\n", " //发送请求\n", " var response = await client.GetAsync(requestUri);\n", " if(response.IsSuccessStatusCode)\n", " {\n", " Console.WriteLine($\"远程请求成功,响应码 {response.StatusCode}\");\n", " }\n", " else\n", " {\n", " Console.WriteLine($\"远程请求失败,响应码 {response.StatusCode}\");\n", " }\n", " }\n", "}\n", "catch(Exception ex)\n", "{\n", " Console.WriteLine($\"远程调用异常:{ex.Message}\");\n", "}\n", "finally\n", "{\n", " //清理业务\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ Response的EnsureSuccessStatusCode方法" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [ { "ename": "Error", "evalue": "System.Net.Http.HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).\r\n at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\r\n at Submission#3.<>d__0.MoveNext()\r\n--- End of stack trace from previous location ---\r\n at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)", "output_type": "error", "traceback": [ "System.Net.Http.HttpRequestException: Response status code does not indicate success: 500 (Internal Server Error).\r\n", " at System.Net.Http.HttpResponseMessage.EnsureSuccessStatusCode()\r\n", " at Submission#3.<>d__0.MoveNext()\r\n", "--- End of stack trace from previous location ---\r\n", " at Microsoft.CodeAnalysis.Scripting.ScriptExecutionState.RunSubmissionsAsync[TResult](ImmutableArray`1 precedingExecutors, Func`2 currentExecutor, StrongBox`1 exceptionHolderOpt, Func`2 catchExceptionOpt, CancellationToken cancellationToken)" ] } ], "source": [ "{ //EnsureSuccessStatusCode方法\n", "\n", " var requestUri = new Uri(webApiBaseUri, \"/api/ErrorDemo/Error500\");\n", "\n", " using(var client = new HttpClient())\n", " {\n", " //发送请求\n", " var response = await client.GetAsync(requestUri);\n", "\n", " //确保响应码正确,否则异常\n", " response.EnsureSuccessStatusCode();\n", "\n", " //输出响应码\n", " Console.WriteLine($\"远程请求成功,响应码 {response.StatusCode}\");\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 静态或工具类中处理" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "静态类或工具类,可以在内部方法里使用 Try Catch 等常规方法进行异常处理。\n", "\n", "注意:推荐使用后面介绍的 Pipeline 管道方式" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ 内部处理" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "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", "正常请求结果:{\"data\":{\"host\":\"localhost\",\"port\":5189,\"scheme\":\"http\",\"pathBase\":\"\",\"baseUrl\":\"http://localhost:5189\",\"webAppMutexName\":\"HttpClientStudy.WebApp\"},\"code\":1,\"message\":\"成功\"}\n", "异常请求结果:StatusCode: 500, ReasonPhrase: 'Internal Server Error', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers:\n", "{\n", " Date: Tue, 13 May 2025 04:30:54 GMT\n", " Server: Kestrel\n", " Transfer-Encoding: chunked\n", " X-WebApi-UseTime: 685\n", " Content-Type: text/plain; charset=utf-8\n", "}\n" ] } ], "source": [ "//静态类中处理异常\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", " //统一设置: 基础Uri\n", " BaseAddress = new Uri(WebApiConfigManager.GetWebApiConfig().BaseUrl),\n", " };\n", "\n", " //统一设置:请求头等\n", " StaticClient.DefaultRequestHeaders.Add(\"x-custom-time\",\"12ms\");\n", "\n", " //统一错误处理:可以在Pipline中统一设置,后面有单独章节\n", " } \n", "\n", " public static async Task GetAsync(string url)\n", " {\n", " //异常处理\n", " try\n", " {\n", " return await StaticClient.GetAsync(url);\n", " }\n", " catch(Exception ex)\n", " {\n", " Console.WriteLine($\"远程调用发生异常:{ex.Message}\");\n", " return await Task.FromResult(new HttpResponseMessage(HttpStatusCode.ExpectationFailed));\n", " }\n", " finally\n", " {\n", " //清理业务\n", " }\n", " }\n", "\n", " public static async Task GetStringAsync(string url)\n", " {\n", " var response = await StaticClient.GetAsync(url);\n", "\n", " //异常处理\n", " response.EnsureSuccessStatusCode();\n", " return await response.Content.ReadAsStringAsync();\n", " }\n", "\n", " public static async Task PostAsync(string url, HttpContent content)\n", " {\n", " //异常处理\n", " try\n", " {\n", " return await StaticClient.PostAsync(url, content);\n", " }\n", " catch(Exception ex)\n", " {\n", " Console.WriteLine($\"远程调用发生异常:{ex.Message}\");\n", " return await Task.FromResult(new HttpResponseMessage(HttpStatusCode.ExpectationFailed));\n", " }\n", " finally\n", " {\n", " //清理业务\n", " }\n", " }\n", "}\n", "\n", "{ //正常请求\n", " var response = await HttpClientHelper.GetAsync(\"/api/Config/GetApiConfig\");\n", " var content = await response.Content.ReadAsStringAsync();\n", " Console.WriteLine($\"正常请求结果:{content}\");\n", "\n", " //异常请求\n", " var response2 = await HttpClientHelper.GetAsync(\"/api/ErrorDemo/Error500\");\n", " Console.WriteLine($\"异常请求结果:{response2}\");\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ 添加异常Pipeline管道(下面专项介绍)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 类型化客户端中处理" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "类型化客户端有三种方法:一是类内部处理;二是Pipeline,统一处理;三是结合IoC/工厂和Polly库,初始设置时统一处理。\n", "\n", "推荐第二、第三种,后面有专项介绍。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ 内部处理,类似静态或工具类" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HttpClientServiceA => 构造函数执行一次\n", "System.Exception: 服务器异常\n", " at HttpClientStudy.WebApp.Controllers.ErrorDemoController.Error500() in C:\\Users\\ruyu\\Desktop\\HttpClientStudy\\HttpClientStudy.WebApp\\Controllers\\ErrorDemoController.cs:line 37\n", " at lambda_method3(Closure, Object, Object[])\n", " at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)\n", " at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Logged|12_1(ControllerActionInvoker invoker)\n", " at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n", " at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)\n", " at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)\n", " at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()\n", "--- End of stack trace from previous location ---\n", " at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)\n", " at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)\n", " at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Logged|17_1(ResourceInvoker invoker)\n", " at Microsoft.AspNetCore.Routing.EndpointMiddleware.g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)\n", " at HttpClientStudy.WebApp.Program.<>c.<
b__0_12>d.MoveNext() in C:\\Users\\ruyu\\Desktop\\HttpClientStudy\\HttpClientStudy.WebApp\\Program.cs:line 258\n", "--- End of stack trace from previous location ---\n", " at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)\n", " at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)\n", " at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)\n", " at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)\n", " at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)\n", " at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)\n", "\n", "HEADERS\n", "=======\n", "Host: 127.0.0.1:5189\n", "traceparent: 00-8cccd683184035a261e8996c74c87f82-7440f8a982d79135-00\n", "\n" ] } ], "source": [ "// 类型化客户端A 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", " //常规方法:异常可在方法内处理\n", " public async Task GetAsync()\n", " {\n", " var response = await Client.GetAsync(\"/api/ErrorDemo/Error500\");\n", " var content = await response.Content.ReadAsStringAsync();\n", " return content;\n", " }\n", "}\n", "\n", "// 使用类型化客户端\n", "{\n", " var services = new ServiceCollection();\n", " services\n", " .AddHttpClient()\n", " .ConfigureHttpClient(client=>\n", " {\n", " client.BaseAddress = new Uri(webApiBaseUrl);\n", " });\n", "\n", " var builder = services.BuildServiceProvider();\n", " var serverA = builder.GetRequiredService();\n", "\n", " var dataA = await serverA.GetAsync();\n", " Console.WriteLine(dataA);\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ 与工厂和Polly库统合,统一处理(下面专项介绍)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ 添加异常Pipeline管道(下面专项介绍)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 在管道中,添加异常中间件" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "不管那种方式创建的客户端,都可以配置Pipeline,专门添加异常管道中间件进行异常统一处理,也很推荐这种方式。" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ 准备异常处理中间件" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [], "source": [ "//异常中间件(管道)类\n", "public class ExceptionDelegatingHandler : DelegatingHandler\n", "{\n", " protected override HttpResponseMessage Send(HttpRequestMessage request, CancellationToken cancellationToken)\n", " {\n", " Console.WriteLine(\"ExceptionDelegatingHandler -> Send -> Before\");\n", "\n", " HttpResponseMessage response;\n", " try \n", " {\n", " response = base.Send(request, cancellationToken);\n", " response.EnsureSuccessStatusCode();\n", " }\n", " catch(Exception ex)\n", " {\n", " Console.WriteLine($\"异常管道中间件,监控到异常:{ex.Message}\");\n", "\n", " response = new HttpResponseMessage(HttpStatusCode.ExpectationFailed);\n", " }\n", " finally\n", " {\n", " //清理业务\n", " }\n", " \n", " Console.WriteLine(\"ExceptionDelegatingHandler -> Send -> After\");\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;\n", " try \n", " {\n", " response = response = await base.SendAsync(request, cancellationToken);\n", " response.EnsureSuccessStatusCode();\n", " }\n", " catch(Exception ex)\n", " {\n", " Console.WriteLine($\"异常管道中间件,监控到异常:{ex.Message}\");\n", "\n", " response = new HttpResponseMessage(HttpStatusCode.ExpectationFailed);\n", " }\n", " finally\n", " {\n", " //清理业务\n", " }\n", "\n", " Console.WriteLine(\"ExceptionDelegatingHandler -> SendAsync -> After\");\n", "\n", " return response;\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ 直接创建客户端时,加入异常管道" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "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: 500 (Internal Server Error).\n", "ExceptionDelegatingHandler -> SendAsync -> After\n", "远程请求失败,响应码 ExpectationFailed\n" ] } ], "source": [ "{\n", "\n", " //构建管道(加入异常中间件)\n", " var handler = new ExceptionDelegatingHandler()\n", " {\n", " //相当于下一个中间件(管道)\n", " //最后中间件必须是SocketsHttpHandler\n", " InnerHandler = new SocketsHttpHandler() \n", " {\n", " AllowAutoRedirect = true\n", " }\n", " };\n", "\n", " //构造中传入管道对象\n", " using(var client = new HttpClient(handler){BaseAddress = new Uri(webApiBaseUrl)})\n", " {\n", " //发送请求\n", " var response = await client.GetAsync(\"/api/ErrorDemo/Error500\");\n", "\n", " if(response.IsSuccessStatusCode)\n", " {\n", " Console.WriteLine($\"远程请求成功,响应码 {response.StatusCode}\");\n", " }\n", " else\n", " {\n", " Console.WriteLine($\"远程请求失败,响应码 {response.StatusCode}\");\n", " }\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ 静态或工具类,加入异常管道" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "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", "ExceptionDelegatingHandler -> SendAsync -> Before\n", "ExceptionDelegatingHandler -> SendAsync -> After\n", "正常请求结果:{\"data\":{\"host\":\"localhost\",\"port\":5189,\"scheme\":\"http\",\"pathBase\":\"\",\"baseUrl\":\"http://localhost:5189\",\"webAppMutexName\":\"HttpClientStudy.WebApp\"},\"code\":1,\"message\":\"成功\"}\n", "ExceptionDelegatingHandler -> SendAsync -> Before\n", "异常管道中间件,监控到异常:Response status code does not indicate success: 500 (Internal Server Error).\n", "ExceptionDelegatingHandler -> SendAsync -> After\n", "异常请求结果:StatusCode: 417, ReasonPhrase: 'Expectation Failed', Version: 1.1, Content: System.Net.Http.EmptyContent, Headers:\n", "{\n", " Content-Length: 0\n", "}\n" ] } ], "source": [ "//静态类中处理异常\n", "public class HttpClientHelper2\n", "{\n", " public readonly static HttpClient StaticClient;\n", "\n", " static HttpClientHelper2()\n", " {\n", " //构建管道(加入异常中间件)\n", " var handler = new ExceptionDelegatingHandler()\n", " {\n", " //相当于下一个中间件(管道)\n", " //最后中间件必须是SocketsHttpHandler\n", " InnerHandler = new SocketsHttpHandler() \n", " {\n", " AllowAutoRedirect = true,\n", " PooledConnectionLifetime = TimeSpan.FromSeconds(30),\n", " }\n", " };\n", "\n", " StaticClient = new HttpClient(handler)\n", " {\n", " //统一设置: 基础Uri\n", " BaseAddress = new Uri(WebApiConfigManager.GetWebApiConfig().BaseUrl),\n", " };\n", "\n", " //统一设置:请求头等\n", " StaticClient.DefaultRequestHeaders.Add(\"x-custom-time\",\"12ms\");\n", "\n", " //统一错误处理:可以在Pipline中统一设置,后面有单独章节\n", " } \n", "\n", " public static async Task GetAsync(string url)\n", " {\n", " return await StaticClient.GetAsync(url);\n", " }\n", "\n", " public static async Task GetStringAsync(string url)\n", " {\n", " var response = await StaticClient.GetAsync(url);\n", "\n", " //无异常处理\n", "\n", " return await response.Content.ReadAsStringAsync();\n", " }\n", "\n", " public static async Task PostAsync(string url, HttpContent content)\n", " {\n", " //无异常处理\n", " return await StaticClient.PostAsync(url,content);\n", " }\n", "}\n", "\n", "{ //正常请求\n", " var response = await HttpClientHelper2.GetAsync(\"/api/Config/GetApiConfig\");\n", " var content = await response.Content.ReadAsStringAsync();\n", " Console.WriteLine($\"正常请求结果:{content}\");\n", "\n", " //异常请求\n", " var response2 = await HttpClientHelper2.GetAsync(\"/api/ErrorDemo/Error500\");\n", " Console.WriteLine($\"异常请求结果:{response2}\");\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ 类型化客户端,加入异常管道" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "HttpClientServiceC => 构造函数执行一次\n", "ExceptionDelegatingHandler -> SendAsync -> Before\n", "异常管道中间件,监控到异常:Response status code does not indicate success: 500 (Internal Server Error).\n", "ExceptionDelegatingHandler -> SendAsync -> After\n", "响应数据为:\n" ] } ], "source": [ "// 类型化客户端A HttpClient\n", "public class HttpClientServiceC\n", "{\n", " public HttpClient Client { get; }\n", " public HttpClientServiceC(HttpClient client)\n", " {\n", " Client = client;\n", " Console.WriteLine(\"HttpClientServiceC => 构造函数执行一次\");\n", " }\n", "\n", " //常规方法:异常可在方法内处理\n", " public async Task GetAsync()\n", " {\n", " var response = await Client.GetAsync(\"/api/ErrorDemo/Error500\");\n", " var content = await response.Content.ReadAsStringAsync();\n", " return content;\n", " }\n", "}\n", "\n", "// 使用类型化客户端\n", "{\n", " var services = new ServiceCollection();\n", " services\n", " //1、注入异常管道到服务\n", " .AddTransient()\n", " .AddHttpClient()\n", " .ConfigureHttpClient(client=>\n", " {\n", " client.BaseAddress = new Uri(webApiBaseUrl);\n", " })\n", " //2、添加异常管道至客户端\n", " .AddHttpMessageHandler();\n", "\n", " var builder = services.BuildServiceProvider();\n", " var serverC = builder.GetRequiredService();\n", "\n", " var dataC = await serverC.GetAsync();\n", " Console.WriteLine($\"响应数据为:{dataC}\");\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "+ 工厂,加入异常管道" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "ExceptionDelegatingHandler -> SendAsync -> Before\n", "ExceptionDelegatingHandler -> SendAsync -> After\n", "Pong\n", "ExceptionDelegatingHandler -> SendAsync -> Before\n", "异常管道中间件,监控到异常:Response status code does not indicate success: 500 (Internal Server Error).\n", "ExceptionDelegatingHandler -> SendAsync -> After\n", "响应码:ExpectationFailed\n" ] } ], "source": [ "//使用\n", "{\n", " var services = new ServiceCollection();\n", "\n", " //基础配置\n", " services\n", " //1、注入异常管道到服务\n", " .AddTransient()\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(string.Empty, config => \n", " {\n", " config.DefaultRequestHeaders.Add(\"X-Custom-Demo\", \"true\");\n", " })\n", "\n", " //配置客户端\n", " .ConfigureHttpClient(client => \n", " {\n", " client.BaseAddress = new Uri(webApiBaseUrl);\n", " client.Timeout = TimeSpan.FromSeconds(10);\n", " })\n", "\n", " //添加类型化客户端\n", " //.AddTypedClient()\n", "\n", " //2、添加异常管道至客户端\n", " .AddHttpMessageHandler()\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(11, TimeSpan.FromSeconds(30)))\n", " ;\n", "\n", " \n", " var factory = services.BuildServiceProvider().GetRequiredService();\n", "\n", " //正常请求\n", " var defaultClient = factory.CreateClient();\n", " var defaultContent = await defaultClient.GetStringAsync(\"api/hello/ping\");\n", " Console.WriteLine(defaultContent);\n", "\n", " //异常请求\n", " var clientA = factory.CreateClient();\n", " var responseA = await clientA.GetAsync(\"/api/ErrorDemo/Error500\");\n", " Console.WriteLine($\"响应码:{responseA.StatusCode}\");\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 工厂 + Polly库处理" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Polly库提供了多种多样的超时、异常等功能,非常推荐使用(可以配合IoC/工厂)。\n", "\n", "下面是简单例子:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "polyglot_notebook": { "kernelName": "csharp" }, "vscode": { "languageId": "polyglot-notebook" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "正常请求, 响应内容: Pong\n", "运程调用发性异常,Polly进行了回退!\n", "异常请求,响应码:InternalServerError\n" ] } ], "source": [ "//polly策略\n", "var policy = Policy\n", " .Handle()\n", " .OrResult(message => message.StatusCode != System.Net.HttpStatusCode.OK)\n", " //后备策略,代替异常\n", " .FallbackAsync(async fallbackAction =>\n", " {\n", " Console.WriteLine(\"运程调用发性异常,Polly进行了回退!\");\n", "\n", " return await Task.FromResult(new HttpResponseMessage(HttpStatusCode.InternalServerError));\n", " });\n", "\n", "//工厂中使用 Polly异常回退策略\n", "{\n", " var services = new ServiceCollection();\n", "\n", " //默认命名客户端\n", " services\n", " .AddHttpClient(string.Empty)\n", " .ConfigureHttpClient(client => \n", " {\n", " client.BaseAddress = new Uri(webApiBaseUrl);\n", " client.Timeout = TimeSpan.FromSeconds(10);\n", " })\n", "\n", " //Polly策略配置\n", " .AddPolicyHandler(policy);\n", "\n", " \n", " var factory = services.BuildServiceProvider().GetRequiredService();\n", "\n", " //正常请求\n", " var defaultClient = factory.CreateClient();\n", " var defaultContent = await defaultClient.GetStringAsync(\"api/hello/ping\");\n", " Console.WriteLine($\"正常请求, 响应内容: {defaultContent}\");\n", "\n", " //异常请求\n", " var clientA = factory.CreateClient();\n", " var responseA = await clientA.GetAsync(\"/api/ErrorDemo/Error500\");\n", " Console.WriteLine($\"异常请求,响应码:{responseA.StatusCode}\");\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 }