项目

RestSharp 基础知识

RestSharp 主要是一个围绕 HttpClient 的封装,它允许你执行以下操作:

  • 一次设置客户端的默认参数(不限于头信息)
  • 以直观的方式向每个请求添加任何类型的参数(查询、URL 片段、表单、附件、序列化体、头信息)
  • 如果需要,将负载序列化为 JSON 或 XML
  • 设置正确的内容头(内容类型、位置、长度等)
  • 处理远程端点的响应
  • 如果需要,从 JSON 或 XML 解析响应

API 客户端

调用外部 HTTP API 最好的方式是创建一个类型化的客户端,该客户端封装了 RestSharp 调用,不会在公共接口中暴露 RestClient 实例。

示例页面上有一个 Twitter API 客户端的实例。

创建客户端

可以通过以下构造函数之一创建一个 RestSharp 客户端:

// 使用给定基地址创建一个带有默认选项的客户端
var client = new RestClient("https://localhost:5000");

// 使用 RestClientOptions 对象创建客户端
var options = new RestClientOptions("https://localhost:5000") {
    MaxTimeout = 1000
};
var client = new RestClient(options);

简单工厂

另一种创建客户端实例的方法是使用简单的客户端工厂。工厂会使用客户端选项的 BaseUrl 属性来缓存 HttpClient 实例。每个不同的基地址都会得到自己的 HttpClient 实例。其他选项不会影响缓存。因此,如果使用不同选项但基地址相同,你会得到相同的 HttpClient 实例,它不会被配置为新的选项。在第一次创建客户端实例后应用的选项会被忽略,这些选项包括:

  • Credentials
  • UseDefaultCredentials
  • AutomaticDecompression
  • PreAuthenticate
  • FollowRedirects
  • RemoteCertificateValidationCallback
  • ClientCertificates
  • MaxRedirects
  • MaxTimeout
  • UserAgent
  • Expect100Continue

用于配置 HttpMessageHandler 和默认 HttpClient 头部信息的构造函数参数,对于缓存的实例也会被忽略,因为工厂仅配置一次处理器。

要在构造函数中启用工厂,你需要将 useClientFactory 参数设置为 true

var client = new RestClient("https://api.twitter.com/2", true);

创建请求

在使用 RestClient 进行请求之前,你需要创建一个 RestRequest 实例:

var request = new RestRequest(resource); // resource 是客户端基路径的子路径

默认的请求类型是 GET,你可以通过设置 Method 属性覆盖它。也可以使用构造函数重载设置方法:

var request = new RestRequest(resource, Method.Post);

创建 RestRequest 后,你可以向其中添加参数。以下是你可以在 RestSharp 中使用的所有参数类型。

添加头信息

将标题参数作为 HTTP 头添加到随请求一起发送的头部中。头名称为参数的名称,头值为该参数的值。

您可以使用以下任一请求方法来添加头参数:

AddHeader(string name, string value);
AddHeader<T>(string name, T value); // value 将转换为字符串
AddOrUpdateHeader(string name, string value); // 如果已存在则替换头信息

例如:

var request = new RestRequest("/path").AddHeader("X-Key", someKey);

你也可以向客户端添加默认头信息,它们将被添加到客户端发出的每个请求中。这对于添加身份验证头非常有用。

client.AddDefaultHeader(string name, string value);

GET 或 POST 参数

默认的 RestSharp 参数类型是 GetOrPostParameter。你可以使用 AddParameter 函数向请求添加 GetOrPost 参数:

request
    .AddParameter("name1", "value1")
    .AddParameter("name2", "value2");

GetOrPost 根据 HTTP 方法的行为有所不同。如果你执行 GET 调用,RestSharp 将在 URL 后追加参数,如 url?name1=value1&name2=value2

对于 POSTPUT 请求,如果请求没有文件附加,则参数将作为请求体发送,形式为 name1=value1&name2=value2。同时,请求将以 application/x-www-form-urlencoded 方式发送。

在两种情况下,名称和值都将自动进行 URL 编码,除非另有说明:

request.AddParameter("name", "Væ üé", false); // 不编码值

如果有文件,RestSharp 将发送一个 multipart/form-data 请求。参数将作为请求的一部分,格式如下:

Content-Disposition: form-data; name="parameterName"

