aspnetcore: как программно вызвать другой контроллер на основе URI? - PullRequest
0 голосов
/ 21 мая 2018

Альтернативное название, которое мог иметь этот вопрос:

Что такое ядро ​​Asp.NET, эквивалентное HttpControllerSelector?


У меня есть веб-сайт asp.net core 2.0, и я хочучтобы делегировать / прокси-запросы HTTP, что-то вроде этого:

  1. Клиент делает "упакованный" запрос, например GET server/api/batch?url1=api/thing1&url2=api/thing2
  2. Сервер разворачивает упакованный запрос - в этом примере у нас есть дваURL для api/thing1 и api/thing2
  3. Сервер внутренне вызывает базовые URL-адреса - в этом примере имитируют GET api/thing и GET api/thing2

В старом asp.netКлючом к этому в WebApi было создание нового внутреннего HttpRequest, а затем вызов Configuration.Services.GetHttpControllerSelector().SelectController(request), чтобы узнать, какой контроллер был настроен для этого URL.

В ядре Asp.Net я не могу найти, как это сделать?Я понимаю, что если я знаю контроллер заранее, я могу просто создать его экземпляр, но я не могу найти URL-адрес -> Маршрутизация -> Тип контроллера разрыв

1 Ответ

0 голосов
/ 13 октября 2018

Итак, во время постановки этого вопроса я покопался в исходном коде ASPNetCore 2, скомпилировал его сам и попытался найти способ его достижения.

Я пришел к выводу, что этобыло невозможно без изменений в структуре.Я не могу вспомнить точные причины, как это было несколько месяцев назад, но в основном нам нужно получить доступ к основному IRouteCollection, чтобы сопоставить строку маршрута с контроллером / действием, однако IRouteCollection находится только вМаршрутизация Middleware.Поскольку промежуточное ПО в ASPNetCore представляет собой просто цепочку лямбда-функций, одно промежуточное ПО не может опрашивать другие промежуточные программы или даже получать их набор.Все, что вы получите, - это список лямбда, у которого нет возможности покопаться в данных маршрутизации на другой стороне.

Итак, учитывая, что я не смог определить способ построения обобщенного решения для исходного вопроса(«пакетирование» нескольких запросов) Я разработал конкретное решение для конкретной проблемы, которая у меня была.В моем случае я пакетирую запросы вместе, но я пакетирую только определенное подмножество запросов (примерно до 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()
        };
    }
}
...