19. 远程请求

19.1 关于远程请求#

在互联网大数据的驱动下,平台或系统免不了需要和第三方进行数据交互,而第三方往往提供了 RESTful API 结果规范,这个时候就需要通过 Http 请求第三方接口进行数据传输交互。

也就是本章节所说的远程请求。

19.2 远程请求的作用#

  • 跨系统、跨设备通信
  • 实现多个系统数据传输交互
  • 跨编程语言协同开发

19.3 基础使用#

19.3.1 注册服务#

使用之前需在 Startup.cs 注册 远程请求服务

public void ConfigureServices(IServiceCollection services)
{
services.AddRemoteRequest();
}

19.3.2 使用方式#

  • IHttpDispatchProxy 代理方式

定义代理请求的 接口 并继承 IHttpDispatchProxy 接口

[Host("http://47.100.247.61/", 5000)]
public interface IHttp : IHttpDispatchProxy
{
[Get("api/sysdata/categories")]
Task<RESTfulResult<List<Data>>> GetCategoryAsync();
}

通过构造函数注入 接口

using Furion.DynamicApiController;
using Furion.RemoteRequest;
namespace Furion.Application
{
public class RemoteRequestService : IDynamicApiController
{
private readonly IHttp _http;
public RemoteRequestService(IHttp http)
{
_http = http;
}
public async Task GetData()
{
var data = await _http.GetCategoryAsync();
}
}
}
  • 字符串方式
var data = await "http://47.100.247.61/api/sysdata/categories".GetAsAsync<object>();

19.4 使用示例#

19.4.1 字符串方式#

  • [METHOD]Async/[METHOD]AsAsync 方式
// ============ GET 请求 ============
// 返回 HttpResponseMessage 对象
var response = await "https://www.furion.pro/data".GetAsync();
// 返回特定类型对象
var data = await "http://47.100.247.61/api/sysdata/categories".GetAsAsync<object>();
// 设置请求报文头
var data = await "http://47.100.247.61/api/sysdata/categories".GetAsAsync<object>(headers: new Dictionary<string, string>{
{"ipaddress", "127.0.0.1"}
});
// 设置请求拦截
var data = await "http://47.100.247.61/api/sysdata/categories".GetAsAsync<object>(interceptor: request=>{
request.Headers.Add("Authorization","Bearer token字符串");
});
// ============ HEAD 请求 ============
// 同上
// ============ POST 请求 ============
// 返回 HttpResponseMessage 对象
var response = await "https://www.furion.pro/data".PostAsync(new { parm1="", parm2="" });
// 返回特定类型对象
var data = await "http://47.100.247.61/api/sysdata/categories".PostAsAsync<object>(new { parm1="", parm2="" });
// 设置请求报文头
var data = await "http://47.100.247.61/api/sysdata/categories".PostAsAsync<object>(new { parm1="", parm2="" }, headers: new Dictionary<string, string>{
{"ipaddress", "127.0.0.1"}
});
// 设置请求拦截
var data = await "http://47.100.247.61/api/sysdata/categories".PostAsAsync<object>(new { parm1="", parm2="" }, interceptor: request=>{
request.Headers.Add("Authorization","Bearer token字符串");
});
// ============ PUT 请求 ============
// 同上
// ============ PATCH 请求 ============
// 同上
// ============ DELETE 请求 ============
// 同上
  • SendAsync/SendAsAsync 方式
// 返回 HttpResponseMessage 对象
var response = await "https://www.furion.pro/data".SendAsync(HttpMethod.Get);
// 返回特定类型对象
var data = await "http://47.100.247.61/api/sysdata/categories".SendAsAsync<object>(HttpMethod.Get);
// 设置请求报文头
var data = await "http://47.100.247.61/api/sysdata/categories".SendAsAsync<object>(HttpMethod.Get, headers: new Dictionary<string, string>{
{"ipaddress", "127.0.0.1"}
});
// 设置请求拦截
var data = await "http://47.100.247.61/api/sysdata/categories".SendAsAsync<object>(HttpMethod.Post, interceptor: request=>{
request.Headers.Add("Authorization","Bearer token字符串");
});

19.4.2 代理方式#

  • 通过 特性 方式配置