ParameterValue

你也可以将 GetOrPost 参数作为默认参数添加到客户端。这将向客户端发出的每个请求添加参数。

client.AddDefaultParameter("foo", "bar");

这与请求参数的工作方式相同,只是它会被添加到每个请求中,而不是单独请求。

使用 AddObject

如果你收集所有参数在一个对象中,然后使用 AddObject,可以避免多次调用 AddParameter

例如:

var params = new {
    status = 1,
    priority = "high",
    ids = new[] { "123", "456" }
};
request.AddObject(params);

这等同于:

request.AddParameter("status", 1);
request.AddParameter("priority", "high");
request.AddParameter("ids", "123,456");

请注意,AddObject 只适用于具有基本类型属性的对象。它也适用于包含基本类型数组的情况,如上面所示。

如果需要覆盖属性名称或格式,可以使用 RequestProperty 属性。例如:

public class RequestModel {
    // 重命名并指定格式
    [RequestProperty(Name = "from_date", Format = "d")]
    public DateTime FromDate { get; set; }
}

// 添加到请求
request.AddObject(new RequestModel { FromDate = DateTime.Now });

在这种情况下,请求将获得名为 from_date 的 GET 或 POST 参数,其值为当前日期的短日期格式。

使用 AddObjectStatic

AddObjectStatic<T>(...) 方法允许使用预编译表达式获取对象属性值。与使用反射的 AddObject 相比,AddObjectStatic 缓存从类型 T 的对象获取属性的函数,因此速度更快。

要使用自定义参数名称和格式,或者指定需要作为参数使用的属性列表,请使用 RequestProperty 属性。例如:

class TestObject {
    [RequestProperty(Name = "some_data")]
    public string SomeData { get; set; }

    [RequestProperty(Format = "d")]
    public DateTime SomeDate { get; set; }

    [RequestProperty(Name = "dates", Format = "d")]
    public DateTime[] DatesArray { get; set; }

    public int        Plain      { get; set; }
    public DateTime[] PlainArray { get; set; }
}

URL 片段参数

GetOrPost 不同,URL 片段参数会替换请求 URL 中的占位符值:

var request = new RestRequest("health/{entity}/status")
    .AddUrlSegment("entity", "s2");

当请求执行时,RestSharp 将尝试匹配任何 {placeholder} 与参数名称(不带 {})并用值替换。因此,上述代码将生成 health/s2/status 作为 URL。

你也可以将 UrlSegment 参数作为默认参数添加到客户端。这将向客户端发出的每个请求添加参数。

client.AddDefaultUrlSegment("foo", "bar");

Cookie

你可以使用 AddCookie 方法向请求添加 cookie:

request.AddCookie("foo", "bar");

RestSharp 将将来自请求的 cookie 添加为头,并从响应中提取匹配的 cookie。你可以通过 RestResponse.Cookies 属性观察并提取响应 cookie,该属性具有 CookieCollection 类型。

然而,使用默认 URL 片段参数可能有疑问,因为你可以直接将参数值包含在客户端的基础 URL 中。但是,请求级别有一个 CookieContainer。你可以为请求分配一个预先填充的容器 request.CookieContainer,或者让容器在调用 AddCookie 时由请求创建。尽管如此,容器仅用于从容器中提取所有 cookie 并为请求创建 cookie 头,而不是直接使用容器。这是因为 cookie 容器通常在 HttpClientHandler 水平上配置,cookie 在同一个客户端实例发出的请求之间共享。在大多数情况下,这种行为可能是有害的。

如果您的用例需要在客户端实例发出的请求之间共享 cookie,可以使用客户端级别的 CookieContainer,您需要通过选项属性提供它。您可以使用容器 API 向容器中添加 cookie。但是,响应 cookie 不会自动添加到容器中,但您可以在代码中获取响应的 Cookies 属性并将它们添加到通过 IRestClient.Options.CookieContainer 属性可访问的客户端级容器中。

请求 Body

RestSharp 支持多种方式添加请求体:

  • AddJsonBody 用于 JSON 请求体
  • AddXmlBody 用于 XML 请求体
  • AddStringBody 用于预序列化的 请求体

我们建议使用 AddJsonBodyAddXmlBody 方法,而不是使用 AddParameterBodyParameter 类型。这些方法会设置正确的请求类型,并为您完成序列化工作。

