You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从接口动态创建Mock对象并实现远程调用代理?

这个思路本质上就是做一个轻量级的远程代理网关,把本地接口调用转成HTTP请求发去服务端,正好能解决你要的“改一处全生效”的需求。我来帮你把代码补全,还有几个关键坑给你指出来:

一、客户端:用动态代理自动生成接口实现

你原来想手动Mock每个方法太麻烦,.NET自带的DispatchProxy可以帮你动态生成接口的代理类,所有方法调用都会统一走到一个处理逻辑里,完美实现“自动转发”的需求。

首先写一个代理类,继承DispatchProxy:

public class RemoteInterfaceProxy<T> : DispatchProxy
{
    private HttpClient _httpClient;

    protected override object Invoke(MethodInfo targetMethod, object[] args)
    {
        // 1. 打包请求需要的元数据:接口全名、方法名、参数信息
        var interfaceFullName = typeof(T).FullName;
        var methodName = targetMethod.Name;
        
        // 把参数转换成服务端能解析的结构(类型全名+JSON序列化值)
        var parameters = args?.Select(arg => new Parameter
        {
            TypeName = arg?.GetType().FullName,
            Json = JsonConvert.SerializeObject(arg)
        }).ToArray() ?? Array.Empty<Parameter>();

        // 2. 发送POST请求到服务端
        var response = _httpClient.PostAsJsonAsync(
            $"invoke/{interfaceFullName}/{methodName}", 
            parameters
        ).GetAwaiter().GetResult();

        // 3. 处理响应,转换成方法返回值
        response.EnsureSuccessStatusCode();
        if (targetMethod.ReturnType == typeof(void))
        {
            return null;
        }
        
        var responseContent = response.Content.ReadAsStringAsync().GetAwaiter().GetResult();
        return JsonConvert.DeserializeObject(responseContent, targetMethod.ReturnType);
    }

    // 注入HttpClient,用来发请求到服务端
    public void SetHttpClient(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }
}

// 和服务端对应的参数DTO,必须和服务端定义一致
public class Parameter
{
    public string TypeName { get; set; }
    public string Json { get; set; }
}

然后完善你的服务扩展类,把代理注册到DI容器:

public static class IServiceCollectionExtensions
{
    public static IServiceCollection AddMiddlewareClient<T>(this IServiceCollection services, string serviceBaseUrl)
    {
        // 注册HttpClient,指定服务端基础地址
        services.AddHttpClient<RemoteInterfaceProxy<T>>(client =>
        {
            client.BaseAddress = new Uri(serviceBaseUrl);
        });

        // 动态生成代理实例,作为接口T的实现注册到DI
        services.AddSingleton<T>(provider =>
        {
            var proxy = DispatchProxy.Create<T, RemoteInterfaceProxy<T>>() as RemoteInterfaceProxy<T>;
            var httpClient = provider.GetRequiredService<IHttpClientFactory>().CreateClient<RemoteInterfaceProxy<T>>();
            proxy.SetHttpClient(httpClient);
            return proxy as T;
        });

        return services;
    }
}

这样客户端只需要一行代码就能注册接口的远程代理,完全不用关心底层的HTTP请求逻辑。

二、服务端:完善调用逻辑,踩坑避坑

你的控制器已经有了雏形,但还有几个关键问题要解决:

1. 精准匹配方法,避免重载冲突

原来的InvokeMember只靠方法名找方法,容易匹配到重载方法,改成用参数类型精准查找:

// 替换原来的InvokeMember逻辑
var interfaceType = Type.GetType(typeName);
if (interfaceType == null)
{
    return BadRequest($"找不到类型:{typeName}");
}

var argTypes = args.Select(a => a.GetType()).ToArray();
var method = interfaceType.GetMethod(memberName, 
    BindingFlags.Public | BindingFlags.Instance,
    null,
    argTypes,
    null);

if (method == null)
{
    return BadRequest($"找不到方法 {memberName} 匹配给定参数类型");
}

2. 正确处理异步方法

如果接口有异步方法(比如Task Method1Async()),服务端需要解析Task的返回值:

// 改成异步控制器方法,避免阻塞
[HttpPost("invoke/{typeName}/{memberName}")]
public async Task<IActionResult> Invoke(string typeName, string memberName, [FromBody]Parameter[] parameters = null)
{
    // ... 前面的类型和参数解析逻辑 ...

    var impl = _services.GetRequiredService(interfaceType);
    
    // 处理同步/异步方法
    if (method.ReturnType.IsGenericType && method.ReturnType.GetGenericTypeDefinition() == typeof(Task<>))
    {
        var task = (Task)method.Invoke(impl, args);
        await task;
        var resultProperty = task.GetType().GetProperty("Result");
        return Ok(resultProperty.GetValue(task));
    }
    else if (method.ReturnType == typeof(Task))
    {
        var task = (Task)method.Invoke(impl, args);
        await task;
        return Ok();
    }
    else
    {
        var result = method.Invoke(impl, args);
        return Ok(result);
    }
}

3. 类型加载问题

Type.GetType(typeName)需要接口的完整程序集名称(比如"MyInterfaceNamespace.MyInterface, MyInterfaceAssembly"),所以客户端传递interfaceFullName的时候要确保带上程序集名,或者服务端提前引用接口类库,确保类型能被正确加载。

三、必注意的细节
  • 序列化一致性:客户端和服务端必须用同一个JSON序列化库(比如都用Newtonsoft.Json或者System.Text.Json),不然会出现反序列化失败的问题。
  • 异常处理:客户端要捕获HTTP异常,转换成接口方法的业务异常抛出;服务端要捕获方法调用的异常,包装成HTTP错误返回,让客户端能感知到服务端的问题。
  • 性能优化:可以缓存接口的MethodInfo,避免每次请求都反射查找,提升高并发场景下的性能。
  • 安全防护:给服务端的Invoke接口加认证授权(比如JWT),还要校验传入的typeName和memberName,防止恶意调用未授权的方法。
  • 异步适配:如果客户端需要支持异步方法,把代理的Invoke改成异步版本即可,DispatchProxy完全支持异步方法调用。
四、使用示例

客户端注册代理:

// 在Program.cs或者Startup.cs里
builder.Services.AddMiddlewareClient<MyInterface>("http://your-service-server-address/");

客户端正常通过DI使用接口,调用会自动转发到服务端:

public class ClientBusinessService
{
    private readonly MyInterface _myInterface;

    public ClientBusinessService(MyInterface myInterface)
    {
        _myInterface = myInterface;
    }

    public void Execute()
    {
        _myInterface.Method1(); // 自动发HTTP请求到服务端
        var result = _myInterface.Method2(456); // 接收服务端返回的结果
        var strResult = _myInterface.Method3("test");
    }
}

内容的提问来源于stack exchange,提问作者Ray Saltrelli

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:14:21