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

ASP.NET Core如何基于URI编程调用其他控制器?HttpControllerSelector等效实现是什么?

我明白你在ASP.NET Core 2.0里遇到的这个内部请求路由的问题——原来在旧WebApi里用Configuration.Services.GetHttpControllerSelector().SelectController(request)那套确实没法直接照搬了,毕竟Core的路由系统完全重构过。下面给你几个可行的方案,一步步解决这个批量请求代理的需求:

方案1:用HttpClient发起内部请求(简单易实现)

这个方法最直接,相当于让服务器自己当客户端调用内部API,不用纠结路由到控制器的细节,适合快速落地:

首先在Startup.cs的ConfigureServices里注册HttpClient:

services.AddHttpClient();

然后在你的批量处理控制器里注入IHttpClientFactory,构造请求并调用:

[Route("server/api")]
[ApiController]
public class BatchController : ControllerBase
{
    private readonly IHttpClientFactory _httpClientFactory;

    public BatchController(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    [HttpGet("batch")]
    public async Task<IActionResult> BatchGet([FromQuery] List<string> url)
    {
        var client = _httpClientFactory.CreateClient();
        // 拼接当前服务器的基础URL,避免硬编码
        var baseUrl = $"{Request.Scheme}://{Request.Host}";
        var results = new Dictionary<string, object>();

        foreach (var path in url)
        {
            var fullUrl = $"{baseUrl}/{path.TrimStart('/')}";
            var response = await client.GetAsync(fullUrl);
            
            if (response.IsSuccessStatusCode)
            {
                var content = await response.Content.ReadFromJsonAsync<object>();
                results[path] = content;
            }
            else
            {
                results[path] = new { StatusCode = (int)response.StatusCode, Error = response.ReasonPhrase };
            }
        }

        return Ok(results);
    }
}

优点:零路由解析成本,完全复用现有接口逻辑;缺点:会走网络栈,性能略差,但大部分场景下足够用。

方案2:用IActionInvoker直接调用内部Action(高性能无网络)

如果追求极致性能,不想走网络,可以直接在服务器内部找到对应的Action并执行,这需要利用Core的ActionDescriptor和IActionInvoker体系:

首先在Startup.cs里注册必要服务:

services.AddHttpContextAccessor(); // 用于获取当前HttpContext

然后在控制器里注入相关服务:

private readonly IActionDescriptorCollectionProvider _actionDescriptorProvider;
private readonly IActionInvokerFactory _actionInvokerFactory;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IRouter _router;

public BatchController(IActionDescriptorCollectionProvider actionDescriptorProvider,
                       IActionInvokerFactory actionInvokerFactory,
                       IHttpContextAccessor httpContextAccessor,
                       IRouter router)
{
    _actionDescriptorProvider = actionDescriptorProvider;
    _actionInvokerFactory = actionInvokerFactory;
    _httpContextAccessor = httpContextAccessor;
    _router = router;
}

第一步:准确匹配路由对应的ActionDescriptor

用IRouter来处理路由匹配(支持带参数的路由,比如api/thing/{id}):

private async Task<ActionDescriptor> MatchRouteAsync(string path, HttpMethod method)
{
    var originalContext = _httpContextAccessor.HttpContext;
    var newContext = new DefaultHttpContext(originalContext.RequestServices)
    {
        Request =
        {
            Method = method.Method,
            Path = new PathString($"/{path.TrimStart('/')}")
        }
    };

    var routeContext = new RouteContext(newContext);
    await _router.RouteAsync(routeContext);

    // 从匹配到的Endpoint里获取ActionDescriptor
    return routeContext.Handler is ActionEndpoint endpoint 
        ? endpoint.Metadata.GetMetadata<ActionDescriptor>() 
        : null;
}

第二步:创建模拟上下文并执行Action

private async Task<object> InvokeActionAsync(ActionDescriptor descriptor, string path, HttpMethod method)
{
    var originalContext = _httpContextAccessor.HttpContext;
    var newContext = new DefaultHttpContext(originalContext.RequestServices)
    {
        Request =
        {
            Method = method.Method,
            Path = new PathString($"/{path.TrimStart('/')}"),
            Scheme = originalContext.Request.Scheme,
            Host = originalContext.Request.Host
        }
    };

    var actionContext = new ActionContext(newContext, new RouteData(), descriptor);
    var invoker = _actionInvokerFactory.CreateInvoker(actionContext);

    object resultValue = null;
    int? statusCode = null;

    // 监听Action执行完成事件,获取返回结果
    invoker.ResultExecuted += (sender, args) =>
    {
        if (args.Result is ObjectResult objResult)
        {
            resultValue = objResult.Value;
            statusCode = objResult.StatusCode;
        }
        // 可以扩展处理其他类型的ActionResult,比如JsonResult、StatusCodeResult等
        return Task.CompletedTask;
    };

    await invoker.InvokeAsync();
    return statusCode.HasValue && statusCode >= 400 
        ? new { StatusCode = statusCode, Error = "Action execution failed" } 
        : resultValue;
}

第三步:批量处理请求

[HttpGet("batch")]
public async Task<IActionResult> BatchGet([FromQuery] List<string> url)
{
    var results = new Dictionary<string, object>();
    foreach (var path in url)
    {
        var descriptor = await MatchRouteAsync(path, HttpMethod.Get);
        if (descriptor == null)
        {
            results[path] = new { Error = "No matching action found for this path" };
            continue;
        }

        try
        {
            var content = await InvokeActionAsync(descriptor, path, HttpMethod.Get);
            results[path] = content;
        }
        catch (Exception ex)
        {
            results[path] = new { Error = ex.Message };
        }
    }

    return Ok(results);
}

优点:完全内部调用,无网络开销;缺点:需要处理路由匹配和上下文模拟,代码量稍大,但能精准控制请求流程。

总结
  • 如果你追求快速实现,选方案1即可;
  • 如果你需要高性能或者要自定义内部请求的细节,选方案2。

ASP.NET Core里确实没有直接对应旧WebApi的GetHttpControllerSelector的API,因为Core把路由和控制器的映射逻辑改成了基于ActionDescriptor和Endpoint的体系,上面的方案就是基于这套新体系的替代实现。

内容的提问来源于stack exchange,提问作者Orion Edwards

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:20:53