当您执行 POSTPUTPATCH 请求,并添加了 GetOrPost 参数(参见此处),RestSharp 将默认将它们作为 URL 编码表单请求体发送。如果有文件,则会发送 multipart/form-data 请求。您还可以通过设置 AlwaysMultipartFormData 属性为 true 来指示 RestSharp 使用 multipart/form-data 发送请求体。

如有必要,您可以指定自定义请求体内容类型。所有添加请求体的方法都支持 contentType 参数。

无法在客户端级别设置默认的请求体参数。

字符串 Body

如果您有一个预序列化的 body,如 JSON 字符串,可以使用 AddStringBody 将其添加为 body 参数。您需要指定内容类型,以便远程端点知道如何处理请求体。例如:

const json = "{ data: { foo: \"bar\" } }";
request.AddStringBody(json, ContentType.Json);

JSON Body

调用 AddJsonBody 会为您完成以下操作:

  • 指令 RestClient 在发出请求时将对象参数序列化为 JSON
  • 设置内容类型为 application/json
  • 将内部请求体数据类型设置为 DataType.Json

示例:

var param = new MyClass { IntData = 1, StringData = "test123" };
request.AddJsonBody(param);

您可以提供 contentType 参数覆盖默认内容类型。例如:

request.AddJsonBody(param, "text/x-json");

如果使用预序列化的字符串与 AddJsonBody 结合,它将直接发送。AddJsonBody 会检测参数是否为字符串,并将其作为带有 JSON 内容类型的字符串 body 添加。

这意味着,当您使用 AddJsonBody 时,顶级字符串不会被序列化为 JSON。要解决这个问题,您可以使用一个允许 AddJsonBody 序列化字符串的重载:

const string payload = @"
""requestBody"": {
    ""content"": {
        ""application/json"": {
            ""schema"": {
                ""type"": ""string""
            }
        }
    }
},";
request.AddJsonBody(payload, forceSerialize: true); // 字符串会被序列化
request.AddJsonBody(payload); // 字符串不会被序列化并直接发送

XML Body

调用 AddXmlBody 会为您完成以下操作:

  • 指令 RestClient 在发出请求时将对象参数序列化为 XML
  • 设置内容类型为 application/xml
  • 将内部请求体数据类型设置为 DataType.Xml
警告
请勿将 XML 字符串传递给 AddXmlBody,这将不起作用!

查询字符串

QueryString 的工作方式类似于 GetOrPost,但无论请求方法如何,它都会始终将参数附加到 URL,格式为 url?name1=value1&name2=value2

示例:

var client = new RestClient("https://search.me");
var request = new RestRequest("search")
    .AddParameter("foo", "bar");
var response = await client.GetAsync<SearchResponse>(request);

这将向 https://search.me/search?foo=bar 发送 GET 请求。

对于 POST 风格的请求,您需要显式添加查询字符串参数:

request.AddQueryParameter("foo", "bar");

在某些情况下,您可能需要阻止 RestSharp 对查询字符串参数进行编码。 要做到这一点,添加参数时将 encode 参数设置为 false

request.AddQueryParameter("foo", "bar/fox", false);

您也可以将查询字符串参数作为客户端的默认参数添加。这将使该参数添加到客户端发出的所有请求中。

client.AddDefaultQueryParameter("foo", "bar");

上述代码将导致客户端实例发出的所有请求在查询字符串中都包含 foo=bar

发起请求

一旦您在 RestRequest 上添加了所有参数,就可以准备发出请求了。

RestClient 只有一个用于此目的的方法:

public async Task<RestResponse> ExecuteAsync(
    RestRequest request,
    CancellationToken cancellationToken = default
)

您还可以不事先设置请求方法,并使用以下重载之一:

Task<RestResponse> ExecuteGetAsync(RestRequest request, CancellationToken cancellationToken)
Task<RestResponse> ExecutePostAsync(RestRequest request, CancellationToken cancellationToken)
Task<RestResponse> ExecutePutAsync(RestRequest request, CancellationToken cancellationToken)

使用这些方法时,您将在 response.Content 中获得响应内容。

RestSharp 可以帮助您自动反序列化响应。要使用这个功能,请使用泛型重载之一:

Task<RestResponse<T>> ExecuteAsync<T>(RestRequest request, CancellationToken cancellationToken)
Task<RestResponse<T>> ExecuteGetAsync<T>(RestRequest request, CancellationToken cancellationToken)
Task<RestResponse<T>> ExecutePostAsync<T>(RestRequest request, CancellationToken cancellationToken)
Task<RestResponse<T>> ExecutePutAsync<T>(RestRequest request, CancellationToken cancellationToken)

所有以 Execute 开头的重载在服务器返回错误时不抛出异常。有关更多信息,请参阅 这里

如果您只需要反序列化的响应,可以使用扩展方法之一:

Task<T> GetAsync<T>(RestRequest request, CancellationToken cancellationToken)
Task<T> PostAsync<T>(RestRequest request, CancellationToken cancellationToken)
Task<T> PutAsync<T>(RestRequest request, CancellationToken cancellationToken)
Task<T> HeadAsync<T>(RestRequest request, CancellationToken cancellationToken)
Task<T> PatchAsync<T>(RestRequest request, CancellationToken cancellationToken)
Task<T> DeleteAsync<T>(RestRequest request, CancellationToken cancellationToken)

这些扩展方法如果服务器返回错误会抛出异常,因为没有其他方法将错误返回给调用者。

IRestClient 接口还提供了不进行反序列化的请求扩展方法,即使客户端配置为不抛出异常,这些方法也会在服务器返回错误时抛出异常。

Task<RestResponse> GetAsync(RestRequest request, CancellationToken cancellationToken)
Task<RestResponse> PostAsync(RestRequest request, CancellationToken cancellationToken)
Task<RestResponse> PutAsync(RestRequest request, CancellationToken cancellationToken)
Task<RestResponse> HeadAsync(RestRequest request, CancellationToken cancellationToken)
Task<RestResponse> PatchAsync(RestRequest request, CancellationToken cancellationToken)
Task<RestResponse> DeleteAsync(RestRequest request, CancellationToken cancellationToken)

JSON 请求

要使用预形成的资源字符串执行简单的 GET 请求并获取反序列化的 JSON 响应,请使用:

var response = await client.GetJsonAsync<TResponse>("endpoint?foo=bar", cancellationToken);

您也可以使用更高级的扩展方法,该方法使用对象来构建资源字符串:

var client = new RestClient("https://example.org");
var args = new {
    id = "123",
    foo = "bar"
};
// 将向 https://example.org/endpoint/123?foo=bar 发送请求
var response = await client.GetJsonAsync<TResponse>("endpoint/{id}", args, cancellationToken);

它会搜索 URL 段参数,匹配对象的任何属性,并用值替换它们。其他所有属性将作为查询参数使用。

对于 POST 请求,也有类似的功能。

var request = new CreateOrder("123", "foo", 10100);
// 将请求对象以 JSON 形式 POST 到 "orders",并返回一个 JSON 响应,解序列化为 OrderCreated
var result = client.PostJsonAsync<CreateOrder, OrderCreated>("orders", request, cancellationToken);
var request = new CreateOrder("123", "foo", 10100);
// 将请求对象以JSON形式POST到"orders",不期望任何响应体,只返回状态码
var statusCode = client.PostJsonAsync("orders", request, cancellationToken);

这两个扩展方法同样适用于 PUT 请求( PutJsonAsync )。

JSON 流式 API

对于那些可以流式传输响应数据的 HTTP API 端点(如 Twitter 搜索流 ),你可以使用 RestSharpStreamJsonAsync<T>,它返回一个 IAsyncEnumerable<T>

public async IAsyncEnumerable<SearchResponse> SearchStream(
    [EnumeratorCancellation] CancellationToken cancellationToken = default
) {
    var response = _client.StreamJsonAsync<TwitterSingleObject<SearchResponse>>(
        "tweets/search/stream", cancellationToken
    );

    await foreach (var item in response.WithCancellation(cancellationToken)) {
        yield return item.Data;
    }
}

这个函数的主要限制是它期望每个 JSON 对象作为单独的一行返回。它无法通过组合多行成一个 JSON 字符串来解析响应。

上传文件

要向请求添加文件,可以使用 RestRequestAddFile 方法。主要函数接受一个 FileParameter 参数:

