跳到主要内容
版本:3.0.0

远程调用客户端(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 BodyHTTP / REST这是默认路线,也是新系统里最常见的对接方式
对方给的是 .svc.asmxServiceContract、SOAP Action、SOAP 1.1 / 1.2WebService这类接口更依赖契约、命名空间和 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 的契约建模、XmlSerializerFormatMessageParameter、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。