远程调用客户端(Aegis.Net.Broker)
当你需要通过“契约接口 + 特性”来描述远程服务,并把方法调用自动转成 HTTP 或 SOAP 请求时,可以使用 Aegis.Net.Broker。它适合跨服务调用、第三方接口封装,以及把零散 HTTP 请求收敛成稳定契约的场景。
组件概览
| 字段 | 说明 |
|---|---|
| 组件名称 | 远程调用客户端 |
| 真实类库 | Aegis.Net.Broker |
| 组件定位 | 通过契约接口生成远程调用代理,统一 HTTP / SOAP 请求方式 |
| 引入方式 | 直接安装 NuGet 并在代码中使用 BrokerClient |
| 核心能力 | 契约式 REST 调用、SOAP / WebService 调用、请求特性映射、过滤器、简易请求 |
| 是否可扩展 | 是 |
| 注册入口 | BrokerClient.Get<T>(...)、new BrokerClient(...) |
| 组件声明 | 无 |
这是一个纯客户端组件,不需要写入 Component.deps.json,也不需要额外注册到 DI 容器。为了减少不同协议混在一起带来的理解成本,当前文档把它拆成两条使用路线:HTTP / REST 调用,以及 WebService 调用。
如何引入
NuGet 包
| 角色 | NuGet 包 | 是否必需 | 说明 |
|---|---|---|---|
| 主组件 | Aegis.Net.Broker | 是 | 提供契约代理、HTTP / SOAP 调用和序列化能力 |
| 链路上下文 | Aegis.Context | 按需 | 如果你希望远程调用自动带上链路请求标识,可继续配合上下文组件使用 |
接入方式
Aegis.Net.Broker 不通过 Component.deps.json 引入,也没有中间件阶段。常见接法只有两类:
BrokerClient.Get<T>(baseUrl):最常用,适合标准契约调用new BrokerClient(baseUrl):适合需要自定义序列化、过滤器或请求行为的场景
配置说明
Broker 本身没有强制配置节点。最常见的做法是把远程服务地址放在业务配置里,再在业务代码中读取,例如:
{
"RemoteServices": {
"UserApi": "https://api.example.com"
}
}
这样做的好处是:
- 环境切换时只改配置,不改代码
- 契约接口可以长期复用
- 不同业务系统可以共享同一套调用约定
快速使用
第一步:定义契约接口
using Aegis.Net.Broker;
[BasePath("api/users")]
public interface IUserContract
{
[Get("list")]
Task<List<UserDto>> GetListAsync([Query] UserListRequest request);
[Post("create")]
Task<ApiResponse<UserDto>> CreateAsync([Body] CreateUserRequest request);
}
第二步:获取代理并发起调用
public class UserGateway
{
private readonly IUserContract _userContract;
public UserGateway()
{
_userContract = BrokerClient.Get<IUserContract>(
ConfigManager.Get("RemoteServices:UserApi"));
}
public Task<List<UserDto>> GetUsersAsync(UserListRequest request)
{
return _userContract.GetListAsync(request);
}
}
第三步:验证接入是否成功
当接入正确时,你通常会看到这些结果:
- 业务代码可以像调用本地接口一样调用远程服务
Task<T>返回值会自动反序列化[Query]、[Body]、[Path]等特性会按定义组装请求- 远程返回非成功状态时,调用方会得到异常或原始响应信息
使用路线
如果你只记一条判断规则,可以先看这个:
| 你手上的接口长什么样 | 优先路线 | 说明 |
|---|---|---|
对方给的是 URL、GET/POST/PUT/DELETE、Query、Header、JSON Body | HTTP / REST | 这是默认路线,也是新系统里最常见的对接方式 |
对方给的是 .svc、.asmx、ServiceContract、SOAP Action、SOAP 1.1 / 1.2 | WebService | 这类接口更依赖契约、命名空间和 SOAP 报文细节 |
路线一:HTTP / REST 调用
适合场景:
- 普通 Web API
- 第三方 HTTP 接口
- 内部服务之间的 JSON 调用
从这里继续看:
路线二:WebService 调用
适合场景:
- WCF
.svc - ASMX
.asmx - 需要 SOAP 1.1 / SOAP 1.2 兼容
从这里继续看:
该先看哪一页
如果你正在做新系统之间的普通接口联调,先看 HTTP请求组件。这页已经把契约层共享 Header、动态 Query、上传下载、状态码处理和更细的返回类型都展开了。
如果你面对的是院内遗留平台、厂商提供的 SOAP 接口或带 ServiceContract 的契约文件,先看 WebService调用。这页会更关注 WCF / ASMX 的契约建模、XmlSerializerFormat、MessageParameter、SOAP 1.2 和复杂类型映射。
具体使用详情
REST 与 SOAP 的使用边界
Broker 同时支持两类调用,但建议按两条独立路线理解:
- REST / 普通 HTTP 接口:最常见,也是默认使用方式
- SOAP / WebService 接口:适合
.svc、.asmx等遗留或平台接口
如果接口定义上带有 [ServiceContract],Broker 会按 SOAP 方式创建代理;否则默认按 REST 契约方式处理。
URL 组成规则
一次请求的目标地址通常由三部分组成:
BaseAddress:基础地址,通常通过BrokerClient.Get<T>(baseUrl)传入BasePath:接口级固定路径,例如[BasePath("api/users")]- 方法路径:方法上的
[Get("list")]、[Post("create")]等
推荐做法是:
- 基础地址放在配置里
- 领域路径放在接口上
- 具体动作路径放在方法上
这样接口职责更清晰,也更方便后期维护。
下面是一个最小 REST 契约示例:
using Aegis.Net.Broker;
[BasePath("api/users")]
public interface IUserContract
{
[Get("list")]
Task<List<UserDto>> GetListAsync([Query] UserListRequest request);
[Get("{id}")]
Task<UserDto> GetByIdAsync([Path] string id);
[Post("create")]
Task<ApiResponse<UserDto>> CreateAsync([Body] CreateUserRequest request);
[Put("{id}")]
Task<ApiResponse> UpdateAsync([Path] string id, [Body] UpdateUserRequest request);
[Delete("{id}")]
Task<ApiResponse> DeleteAsync([Path] string id);
}
public class UserGateway
{
private readonly IUserContract _contract;
public UserGateway()
{
_contract = BrokerClient.Get<IUserContract>(
ConfigManager.Get("RemoteServices:UserApi"));
}
public async Task<List<UserDto>> QueryAsync()
{
return await _contract.GetListAsync(new UserListRequest
{
PageIndex = 1,
PageSize = 10,
Keyword = "alice"
});
}
}
如果你要看更完整的 HTTP 调用写法,请继续看 HTTP请求组件。
HTTP 调用里最常见的能力
Broker 常用的参数特性主要有这些:
| 特性 | 作用 | 典型场景 |
|---|---|---|
[Body] | 把参数放入请求体 | JSON 提交、表单提交、上传流 |
[Query] | 作为 Query 参数发送 | 查询接口、分页条件 |
[Path] | 替换 URL 中的占位符 | /users/{id} |
[Header] | 设置固定或动态请求头 | Authorization、来源标识 |
需要特别注意的一点是:当 [Query] 作用在复杂对象上时,Broker 会默认把对象的一层公共属性展开为多个 Query 参数,而不是直接调用对象的 ToString()。
一个典型的参数绑定示例如下:
using Aegis.Net.Broker;
[BasePath("api/orders")]
public interface IOrderContract
{
[Get("{orderId}")]
Task<OrderDto> GetAsync(
[Path] string orderId,
[Header("Authorization")] string authorization);
[Post("search")]
Task<List<OrderDto>> SearchAsync([Body] OrderSearchRequest request);
[Post("submit-form")]
Task<ApiResponse> SubmitFormAsync(
[Body(BodySerializationMethod.UrlEncoded)] Dictionary<string, object> formData);
[Post("upload")]
Task<ApiResponse> UploadAsync(
[Header("Content-Disposition", "form-data; filename=\"demo.txt\"")]
[Header("Content-Type", "text/plain")]
[Body] Stream fileStream);
}
var contract = BrokerClient.Get<IOrderContract>(
ConfigManager.Get("RemoteServices:OrderApi"));
var order = await contract.GetAsync("A1001", "Bearer token-value");
var orders = await contract.SearchAsync(new OrderSearchRequest
{
Keyword = "drug",
Status = "Created"
});
await contract.SubmitFormAsync(new Dictionary<string, object>
{
["userName"] = "alice",
["password"] = "secret"
});
HTTP 返回类型
当前最常用的返回类型有:
| 返回类型 | 说明 |
|---|---|
Task | 只关心是否调用成功 |
Task<T> | 自动反序列化为目标类型 |
Task<string> | 获取原始字符串结果 |
Task<HttpResponseMessage> | 保留完整 HTTP 响应 |
Task<Response<T>> | 同时获取原始响应和延迟解析结果 |
Task<Stream> | 文件下载或流式返回 |
如果你需要既看响应头、状态码,又想按类型读取正文,Task<Response<T>> 通常比单纯 Task<T> 更合适。
[Get("detail/{id}")]
Task<Response<OrderDetailDto>> GetDetailAsync([Path] string id);
var response = await contract.GetDetailAsync("A1001");
if (!response.ResponseMessage.IsSuccessStatusCode)
{
throw new ApplicationException("远程接口返回失败");
}
var content = response.GetContent();
HTTP Header 处理
Broker 支持三类常见 Header 写法:
- 接口级固定 Header:整个契约统一附带
- 方法级固定 Header:只作用于某个方法
- 参数级动态 Header:调用时动态传入
其中动态 Header 最适合 Authorization、租户标识、业务 Trace 等每次请求可能变化的值。
[Header("X-System", "HIS")]
public interface IReportContract
{
[Get("daily")]
Task<ReportDto> GetDailyAsync(
[Header("Authorization")] string authorization,
[Query] string date);
}
var contract = BrokerClient.Get<IReportContract>(
ConfigManager.Get("RemoteServices:ReportApi"));
var report = await contract.GetDailyAsync(
"Bearer token-value",
"2026-03-18");
WebService 调用
如果你对接的是 WCF 或 ASMX 服务,可以直接定义带 [ServiceContract] 的契约接口,然后通过:
var service = BrokerClient.Get<ISampleService>("http://host/Service.svc");
进行调用。
这类场景下建议注意:
.svc和.asmx都可以通过同一组件调用- 契约方法应按服务端定义补齐
OperationContract等标注 - 如果目标服务要求 SOAP 1.2,可在接口上补充
SoapContract配置
WCF .svc 最小示例
using System.ServiceModel;
[ServiceContract]
public interface ISampleSoapService
{
[OperationContract]
string Ping(string message);
}
var service = BrokerClient.Get<ISampleSoapService>(
ConfigManager.Get("RemoteServices:SoapDemo"));
var result = service.Ping("Hello, SOAP Service!");
ASMX .asmx 最小示例
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Xml.Serialization;
[DataContract(Namespace = "http://tempuri.org/")]
public class ComplexInput
{
[DataMember]
public int IntValue { get; set; }
[DataMember]
public string StringValue { get; set; }
}
[DataContract(Namespace = "http://tempuri.org/")]
public class ComplexOutput
{
[DataMember]
public string ResultMessage { get; set; }
}
[ServiceContract]
[XmlSerializerFormat]
public interface IWebserviceContract
{
[OperationContract(Action = "http://tempuri.org/ProcessComplexType")]
[return: MessageParameter(Name = "ComplexOutput")]
ComplexOutput ProcessComplexType(
[MessageParameter(Name = "ComplexInput")] ComplexInput input);
}
var service = BrokerClient.Get<IWebserviceContract>(
ConfigManager.Get("RemoteServices:LegacyWebservice"));
var result = service.ProcessComplexType(new ComplexInput
{
IntValue = 42,
StringValue = "Test String from Client"
});
如果你要看更完整的 WebService 契约、复杂对象和 SOAP 版本处理,请继续看 WebService调用。
简易请求
如果你只是偶尔发一个临时请求,不想单独定义契约接口,也可以使用:
var response = await BrokerClient.EasyRequestAsync(
"https://api.example.com/api/users/query",
"Post",
"{\"keyword\":\"alice\"}",
new Dictionary<string, string>
{
["Authorization"] = "Bearer token"
});
这种方式适合:
- 一次性或临时性请求
- 排查第三方接口
- 还没来得及沉淀正式契约前的过渡调用
但只要调用开始稳定下来,仍然建议尽快收敛为契约接口。
var response = await BrokerClient.EasyRequestAsync(
ConfigManager.Get("RemoteServices:UserApi") + "/api/users/query",
"Post",
"{\"keyword\":\"alice\",\"pageIndex\":1,\"pageSize\":10}",
new Dictionary<string, string>
{
["Authorization"] = "Bearer token-value",
["X-System"] = "HIS"
});
var content = await response.Content.ReadAsStringAsync();
如何扩展
使用实例模式定制客户端行为
当你需要更多控制能力时,优先使用实例模式:
var broker = new BrokerClient("https://api.example.com")
{
Filters = new[] { new LoggingBrokerFilter() },
JsonSerializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore
}
};
var contract = broker.Get<IUserContract>();
这适合:
- 想统一控制 JSON 序列化规则
- 想给请求加日志、审计或额外拦截
- 想对特定远程系统使用单独配置
添加过滤器
如果你需要在请求前后插入处理逻辑,可以实现 IBrokerFilter。常见用途包括:
- 记录请求日志
- 统一补充请求头
- 采集远程接口耗时
- 对响应做统一检查
using Aegis.Net.Broker.Filters;
public class LoggingBrokerFilter : IBrokerFilter
{
public Task OnActionExecutingAsync(RequestContext context)
{
Console.WriteLine(
$"[Broker] {context.MethodType} {context.BaseAddress}{context.Path}");
return Task.CompletedTask;
}
public Task OnActionExecutedAsync(ResponseContext context)
{
Console.WriteLine($"[Broker] Status: {context.StatusCode}");
return Task.CompletedTask;
}
}
var broker = new BrokerClient(ConfigManager.Get("RemoteServices:UserApi"))
{
Filters = new[] { new LoggingBrokerFilter() }
};
var contract = broker.Get<IUserContract>();
自定义序列化行为
Broker 允许继续替换:
- 请求体序列化器
- Query 参数序列化器
- 响应反序列化器
- 路径参数序列化器
这类扩展通常只在你接第三方非标准接口、特殊编码格式或遗留系统时才需要。
常见问题指南
为什么这个组件不用写进 Component.deps.json?
因为它是纯客户端调用库,不是 Aegis 运行时中的服务注册型组件。正常使用时,直接安装 NuGet 并在代码里调用 BrokerClient 即可。
我应该优先用 BrokerClient.Get<T>(...) 还是 EasyRequestAsync(...)?
长期维护场景优先用 BrokerClient.Get<T>(...)。只有在临时请求、快速验证或尚未沉淀契约时,才建议使用 EasyRequestAsync(...)。
为什么 [Query] 传对象后,URL 里出现了一组展开参数?
这是 Broker 的默认行为。复杂对象会按一层公共属性展开为多个 Query 参数,便于对接常见 REST 查询接口。
SOAP 和 REST 能不能共用同一个组件?
可以。Aegis.Net.Broker 本身就同时覆盖这两类场景。区别只在于你的契约接口如何定义,以及目标服务本身使用的是 REST 还是 WebService。