当前位置: 首页 > news >正文

.NET 高级开发 | http 接口对接和客户端开发技巧

本节讲解了 HttpClient 的一些基础知识和如何应对常见的 http 请求操作,代码示例请参考 Demo6.API、Demo6.Console 两个项目。

请求参数

http 请求携带参数常用有四种方式,query、header、表单、json 等,接下来笔者逐一介绍。

Query

Query 请求实际上是在地址中拼接参数,使用&符号将参数拼接起来,比如https://localhost:5001/test?a=1&b=2中有 a 和 b 两个参数,由于 query 是直接存在 url 中,所以可以手动通过字符串插值或拼接的方法生成一个带 query 参数的 url。

// URL Query 参数 public static async Task Query(string a, string b) { using var httpClient = new HttpClient(httpclientHandler); var response = await httpClient.GetAsync($"https://localhost:5001/query?a={a}&b={b}"); }

在 ASP.NET Core 中可以使用[FromQuery]特性从 URL 参数中解析值。

[HttpGet("/query")] public string Query([FromQuery] string a, [FromQuery] string b) { return a + b; }

使用[FromQuery]修饰的参数,会忽略大小写。

但是构建 query 参数比较麻烦,也很容易出错,我们可以使用 System.Web.HttpUtility 类来处理 query 参数。

var nv = System.Web.HttpUtility.ParseQueryString(string.Empty); nv.Add("a", "1"); nv.Add("b", "2"); var query = nv.ToString(); var url = "https://localhost:5001/query?" + nv; // https://localhost:5001/query?a=1&b=2

在 ASP.NET Core 中也有一个类似的方法拼接 query 参数。

var dic = new Dictionary<string, string>() { { "a","1"}, { "b","2"} }; var query = QueryHelpers.AddQueryString("https://localhost:5001/query", dic);

在 url 中携带参数的值包含特殊字符时,需要对特殊字符进行转义。比如设置a=http://localhost:5001/query?a=1&b=2,直接拼接 url 地址,会输出:

http://localhost:5001/query?a=http://localhost:5001/query?a=1&b=2&b=2

实际上在服务端接收请求后,解析出的结果为:

a=http://localhost:5001/query?a=1 b=2

因此,当 query 参数中包含特殊字符时,需要先将特殊字符转义,才能拼接到 url 中。

在 C# 中可以使用Uri.EscapeDataString转义 query 参数中的特殊字符,示例如下:

a = Uri.EscapeDataString("http://localhost:5001/query?a=1&b=2"); b = "2"; var response = await httpClient.GetAsync($"https://localhost:5001/query?a={a}&b={b}");

最后生成的 url 为:

http://localhost:5001/query?a=http%3A%2F%2Flocalhost%3A5001%2Fquery%3Fa%3D1%26b%3D2&b=2

Header

header 是 http 通讯协议中的一部分,heder 包含了许多重要的信息。

HttpClient 以键值对的形式存储 Header,参考示例如下:

public static async Task Header() { using var httpClient = new HttpClient(httpclientHandler); // Header 头 httpClient.DefaultRequestHeaders.Add("MyEmail", "123@qq.com"); var response = await httpClient.GetAsync($"https://localhost:5001/header"); var result = await response.Content.ReadAsStringAsync(); }

在 ASP.NET Core 中编写 API,可以使用[FromHeader]在函数参数中获得 header 的参数,而不需要通过 HttpContext 获取,有助于简化代码。

[HttpGet("/header")] public string Header([FromHeader] string? myEmail) { return myEmail; }

使用[FromHeader]修饰的参数,会忽略大小写,使用myEmail还是MyEmail都一样。

HttpRequestHeaders 已经定义了一些常用的 Header 头,封装处理逻辑到相关的属性中,常用 Header 如下:

Accept AcceptCharset AcceptEncoding AcceptLanguage Authorization

在 HttpClient 中可以直接设置,不需要通过键值对的形式插入,使用示例:

httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); httpClient.DefaultRequestHeaders.AcceptLanguage = ...;

表单

表单有 x-www-form-urlencoded、form-data 两种请求类型。form-data兼容x-www-form-urlencoded,并且可以用于上传文件,而x-www-form-urlencoded只能传递字符串键值对。由于x-www-form-urlencoded性能比form-data高一些,所以不需要上传文件时,使用x-www-form-urlencoded比较好。

使用x-www-form-urlencoded携带请求参数的代码示例如下:

// 表单提交 // application/x-www-form-urlencoded public static async Task From() { var fromContent = new FormUrlEncodedContent(new[] { new KeyValuePair<string,string>("Id","1"), new KeyValuePair<string,string>("Name","痴者工良"), new KeyValuePair<string, string>("Number","666666") }); using var httpClient = new HttpClient(httpclientHandler); var response = await httpClient.PostAsync("http://localhost:5001/form1", fromContent); }

ASP.NET Core 中可以使用字典类型来接收相应的参数。

[HttpPost("/form1")] public string Form1([FromForm] Dictionary<string, string> dic) { return "success"; }

也可以使用模型类来接收表单参数。

[HttpPost("/form2")] public string Form2([FromForm] Form2Model model) { return "success"; } public class Form2Model { public string Id { get; set; } public string Name { get; set; } public string Number { get; set; } }

HttpClient 还可以使用form-data上传文件,代码示例如下:

// 上传文件 public static async Task SendFile(string filePath, string fromName, string url) { using var client = new HttpClient(); FileStream imagestream = System.IO.File.OpenRead(filePath); // multipartFormDataContent.Add( ... ...); var multipartFormDataContent = new MultipartFormDataContent() { { new StreamContent(File.OpenRead(filePath)), // 对应 服务器 WebAPI 的传入参数 fromName, // 上传的文件名称 Path.GetFileName(filePath) }, // 可上传多个文件 }; HttpResponseMessage response = await client.PostAsync(url, multipartFormDataContent); }

HttpClient 使用 HttpContent 来抽象 body 参数类型,相关的派生类型有很多,比如 MultipartFormDataContent 、StreamContent、StringContent 、FormUrlEncodedContent 等。MultipartFormDataContent 实现了IEnumerable<HttpContent>接口,因此 MultipartFormDataContent 中还可以嵌套其它 HttpContent 类型,比如在上传文件时可以携带其他表单参数。

