这个问题的备选标题可能有:
我有一个asp.net核心2.0网站,我想委托/代理http请求,如下所示:
GET server/api/batch?url1=api/thing1&url2=api/thing2
api/thing1
和api/thing2
api/thing
并获取api/thing2
在旧的asp.net WebApi中,关键是要新建一个内部HttpRequest,然后调用Configuration.Services.GetHttpControllerSelector().SelectController(request)
来获取针对该URL配置的控制器。
在Asp.Net核心中,我找不到怎么做?我意识到如果我事先知道控制器我可以创建它的实例,但我似乎无法通过URL - >路由 - >控制器类型间隙
答案 0 :(得分:1)
因此,在问这个问题时,我翻阅了ASPNetCore 2源代码,自己进行了编译,并试图找出实现它的方法。
我得出的结论是,不更改框架就不可能做到这一点。我不记得几个月前的确切原因,但是基本上我们需要访问主IRouteCollection
才能将路由字符串映射到控制器/动作,但是IRouteCollection仅在路由中间件中。因为ASPNetCore中的中间件只是一串lambda函数,所以一个中间件无法查询其他中间件,甚至无法获得它们的集合。您所获得的只是Lambda的列表,而无法深入挖掘另一侧的Routing数据。
因此,考虑到我无法确定构建通用解决方案的方法,对于原始问题(“批处理”多个请求),我为遇到的特定问题构建了特定解决方案。在我的实例中,我将请求一起批处理,但是我只批处理了特定的请求子集(约6个控制器/操作方法)。
有一整堆的辅助代码,但总的想法是这段摘录:
if(httpContext.Request.Path.TryMatchRoute("api/{itemType}/{id}/updates/{bookmark?}", out routeValues) &&
routeValues.TryGetString("itemType", out string itemType) &&
routeValues.TryGetInt("id", out int id))
{
routeValues.TryGetString("bookmark", out string bookmark);
queryParams.TryGetString("fields", out var fields);
switch (itemType) {
case "animals":
return InvokeController<AnimalsController>(async controller =>
await controller.GetUpdates(id, bookmark, fields).ConfigureAwait(false));
case "vegetables":
return InvokeController<VegetablesController>(async controller =>
await controller.GetUpdates(id, bookmark, fields).ConfigureAwait(false));
...
注意:TryMatchRoute
是我拥有的一个辅助函数,它使用TemplateSelector
对路由字符串进行模式匹配。 InvokeController
也是我所拥有的功能,它封装了实例化控制器并使用HttpContext
设置IHttpContextFactory
的样板,以及正确调用控制器所需的所有功能。
我想这样做是因为我对所有将被批处理的GetUpdates
方法有特定的了解,并且希望以特定的方式合并它们的结果。
我确实有另一个实例,而不是一起“分批”处理请求,而是需要“隧道化”它们,并且不需要特定的结果对象知识(尽管这样做很方便)。通过有效地站起ASPNetCore服务器堆栈的整个内部副本,但用伪造的层替换了Kestrel,我实现了这一点。这有点恶心,但也很整洁。如上所述,这不是完整的代码,但是您可以理解:
void Main()
{
m_server = new FakeServer();
var builder = Microsoft.AspNetCore.WebHost.CreateDefaultBuilder()
.UseStartup<InternalServerStartup>()
.UseSetting(WebHostDefaults.SuppressStatusMessagesKey, "True")
.ConfigureServices(services => {
services.AddSingleton<IServer>(m_server); // this causes the WebHostBuilder to fire requests at our fake server
});
var host = builder.Build();
host.Start();
}
// This class solely exists for us capture the instance of HostingApplication that MVC creates internally so we can invoke it directly
protected class FakeServer : IServer
{
public HostingApplication HostingApplication { get; private set; } = null;
public IFeatureCollection Features => new FeatureCollection();
public void Dispose()
{ }
public Task StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
{
if(application is HostingApplication standardContextApp) {
HostingApplication = standardContextApp;
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
TunneledResponse OnTunneledRequest(string url, string method, string[] headers, byte[] body)
{
var host = m_server.HostingApplication;
var responseBodyStream = new MemoryStream();
var features = new FeatureCollection();
features.Set<IHttpRequestFeature>(new HttpRequestFeature());
features.Set<IHttpResponseFeature>(new HttpResponseFeature { Body = responseBodyStream });
var ctx = host.CreateContext(features);
var httpContext = ctx.HttpContext;
var targetUri = new Uri(url);
httpContext.Request.Scheme = "fakescheme";
httpContext.Request.Host = new HostString("fakehost"); // if host is missing, things crash down the line: https://github.com/aspnet/Home/issues/2718
httpContext.Request.Path = targetUri.AbsolutePath;
httpContext.Request.Method = method;
httpContext.Request.QueryString = new QueryString(targetUri.Query);
for (var i = 0; i < headers.Length / 2; i++)
httpContext.Request.Headers.Add(headers[i * 2], headers[i * 2 + 1]);
try {
await host.ProcessRequestAsync(ctx);
responseBodyStream.Position = 0; // aspnet will have written to it, seek back to the start
return new TunneledResponse {
statusCode = httpContext.Response.StatusCode,
headers = httpContext.Response.Headers.SelectMany(h => new[] { h.Key, h.Value.FirstOrDefault() }).ToArray(),
body = responseBodyStream.ToArray()
};
}
}