You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
HttpClientStudy/Docs/1.3.3.基础使用.处理响应.ipynb

638 lines
18 KiB
Plaintext

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

{
"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
}