diff --git a/Docs/1.0项目管理.dib b/Docs/1.0项目管理.dib index bc4b55f..114cf1c 100644 --- a/Docs/1.0项目管理.dib +++ b/Docs/1.0项目管理.dib @@ -55,15 +55,17 @@ Start-Process -FilePath "Publish\HttpClientStudy.WebApp\HttpClientStudy.WebApp.e #!pwsh # 关闭项目进程 -$WebAppProcName ="HttpClientStudy.WebApp"; -$WebAppProc = Get-Process $WebAppProcName -ErrorAction Ignore -if($null -eq $WebAppProc) { - Write-Host "进程没有找到,可能已经退出" -} -else { - $WebAppProc.Kill(); - Write-Host "$WebAppProcName 进程已退出" + $WebAppProcName ="HttpClientStudy.WebApp"; + $WebAppProc = Get-Process $WebAppProcName -ErrorAction Ignore + if($null -eq $WebAppProc) + { + Write-Host "进程没有找到,可能已经退出" + } + else { + $WebAppProc.Kill(); + Write-Host "$WebAppProcName 进程已退出" + } } #!markdown @@ -72,15 +74,12 @@ else { #!csharp -#r "nuget:Microsoft.Extensions.DependencyInjection,8.0.1" - using System; using System.IO; using System.Threading; using System.Threading.Tasks; using System.Diagnostics; using System.Reflection; -using Microsoft.Extensions.DependencyInjection; //查看各种程序路径 { diff --git a/Docs/1.3.0.基础使用.管理客户端.ipynb b/Docs/1.3.0.基础使用.管理客户端.ipynb index d3bb494..2eb75b0 100644 --- a/Docs/1.3.0.基础使用.管理客户端.ipynb +++ b/Docs/1.3.0.基础使用.管理客户端.ipynb @@ -2174,7 +2174,7 @@ " }\n", "}\n", "\n", - "//类开型客户端\n", + "//类型化客户端\n", "public class Polly8ApiService \n", "{\n", "\n", diff --git a/Docs/1.3.3.基础使用.处理响应.ipynb b/Docs/1.3.3.基础使用.处理响应.ipynb index 23dadc5..f90bd83 100644 --- a/Docs/1.3.3.基础使用.处理响应.ipynb +++ b/Docs/1.3.3.基础使用.处理响应.ipynb @@ -11,7 +11,7 @@ } }, "source": [ - "# HttpClient 使用原则" + "# HttpClient 处理响应数据" ] }, { @@ -33,7 +33,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": { "dotnet_interactive": { "language": "csharp" @@ -45,15 +45,22 @@ "languageId": "polyglot-notebook" } }, - "outputs": [], + "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 \"nuget:Microsoft.Net.Http.Headers,8.0.12\"\n", - "\n", "#r \"./Publish/HttpClientStudy.Model/HttpClientStudy.Model.dll\"\n", - "//已引用相关类库\n", "#r \"./Publish/HttpClientStudy.Core/HttpClientStudy.Core.dll\"\n", "\n", "//全局引用\n", @@ -67,6 +74,9 @@ "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", @@ -98,6 +108,103 @@ "## 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": {}, @@ -105,6 +212,305 @@ "## 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": {}, @@ -119,12 +525,86 @@ "### 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": { @@ -134,7 +614,11 @@ "name": ".net-csharp" }, "language_info": { - "name": "python" + "file_extension": ".cs", + "mimetype": "text/x-csharp", + "name": "C#", + "pygments_lexer": "csharp", + "version": "12.0" }, "polyglot_notebook": { "kernelInfo": {