[Host("http://47.100.247.61/", 5000)] // 配置主机端口
[Header("Authorization", "Bearer 你的token")] // 配置请求头
[Header("ipaddress", "127.0.0.1")] // 配置多个请求头
public interface IHttp : IHttpDispatchProxy
{
[Get("api/sysdata/categories")]
Task<RESTfulResult<List<Data>>> GetCategory();
[Get("api/sysdata/{categoryid}/data")]
Task<RESTfulResult<List<Data2>>> GetData2(int categoryid);
[Post("api/user/modify")]
Task<RESTfulResult<object>> PostVoid(object value);
// 请求拦截器
static HttpRequestMessage RequestInterceptor(HttpRequestMessage httpRequest, MethodInfo method, object[] args)
{
return httpRequest;
}
// 响应拦截器
static HttpResponseMessage ResponseInterceptor(HttpResponseMessage httpResponse, MethodInfo method, object[] args)
{
return httpResponse;
}
}

19.5 代理高级应用#

19.5.1 参数处理#

  • 普通 参数
[Get("https://www.furion.pro/getdata/{id}?name={name}")]
Task<Object> GetData(int id, string name);
  • 数组 参数解构
[Get("https://www.furion.pro/getdata/?{...ids}")]
void DeleteIds(int[] ids);
  • 对象 参数解构
[Get("https://www.furion.pro/getdata/?{...user}")]
void DeleteIds([Query]UserDto user);
类对象解构说明

默认情况下,执行 Get/Head 请求时不会处理 类对象 类型参数,但是如果贴了 [Query] 参数后,会自动将对象解析成 url 地址格式,如 ?name=百小僧&age=27

19.5.2 参数验证#

代理拦截的方式也支持参数 验证特性 使用。如:

[Get("https://www.furion.pro/getdata/{id}?name={name}")]
Task<Object> GetData([Required, Range(1, 1000)] int id, [MaxLength(32)] string name);

另外也支持 类对象 方式,如:

public class User
{
[Required] // 必填验证
[MinLength(4)] // 最小长度验证
public string Account { get; set; }
[Required] // 必填验证
[MaxLength(32)] // 最大长度验证
public string Password { get; set; }
}
[Post("https://www.furion.pro/getdata/{id}?name={name}")]
Task<Object> GetData(User user);

19.5.3 返回值处理#

每一个请求谓词特性都提供了 ResponseType 属性,该属性用来配置返回值类型,默认是 Object 类型。ResponseType 属性可选配置如下:

  • ResponseType 响应类型:
    • Object:对象类型,默认值,也就是自动序列化成代理的方法返回值类型
    • Text:字符串类型
    • Stream:流类型,返回该类型时,需在处理完之后释放流
    • ByteArray:字节数组类型

如:

// 返回对象 T 类型
[Get("https://www.furion.pro/getdata", ResponseType = ResponseType.Object)]
Task<User> GetData();
// 返回字符串类型,也可以使用 ResponseType.Object
[Get("https://www.furion.pro/getdata", ResponseType = ResponseType.Text)]
Task<string> GetData();
// 返回流类型
[Get("https://www.furion.pro/getdata", ResponseType = ResponseType.Stream)]
Task<Stream> GetData();
// 返回字节数组类型
[Get("https://www.furion.pro/getdata", ResponseType = ResponseType.ByteArray)]
Task<byte[]> GetData();

19.5.4 请求/响应拦截#

Furion 框架重复利用了 C# 8.0+ 的特性,实现了接口中可定义静态方法和实现的机制,如:

public interface IHttp : IHttpDispatchProxy
{
[Get("https://www.furion.pro/getdata", ResponseType = ResponseType.Object)]
Task<User> GetData();
// 请求拦截器
static HttpRequestMessage RequestInterceptor(HttpRequestMessage httpRequest, MethodInfo method, object[] args)
{
// 比如这里写日志
return httpRequest;
}
// 响应拦截器
static HttpResponseMessage ResponseInterceptor(HttpResponseMessage httpResponse, MethodInfo method, object[] args)
{
// 比如这里写日志
return httpResponse;
}
}
字符串方式拦截

字符串方式只提供了请求拦截,不提供响应拦截

