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);
内容类型
RestSharp 会自动使用正确的内容类型。除非绝对必要,否则请避免手动向请求添加 Content-Type
头。你可以为 请求体参数 添加自定义内容类型。
GET 或 POST 参数
默认的 RestSharp 参数类型是 GetOrPostParameter
。你可以使用 AddParameter
函数向请求添加 GetOrPost
参数:
request
.AddParameter("name1", "value1")
.AddParameter("name2", "value2");
GetOrPost
根据 HTTP 方法的行为有所不同。如果你执行 GET
调用,RestSharp 将在 URL 后追加参数,如 url?name1=value1&name2=value2
。
对于 POST
或 PUT
请求,如果请求没有文件附加,则参数将作为请求体发送,形式为 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
用于预序列化的 请求体
我们建议使用 AddJsonBody
或 AddXmlBody
方法,而不是使用 AddParameter
和 BodyParameter
类型。这些方法会设置正确的请求类型,并为您完成序列化工作。
当您执行 POST
、PUT
或 PATCH
请求,并添加了 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 搜索流 ),你可以使用 RestSharp
和 StreamJsonAsync<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 字符串来解析响应。
上传文件
要向请求添加文件,可以使用 RestRequest
的 AddFile
方法。主要函数接受一个 FileParameter
参数:
request.AddFile(fileParameter);
你可以使用 FileParameter.Create
或 FileParameter.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
实例之间共享连接池时很有用。
一种实现方式是使用接受 HttpClient
或 HttpMessageHandler
实例作为参数的 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
构造函数来实例化它。实际上,你只能使用接受 HttpClient
或 HttpMessageHandler
作为参数的 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 端点。