MultipartContent multipartContent = new MultipartContent(); multipartContent.Add(multipartFormDataContent); multipartContent.Add(fromContent);
var multipartFormDataContent = new MultipartFormDataContent() { // 文件 { new StreamContent(File.OpenRead(filePath)), fromName, Path.GetFileName(filePath) }, // 表单 fromContent, };

ASP.NET Core 中如果只接收文件,则使用 IFormFile 类型作为函数参数即可,参数的名称要跟表单中文件字段的名称一致。

[HttpPost("/form3")] public string Form3([FromForm] IFormFile img) { return "success"; }
var multipartFormDataContent = new MultipartFormDataContent() { { new StreamContent(File.OpenRead(filePath)), // 该名称要与 API 的参数名称一致 "img", Path.GetFileName(filePath) }, };

如果有批量上传文件,则可以使用 IFormFileCollection 接收。

[HttpPost("/form4")] public string Form4([FromForm] IFormFileCollection imgs) { return "success"; }

如果需要同时接收文件和表单,可以使用模型类:

[HttpPost("/form5")] public string Form5([FromForm] Form5Model model) { return "success"; } public class Form5Model { public string Id { get; set; } public string Name { get; set; } public IFormFile Img { get; set; } }

JSON

http 请求携带 body json 数据时,需要在 header 中通过 Content-Type 指出 body 的类型。

常见 body 类型有以下五种:

类型content-type
Texttext/plain
JavaScriptapplication/javascript
HTMLtext/html
JSONapplication/json
XMLapplication/xml

在 HttpClient 中,请求体可以使用 StringContent 来表示,然后在标头中添加 Content-Type。

// Json 等 public static async Task Json<T>(T obj) where T : class { var json = System.Text.Json.JsonSerializer.Serialize(obj); var jsonContent = new StringContent(json); // 请求时要指定 Content-Type 属性 jsonContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); using var httpClient = new HttpClient(); var response = httpClient.PostAsync("https://localhost:5001/json", jsonContent).Result; var result = await response.Content.ReadAsStringAsync(); } public class JsonModel { public string Id { get; set; } public string Name { get; set; } } await HttpClientHelper.Json(new JsonModel { Id = "1", Name = "工良" });

ASP.NET Core 中通过模型类来接收 json:

[HttpPost("/json")] public string Json([FromBody] JsonModel model) { return "success"; } public class JsonModel { public string Id { get; set; } public string Name { get; set; } }

如果不确定推送的数据有什么字段或 json 的结构是动态的,可以 object 类型接收参数:

[HttpPost("/json1")] public string Json1([FromBody] object model) { return "success"; }

但是这个 object 是何种类型,这个跟 ASP.NET Core 配置的序列化框架有关系。

[HttpPost("/json2")] public string Json2([FromBody] object model) { if (model is System.Text.Json.Nodes.JsonObject jsonObject) { } else if (model is Newtonsoft.Json.Linq.JObject jObject) { } ... ... return "success"; }

请求凭证

客户端请求服务器时,需要通过授权认证许可,方能获取服务器资源,目前比较常见的认证方式有 basic、jwt、cookie 等。

basic 认证示例

basic 认证比较简单,主要在可信任网络中使用,多用于路由器和嵌入式设备,账号密码仅仅使用 base64 进行编码,安全性较差。

// basic 认证 public static async Task<string> Basic(string url, string user, string password) { using HttpClient client = new HttpClient(httpclientHandler); AuthenticationHeaderValue authentication = new AuthenticationHeaderValue( "Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{user}:{password}") )); client.DefaultRequestHeaders.Authorization = authentication; var response = await client.GetAsync(url); return await response.Content.ReadAsStringAsync(); }

jwt 认证示例

jwt 是目前最常用的认证方式之一,在微服务时代的应用越来越广泛,通过是在 header 中添加 Authentication 参数传递 jwt。

// jwt认证 public static async Task<string> Jwt(string token, string url) { using var client = new HttpClient(httpclientHandler); // 创建身份认证 // System.Net.Http.Headers.AuthenticationHeaderValue; client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = await client.GetAsync(url); return await response.Content.ReadAsStringAsync(); }

cookie 示例

HttpClient 中,cookie有两种处理方式。

一种是已经有 cookie 值,直接将 cookie 存储到 HttpClient 中,另一种是还没有 cookie,通过账号密码登录获取到 cookie,自动存储到 HttpClient 对象中,接着使用当前 HttpClient 对象请求其它 URL。

很多时候,我们需要复用同一个 HttpClient,可以设置 HttpClientHandler 的 UseCookies 属性,UseCookies 获取或设置一个值,该值指示处理程序是否使用 CookieContainer 属性存储服务器 Cookie,并在发送请求时使用这些 cookie,这样在同一个 HttpClient 中,携带的 Cookie 是一致的。

httpclientHandler 配置:

var httpclientHandler = new HttpClientHandler() { UseCookies = true };

先用账号密码登陆再请求,成功后,服务器会向 http 客户端写入 cookie,因此复用同一个 HttpClient 即可携带 cookie。

// 获取登录后的 HttpClient public static async Task<HttpClient> Cookie(string user, string password, string loginUrl) { var httpclientHandler = new HttpClientHandler() { ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true, UseCookies = true }; var loginContent = new FormUrlEncodedContent(new[] { new KeyValuePair<string,string>("user",user), new KeyValuePair<string, string>("password",password) }); var httpClient = new HttpClient(httpclientHandler); var response = await httpClient.PostAsync(loginUrl, loginContent); if (response.IsSuccessStatusCode) return httpClient; throw new Exception($"请求失败,http 状态码:{response.StatusCode}"); }

当已经获取 cookie 时,可以直接设置其值:

// 自行设置 cookie public static async Task<string> Cookie(string cookie, string url) { var httpclientHandler = new HttpClientHandler() { ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true, }; using var client = new HttpClient(httpclientHandler); client.DefaultRequestHeaders.Add("Cookie", cookie); var response = await client.GetAsync(url); return await response.Content.ReadAsStringAsync(); }

异常处理

HttpClient 异常主要有三种,请求异常 HttpRequestException 、操作取消异常 OperationCanceledException 、超时异常 TimeoutException,此外还有一些特殊情况导致的异常。

HttpClient 收到响应之后,如果状态码小于 0 或大于 999,则视为无效状态码,弹出 ArgumentOutOfRangeException 异常:

public HttpResponseMessage(HttpStatusCode statusCode) { if (((int)statusCode < 0) || ((int)statusCode > 999)) { throw new ArgumentOutOfRangeException("statusCode"); } }

HttpClient 将 200-299 的状态码视为正常状态码,当响应出现其它状态码时,会弹出 HttpRequestException 异常:

public bool IsSuccessStatusCode { get { return ((int)statusCode >= 200) && ((int)statusCode <= 299); } } public HttpResponseMessage EnsureSuccessStatusCode() { if (!IsSuccessStatusCode) { ... ... throw new HttpRequestException(... ...); } ... ... }

OperationCanceledException 会在 CancellationToken 超时或取消后抛出,而 TimeoutException 主要在两种情况下会出现,一种是网络请求超时,一种是 CancellationToken 超时。由于两种异常都会因为 CancellationToken 超时而抛出,捕获异常时需要注意区分两者:

catch (OperationCanceledException ex) when (ex.InnerException is TimeoutException tex) { Console.WriteLine($"Timed out: {ex.Message}, {tex.Message}"); }

IHttpClientFactory

一般来说,直接使用 HttpClient 需要自行管理生命周期,手动释放连接资源,而且在短时间内大量使用 HttpClient 会导致系统性能严重下降。所以 C# 推出了 IHttpClientFactory ,可以通过 IHttpClientFactory 创建自动管理的 HttpClient 对象,统一控制 HttpClient 对象的行为,如统一配置 HttpClientMessageHandler、添加超时重试机制等。

IHttpClientFactory 基础

本节示例代码可参考 Demo6.HttpFactory 项目。

只需要添加 Microsoft.Extensions.Http 包即可使用 IHttpClientFactory。

IHttpClientFactory 主要有三种注入方式:

static void Main() { var services = new ServiceCollection(); services.AddScoped<Test1>(); services.AddScoped<Test2>(); services.AddScoped<Test3>(); // 1 services.AddHttpClient(); // 2 services.AddHttpClient("Default"); // 3 services.AddHttpClient<Program>(); } public class Test1 { public Test1(IHttpClientFactory httpClientFactory) { var httpClient = httpClientFactory.CreateClient(); // 也可以使用: // httpClientFactory.CreateClient("Default"); } } public class Test2 { public Test2(IHttpClientFactory httpClientFactory) { var httpClient = httpClientFactory.CreateClient("Default"); } } public class Test3 { public Test3(HttpClient httpClient) { } }

通过第二、三种方式注入 HttpClient ,可以配置 HttpClient 的行为,比如携带默认参数、绑定 HttpMessageHandler 等。

// 2 services.AddTransient<MyDelegatingHandler>(); services.AddHttpClient("Default") .ConfigureHttpClient(x => { x.MaxResponseContentBufferSize = 1024; x.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "xxxxx"); }) .AddHttpMessageHandler<MyDelegatingHandler>();

为了能够清晰的记录请求的日志便于排查问题,或者需要拦截 http 请求和响应,可以实现一个 DelegatingHandler 类型,通过容器注册为 Transient 服务,最后使用.AddHttpMessageHandler<MyDelegatingHandler>();注入 DelegatingHandler 服务。

DelegatingHandler 示例如下:

public class MyDelegatingHandler : DelegatingHandler { private readonly ILogger<MyDelegatingHandler> _logger; public MyDelegatingHandler(ILogger<MyDelegatingHandler> logger) { _logger = logger; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { HttpResponseMessage httpResponseMessage = null; try { httpResponseMessage = await base.SendAsync(request, cancellationToken); #if DEBUG _logger.LogDebug(MyException.CreateMessage(request, httpResponseMessage)); #endif if (httpResponseMessage.IsSuccessStatusCode) { return httpResponseMessage; } throw new MyException(request, httpResponseMessage); } catch (Exception) { _logger.LogError(MyException.CreateMessage(request, httpResponseMessage)); throw; } } }

请求策略

Microsoft.Extensions.Http.Polly 是一个 HttpClient 扩展库,可以以流畅且线程安全的方式处理 http 请求重试、断路器、超时、Bulkhead 隔离和回退。

示例代码参考 Demo6.Polly 项目。

public static void Test() { var services = new ServiceCollection(); services.AddHttpClient("Default", client => { client.BaseAddress = new Uri("http://localhost:5000"); }) .AddPolicyHandler(GetRetryPolicy()) .SetHandlerLifetime(TimeSpan.FromMinutes(5)); } static IAsyncPolicy<HttpResponseMessage> GetRetryPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); }

此外 Polly.Contrib.WaitAndRetry 包中具有很多扩展,可以更加细粒度地定制重试等策略。

示例代码参考 Demo6.PollyContrib 项目。

public async Task<string> GetAsync() { var delay = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(1), retryCount: 5); var retryPolicy = Policy .Handle<HttpRequestException>(ex => { // 请求时出现这几种情况,允许重试 if (ex.StatusCode == HttpStatusCode.BadGateway || ex.StatusCode == HttpStatusCode.GatewayTimeout || ex.StatusCode == HttpStatusCode.ServiceUnavailable) return true; return false; }) // 其它方面的异常捕获 .WaitAndRetryAsync(delay); var result = await retryPolicy.ExecuteAsync<string>(async () => { var responseMessage = await _httpClient.GetAsync("https://www.baidu.com"); return await responseMessage.Content.ReadAsStringAsync(); }); return result; }

Refit 框架的使用方法

Refit 是一个动态代码生成库,开发者只需要编写接口, Refit 自动生成对应的 HttpClient 代码。直接使用 HttpClient 发起 http 请求,我们需要编写大量的调用代码,以及封装参数传递和异常处理,如果我们使用 Refit 框架编写客户端,可以减少大量的重复工作。

本书不会对 Refit 做详细的介绍,如需了解 Refit,请参考官方文档 Refit | The automatic type-safe REST library for Xamarin and .NET

本节示例代码请参考 Demo6.Refit 项目。通过 nuget 搜索引入 Refit.HttpClientFactory、Refit.Newtonsoft.Json,

有这样一个 API:

[ApiController] [Route("[controller]")] public class IndexController : ControllerBase { [HttpGet("name")] public string GetName([FromQuery] string name) { return name; } }

使用 Refit 时,客户端只需要编写接口,然后填写对应的参数类型和返回值。

public interface IDemo6Client { [Get("/index/name")] Task<string> GetAsync([Query] string name); }

通过依赖注入注册 IDemo6Client 服务。

services.AddRefitClient<IDemo6Client>() .ConfigureHttpClient(c => c.BaseAddress = new Uri(url)) .SetHandlerLifetime(TimeSpan.FromSeconds(3));

或者使用静态方法构造生成:

var client = RestService.For<IDemo6Client>(url, new RefitSettings());

可以定制 Http 请求时,如何序列化以及反序列化。

JsonSerializerSettings j1 = new JsonSerializerSettings() { DateFormatString = "yyyy-MM-dd HH:mm:ss" }; RefitSettings r1 = new RefitSettings(new NewtonsoftJsonContentSerializer(j1)); //JsonSerializerOptions j2 = new JsonSerializerOptions(); //RefitSettings r2 = new RefitSettings(new SystemTextJsonContentSerializer(j2)); services.AddRefitClient<IDemo6Client>(r1) .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://baidu.com"));

很多时候请求地址是动态的,在运行时才能确定地址。比如在物联网中有很多设备,每个设备上都有 web 服务,我们编写控制台应用向设备发送请求。

可以在编写接口时添加HttpClient Client属性,Refit 会自动注入 HttpClient 服务。

public interface IDemo6ClientDynamic { HttpClient Client { get; } [Get("/index/name")] Task<string> GetAsync([Query] string name); }

在获得 IDemo6ClientDynamic 服务实例之后,再配置新的地址。

services.AddRefitClient<IDemo6ClientDynamic>() .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://baidu.com")) .SetHandlerLifetime(TimeSpan.FromSeconds(3)); ioc = services.BuildServiceProvider(); var clientDynamic = ioc.GetRequiredService<IDemo6ClientDynamic>(); clientDynamic.Client.BaseAddress = new Uri("https://baidu.com"); await clientDynamic.GetAsync("test");

当然,Refit 也可以跟 Policy 类的框架一起使用。

services.AddRefitClient<IDemo6Client>() .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://baidu.com")) .SetHandlerLifetime(TimeSpan.FromSeconds(3)) .AddPolicyHandler(BuildRetryPolicy()); // 构建重试策略 static IAsyncPolicy<HttpResponseMessage> BuildRetryPolicy() { return HttpPolicyExtensions .HandleTransientHttpError() .OrResult(msg => msg.StatusCode == System.Net.HttpStatusCode.NotFound) .WaitAndRetryAsync(6, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))); }

编写 HttpClient 时,尽量使用 IHttpClientFactory,为了快速开发,我们可以使用类似 Refit 之类的框架动态生成代码,要注意处理请求中出现的错误,注意请求的超时时间、重试策略、不同请求失败原因处理。

cli 工具

Refit 代码生成工具

但是手动配置每个 API,这个工作实在过于重复低效,因此我们需要一个工具,能够直接将 swagger 文档转换为 Refit 代码。

安装 Refitter 工具。

dotnet tool install --global Refitter

启动 Demo6.Api 服务。

然后执行命令从 swagger 文件生成 C# 代码。

refitter http://localhost:5001/swagger/v1/swagger.json --namespace "MyApi" --output ./IDemo6Api.cs

命令执行完毕之后,可以在目录中找到 IDemo6Api.cs 文件。

使用 Refit 框架,我们只需要定义接口即可,不需要编写大量繁杂的 HttpClient 代码,大大简化了配置 HttpClient 、请求策略控制等。而使用 Refitter 框架,更是把编写接口的过程省略了。

制作 .NET 工具包

.NET 工具包是一种特殊的 NuGet 包,比如 dotnet-dump、Refitter 等,都属于工具包。在企业内部开发时,为了提高工作效率,往往需要制作一些脚本之类的工具,而 .NET 工具包非常时候用于制作这些脚本,然后下方给其他人使用。

本节示例代码请参考 Maomi.Curl 项目,Maomi.Curl 是一个类似 curl 的工具,可以发起 http 请求,在正式开发之前,先安装 Maomi.Curl ,了解其使用方式。

通过命令安装 Maomi.Curl:

dotnet tool install --global Maomi.Curl --version 2.0.0

Maomi.Curl 别名为 mmurl,在终端中输入 mmurl 查看参数列表和使用示例。

jsonplaceholder.typicode.com 是一个用于测试测试 API 请求的网站,我们可以通过相关接口测试 mmurl 的功能,使用 mmurl 发起 get 请求:

mmurl https://jsonplaceholder.typicode.com/todos/1
request: https://jsonplaceholder.typicode.com/todos/1 { "userId": 1, "id": 1, "title": "delectus aut autem", "completed": false }

使用 mmurl 发起 post 请求:

mmurl -X POST -d '{\"userId\":2}' https://jsonplaceholder.typicode.com/posts
request: https://jsonplaceholder.typicode.com/posts { "userId": 2, "id": 101 }

在了解 Maomi.Curl 之后,下面正式开始编写工具包。编写工具包需要掌握两方面的知识点,一个是工具包项目配置,一个是命令行工具包的使用方式。

工具包项目其实就是控制台项目,只是在 .csproj 中需要添加一些属性配置:

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> <RootNamespace>Maomi.Curl</RootNamespace> </PropertyGroup> <PropertyGroup> <!--设置为工具包项目--> <PackAsTool>true</PackAsTool> <!--命令行工具名称--> <ToolCommandName>mmurl</ToolCommandName> <Version>2.0.0</Version> <Description>一个类似 curl 的工具</Description> <PackageId>Maomi.Curl</PackageId> </PropertyGroup> <ItemGroup> <PackageReference Include="System.CommandLine" Version="2.0.0-beta4.22272.1" /> </ItemGroup> </Project>

首先编写 get、post 两个请求方法,这两个方法其实就是使用 HttpClient 进行请求,代码比较简单。

private static async Task GetAsync(string url, IReadOnlyDictionary<string, string> headers, string? cookie = null) { var client = new HttpClient(); BuildHeader(headers, cookie, client); var response = await client.GetAsync(new Uri(url)); Console.WriteLine(await response.Content.ReadAsStringAsync()); } private static async Task PostAsync(string url, IReadOnlyDictionary<string, string> headers, string body, string? cookie = null) { var client = new HttpClient(); BuildHeader(headers, cookie, client); var jsonContent = new StringContent(body); jsonContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json"); var response = await client.PostAsync(new Uri(url), jsonContent); Console.WriteLine(await response.Content.ReadAsStringAsync()); } private static void BuildHeader(IReadOnlyDictionary<string, string> headers, string? cookie, HttpClient client) { if (headers != null && headers.Count > 0) { foreach (var item in headers) client.DefaultRequestHeaders.Add(item.Key, item.Value); } if (!string.IsNullOrEmpty(cookie)) { client.DefaultRequestHeaders.Add("Cookie", cookie); } }

那么,怎么从命令行参数中解析出对应的参数呢?System.CommandLine 是一个命令行工具包,能够帮助开发者简化解析命令行参数的步骤。

static async Task<int> Main(string[] args) { // 定义命令参数 // http header var headers = new Option<Dictionary<string, string>?>( name: "-H", description: "header,ex: -H \"Accept-Language=zh-CN\".", parseArgument: result => { var dic = new Dictionary<string, string>(); if (result.Tokens.Count == 0) return dic; foreach (var item in result.Tokens) { var header = item.Value.Split("="); dic.Add(header[0], header[1]); } return dic; }) { // 可以出现 0 或多次 Arity = ArgumentArity.ZeroOrMore, }; var cookie = new Option<string?>( name: "-b", description: "cookie.") { Arity = ArgumentArity.ZeroOrOne }; var body = new Option<string?>( name: "-d", description: "post body.") { Arity = ArgumentArity.ZeroOrOne }; var httpMethod = new Option<string?>( name: "-X", description: "GET/POST ...", getDefaultValue: () => "GET") { Arity = ArgumentArity.ZeroOrOne }; // 其它无名的参数 var otherArgument = new Argument<string>(); // 构建命令行参数 var rootCommand = new RootCommand("输入参数请求 url 地址"); rootCommand.AddOption(headers); rootCommand.AddOption(cookie); rootCommand.AddOption(body); rootCommand.AddOption(httpMethod); rootCommand.Add(otherArgument); // 解析参数调用 rootCommand.SetHandler(async (headers, cookie, body, httpMethod, otherArgument) => { Console.WriteLine($"request: {otherArgument}"); if (headers == null) headers = new Dictionary<string, string>(); try { if (!string.IsNullOrEmpty(body) || "POST".Equals(httpMethod, StringComparison.InvariantCultureIgnoreCase)) { ArgumentNullException.ThrowIfNull(body); await PostAsync(otherArgument, headers, body, cookie); } else { await GetAsync(otherArgument, headers, cookie); } } catch (Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(ex.Message); Console.ResetColor(); } }, headers, cookie, body, httpMethod, otherArgument); return await rootCommand.InvokeAsync(args); }
http://www.jsqmd.com/news/1118462/

相关文章:

  • 笔者为某云计算公司产品经理,负责产品的产品设计与前端开发管理。在工作引发了公司级别对产品和设计的讨论,有了以下文章。原文均作为邮件发在公司内部,以下截取出来希望收到更多的讨论。weibo:@侯振宇L4
  • 6DOF运动追踪系统设计与IMU姿态解算优化
  • 工业4-20mA电流环发射器设计与优化实践
  • 如何让Unitree Go2机器人具备ROS2环境感知与自主导航能力
  • CBCX平台:围绕合规意识与外汇行业合规表达的清单复盘
  • 智能客流分析:从计算机视觉到多模态感知的商业应用
  • 【JAVA毕设源码分享】基于springboot智能药箱系统的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 从AI代码风格到工程实践:Codex Taste如何重塑开发者的代码质量观
  • 通达信缠论插件:3分钟让复杂技术分析变得可视化
  • 3步彻底优化Windows 11:这款开源神器让系统轻快又安全
  • STM32F407ZG与LV30工业条码扫描器集成方案
  • C语言实现量子密钥分发(BB84)协议:从原理到代码实战
  • 工业防爆安防系统选型参考:山东区域四家防爆监控厂商技术能力分析
  • QtScrcpy终极指南:5分钟掌握免费安卓投屏神器,大屏玩转手机游戏
  • 魔兽争霸3终极优化指南:3步解决Win10/Win11卡顿闪退问题
  • 从零开始掌握AI编程助手:Codex核心概念、安装配置与实战应用全指南
  • 灾害响应中的多语言情感分析实战:零标注、低延迟、高可解释
  • Mermaid Live Editor:免费在线图表编辑器的终极指南,3分钟快速上手
  • STM32扩展EEPROM存储实战:M24M01E-F应用指南
  • MobileNet手写汉字识别实战:环境配置到模型部署全流程避坑指南
  • SquirrelScan:模块化网络资产发现与漏洞扫描工具实战指南
  • 开源安全仪表盘:API密钥管理与监控的工程实践
  • MTBF, MTTR, MTTF 三个概念的区别和对比
  • STM32F207ZG与25CSM04 Page EEPROM高速数据存储方案
  • 高效无损音乐管理终极指南:TIDAL-DL-NG如何重塑你的数字音乐体验
  • 气候适配科技面料推荐程序,根据地域温湿度匹配透气保暖功能性服饰。
  • [线性代数]正定矩阵
  • 海量用户积分排名算法探讨
  • 为什么峰值是有效值的√2倍?
  • Selenium UI自动化测试:从零搭建框架与最佳实践指南