// 设置请求拦截
var data = await "http://47.100.247.61/api/sysdata/categories".PostAsAsync<object>(new { parm1="", parm2="" }, interceptor: request=>{
request.Headers.Add("Authorization","Bearer token字符串");
});

19.5.5 Body 参数序列化问题#

由于一些第三方接口不规范或对参数大小写敏感,这个时候我们可以配置特性的 PropertyNamingPolicy 属性,如:

[Post("https://www.furion.pro/getdata", PropertyNamingPolicy = JsonNamingPolicyOptions.Null)]
Task<User> GetData();
  • JsonNamingPolicyOptions 可选值:
    • CamelCase:默认,首字母小写属性名
    • Null:保持原有属性名称定义规则

19.5.6 Body 内容配置#

默认情况下,支持 Body 参数配置的请求都会序列化成 Json 内容配置,我们也可以通过 HttpContentType 属性指定,如:

[Post("https://www.furion.pro/getdata", HttpContentType = HttpContentTypeOptions.JsonStringContent)]
Task<User> GetData();
  • HttpContentTypeOptions 可选值:
    • StringContent:字符串内容
    • JsonStringContentJson 字符串内容
    • XmlStringContentXml 字符串内容
    • MultipartFormDataContentmultipart/form-data 类型内容
    • FormUrlEncodedContentx-www-form-urlencoded 类型内容

19.5.7 多个请求客户端配置#

Furion 框架也提供了多个请求客户端配置,可以为多个客户端请求配置默认请求信息,如:

services.AddRemoteRequest(options=>
{
// 配置 Github 基本信息
options.AddHttpClient("github", c =>
{
c.BaseAddress = new Uri("https://api.github.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.github.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
// 配置 Facebook 基本信息
options.AddHttpClient("facebook", c =>
{
c.BaseAddress = new Uri("https://api.facebook.com/");
c.DefaultRequestHeaders.Add("Accept", "application/vnd.facebook.v3+json");
c.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample");
});
})

配置了命名客户端后,每次请求都会自动加上这些配置。

代理请求 使用

[Get("api/getdata"), Client("github")]
Task<User> GetData();
[Put("api/getdata"), Client("facebook")]
Task<User> GetData();

字符串拓展 使用

// 设置请求拦截
var data = await "http://47.100.247.61/api/sysdata/categories".PostAsAsync<object>(new { parm1="", parm2="" }, clientName = "github");

19.6 代理内置特性#

19.6.1 接口特性#

  • 主机 特性
    • [Host]:配置主机地址和端口
  • 请求头 特性
    • [Header]:配置请求报文头,支持多个
  • 客户端 特性
    • [Client]:配置客户端

接口的特性会影响所有的成员方法,也就是会应用到每一个方法中,当然方法可可以重写或忽略。

19.6.2 方法特性#

  • 请求谓词 特性
    • [Get]Get 请求方式
    • [Post]Post 请求方式
    • [Put]Put 请求方式
    • [Delete]Delete 请求方式
    • [Patch]Patch 请求方式
    • [Head]Head 请求方式
  • 主机 特性
    • [Host]:配置主机地址和端口
  • 请求头 特性
    • [Header]:配置请求报文头,支持多个
  • 客户端 特性
    • [Client]:配置客户端
  • 内容类型 特性
    • [MediaTypeHeader]:配置内容类型

19.6.3 方法参数特性#

  • [Query]:自动将参数替换地址中的占位符,占位符格式 {参数名},如:https://www.furion.pro/user/{id},默认基元类型或基元类型数组应用该特性
  • [Body]:自动将参数添加到请求报文体中,默认非基元类型会引用该特性。

19.7 关于同步请求#

Furion 框架内部默认不提供同步请求操作,建议总是使用异步的方式请求。如在不能使用异步的情况下,可自行转换为同步执行。

19.8 异常处理#

默认情况下,如果接口请求异常会抛出请求异常,有时这不是我们需要的结果,我们希望如果接口请求异常,那么直接返回默认值即可,这时候我们只需要在接口或方法贴 [Safety] 特性即可。

19.9 反馈与建议#

与我们交流

给 Furion 提 Issue


了解更多

想了解更多 日志 知识可查阅 ASP.NET Core - HTTP 请求 章节