request.AddFile(fileParameter);

你可以使用 FileParameter.CreateFileParameter.FromFile 方法来实例化文件参数,前者接受字节数组,后者从磁盘加载文件。

还有一些扩展函数,它们封装了 FileParameter 的创建:

// 从磁盘添加文件
AddFile(parameterName, filePath, contentType);

// 添加一个字节数组
AddFile(parameterName, bytes, fileName, contentType);

// 添加由getFile函数返回的流
AddFile(parameterName, getFile, fileName, contentType);

请注意,AddFile 会设置所有必要的头,所以请不要尝试手动设置内容头。

你还可以在调用 AddFile 时提供文件上传选项。这些选项包括:

  • DisableFilenameEncoding(默认为 false ):如果设置为 true,RestSharp 将不会在 Content-Disposition 头中对文件名进行编码
  • DisableFilenameStar(默认为 true ):如果设置为 true,RestSharp 将不会在 Content-Disposition 头中添加 filename* 参数

使用选项的示例:

var options = new FileParameterOptions {
    DisableFilenameEncoding = true,
    DisableFilenameStar = false
};
request.AddFile("file", filePath, options: options);

上述代码片段中指定的选项通常有助于处理包含非 ASCII 字符的文件名上传。

下载二进制数据

有两类函数允许你从远程 API 下载二进制数据。

首先,有 DownloadDataAsync,它返回 Task<byte[]>。它会读取整个二进制响应,然后将整个二进制内容作为字节数组返回。它适用于下载较小的文件。

对于较大的响应,可以使用 DownloadStreamAsync,它返回 Task<Stream>。此函数允许你打开一个流读取器,并异步地将大型响应流式传输到内存或磁盘。

重用 HttpClient

RestSharp 内部使用 HttpClient 执行 HTTP 请求。你可以为多个 RestClient 实例重用同一个 HttpClient 实例。这在你想在多个 RestClient 实例之间共享连接池时很有用。

一种实现方式是使用接受 HttpClientHttpMessageHandler 实例作为参数的 RestClient 构造函数。需要注意的是,在这种情况下,不是所有的 RestClientOptions 都会被使用。以下是可以工作的选项列表:

  • 如果 BaseAddress 未设置,则 BaseAddress 将被用于设置 HttpClient 实例的基础地址。
  • MaxTimeout
  • UserAgent 将作为 HTTP 头添加到 RestClient.DefaultParameters 列表中。这将添加到 RestClient 发出的每个请求中,而 HttpClient 实例不会被修改。这是为了允许 HttpClient 实例在需要不同 User-Agent 头的情况下被重用。
  • Expect100Continue

另一种选择是使用 上面 描述的简单 HTTP 客户端工厂。

Blazor 支持

在 Blazor WebAssembly 应用中,你可以向外部 API 端点发起请求。Microsoft 示例展示了如何使用 HttpClient,同样可以使用 RestSharp 完成相同目的。

需要注意的是,WebAssembly 有一些平台特定的限制。因此,你不能使用所有 RestClient 构造函数来实例化它。实际上,你只能使用接受 HttpClientHttpMessageHandler 作为参数的 RestClient 构造函数。如果你使用无参构造函数,它将调用带默认选项的可选参数构造函数。可选参数构造函数将尝试使用提供的选项创建一个 HttpMessageHandler 实例,但在 Blazor 中,这将失败,因为其中一些选项抛出 “不支持平台” 异常。

这里是一个如何全局注册单例 RestClient 实例的示例:

builder.Services.AddSingleton(new RestClient(new HttpClient()));

然后,在一个页面上,你可以注入该实例:

@page "/fetchdata" 
@using RestSharp 
@inject RestClient _restClient

接着使用它:

@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnInitializedAsync() {
        forecasts = await _restClient.GetJsonAsync<WeatherForecast[]>("http://localhost:5104/weather");
    }

    public class WeatherForecast {
        public DateTime Date { get; set; }
        public int TemperatureC { get; set; }
        public string? Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}

在这种情况下,请求将发送到运行在 http://localhost:5104/weather 的 WebAPI 服务器。请记住,如果 WebAPI 服务器本身没有托管 WebAssembly,它需要配置 CORS 策略,以便浏览器允许 WebAssembly 起源访问 API 端点。

在本文档中