Несколько слушателей owin со своим собственным набором контроллеров, с Autofac для DI

Я пытаюсь использовать несколько слушателей owin в процессе. Каждый должен иметь отдельный набор контроллеров, где они могут иметь один и тот же маршрут, обрабатываемый другим контроллером. Например,

localhost:1234/api/app/test должно ControllerA

localhost:5678/api/app/test должно ControllerB

контроллер a, в Хосте owin 1, имеет атрибут маршрута

[Route("api/app/test")]

контроллер b, в Хосте owin 2, имеет атрибут маршруту

[Route("api/app/{*path}")]

и используется для пересылки запросов на другой хост owin.

мы используем Autofac для инъекции зависимостей. Маршруты настраиваются через маршрутизацию атрибутов. autofac требует строку, такую как

builder.RegisterApiControllers(typeof(ControllerA).Assembly)

наша конфигурация OWIN содержит:

var config = ConfigureWebApi(); // Configure Autofac config.DependencyResolver = new AutofacWebApiDependencyResolver(container); app.UseAutofacMiddleware(container); app.UseAutofacWebApi(config); app.UseWebApi(config);

однако при запуске двух слушателей мне нужно включить оба сборки для разрешения контроллера. Это приводит к исключению "дублировать маршрут":

Multiple controller types were found that match the URL. This can happen if attribute routes on multiple controllers match the requested URL.rnrnThe request has found the following matching controller types: rnLib1.Controllers.ControllerArnLib2.Controllers.ControllerB"

при запуске прослушивателей OWIN в отдельных процессах проблем нет.

Я также пытался использовать несколько контейнеров DI, по одному для каждого прослушивателя OWIN, но это конфликтует с Web Api 2, поскольку для этого требуется GlobalConfiguration.Конфигурация.DependencyResolver должен быть установлен. Что противоречит концепции множественного DI стеклотара.

может ли кто-нибудь помочь мне настроить такую настройку?

1 ответов


использовать OWIN окружающая среда и настройки HttpControllerSelector

С помощью вы можете передать информацию о запросе на заказ HttpControllerSelector. Это позволяет вам выбирать, какие контроллеры используются для соответствия каким маршрутам.

конечно, это легче сказать, чем сделать. Внутренняя работа WebAPI в отношении маршрутизации не очень прозрачна -исходный код часто является лучшей документацией в этом область.

Я не мог получить HttpControllerSelector для полной работы, так что есть уродливый обходной путь в CustomHttpActionSelector. Это все еще может быть достаточно, если все, что вам нужно сделать, это пересылать запросы от одного хоста к другому.

в итоге:

GET to http://localhost:1234/api/app/test возвращает " HellofromAController "(непосредственно вызывает AController)

GET to http://localhost:5678/api/app/test возвращает "(FromBController): \ " HellofromAController\ "" (вызывает BController, который пересылает запрос в AController)

посмотреть полный источник на github

Я оставил код ведения журнала как есть, если это полезно, но это не имеет отношения к решению.

так что без лишних слов:

CustomHttpControllerSelector.cs:

использует специфичную для порта переменную OWIN env ApiControllersAssembly in для фильтрации контроллеры.

public sealed class CustomHttpControllerSelector : DefaultHttpControllerSelector
{
    private static readonly ILog Logger;

    static CustomHttpControllerSelector()
    {
        Logger = LogProvider.GetCurrentClassLogger();
    }

    public CustomHttpControllerSelector(HttpConfiguration configuration) : base(configuration)
    {
    }

    public override HttpControllerDescriptor SelectController(HttpRequestMessage request)
    {
        var apiControllerAssembly = request.GetOwinEnvironment()["ApiControllersAssembly"].ToString();
        Logger.Debug($"{nameof(CustomHttpControllerSelector)}: {{{nameof(apiControllerAssembly)}: {apiControllerAssembly}}}");

        var routeData = request.GetRouteData();
        var routeCollectionRoute = routeData.Route as IReadOnlyCollection<IHttpRoute>;
        var newRoutes = new List<IHttpRoute>();
        var newRouteCollectionRoute = new RouteCollectionRoute();
        foreach (var route in routeCollectionRoute)
        {
            var filteredDataTokens = FilterDataTokens(route, apiControllerAssembly);
            if (filteredDataTokens.Count == 2)
            {
                var newRoute = new HttpRoute(route.RouteTemplate, (HttpRouteValueDictionary)route.Defaults, (HttpRouteValueDictionary)route.Constraints, filteredDataTokens);
                newRoutes.Add(newRoute);
            }
        }

        var newRouteDataValues = new HttpRouteValueDictionary();
        foreach (var routeDataKvp in routeData.Values)
        {
            var newRouteDataCollection = new List<IHttpRouteData>();
            var routeDataCollection = routeDataKvp.Value as IEnumerable<IHttpRouteData>;
            if (routeDataCollection != null)
            {
                foreach (var innerRouteData in routeDataCollection)
                {
                    var filteredDataTokens = FilterDataTokens(innerRouteData.Route, apiControllerAssembly);
                    if (filteredDataTokens.Count == 2)
                    {
                        var newInnerRoute = new HttpRoute(innerRouteData.Route.RouteTemplate, (HttpRouteValueDictionary)innerRouteData.Route.Defaults, (HttpRouteValueDictionary)innerRouteData.Route.Constraints, filteredDataTokens);
                        var newInnerRouteData = new HttpRouteData(newInnerRoute, (HttpRouteValueDictionary)innerRouteData.Values);
                        newRouteDataCollection.Add(newInnerRouteData);
                    }
                }
                newRouteDataValues.Add(routeDataKvp.Key, newRouteDataCollection);
            }
            else
            {
                newRouteDataValues.Add(routeDataKvp.Key, routeDataKvp.Value);
            }

            HttpRouteData newRouteData;
            if (newRoutes.Count > 1)
            {
                newRouteCollectionRoute.EnsureInitialized(() => newRoutes);
                newRouteData = new HttpRouteData(newRouteCollectionRoute, newRouteDataValues);
            }
            else
            {
                newRouteData = new HttpRouteData(newRoutes[0], newRouteDataValues);
            }
            request.SetRouteData(newRouteData);
        }


        var controllerDescriptor = base.SelectController(request);
        return controllerDescriptor;
    }

    private static HttpRouteValueDictionary FilterDataTokens(IHttpRoute route, string apiControllerAssembly)
    {
        var newDataTokens = new HttpRouteValueDictionary();
        foreach (var dataToken in route.DataTokens)
        {
            var actionDescriptors = dataToken.Value as IEnumerable<HttpActionDescriptor>;
            if (actionDescriptors != null)
            {
                var newActionDescriptors = new List<HttpActionDescriptor>();
                foreach (var actionDescriptor in actionDescriptors)
                {
                    if (actionDescriptor.ControllerDescriptor.ControllerType.Assembly.FullName == apiControllerAssembly)
                    {
                        newActionDescriptors.Add(actionDescriptor);
                    }
                }
                if (newActionDescriptors.Count > 0)
                {
                    newDataTokens.Add(dataToken.Key, newActionDescriptors.ToArray());
                }
            }
            else
            {
                newDataTokens.Add(dataToken.Key, dataToken.Value);
            }
        }
        return newDataTokens;
    }
}

CustomHttpActionSelector.cs:

вам не нужен CustomHttpActionSelector, это существует только для решения проблемы с ActionDescriptors для BController. Он работает до тех пор, пока BController имеет только один метод, иначе вам нужно будет реализовать определенный маршрут логика.

public sealed class CustomHttpActionSelector : ApiControllerActionSelector
{
    private static readonly ILog Logger;

    static CustomHttpActionSelector()
    {
        Logger = LogProvider.GetCurrentClassLogger();
    }

    public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
    {
        try
        {
            var actionDescriptor = base.SelectAction(controllerContext);
            return actionDescriptor;
        }
        catch (Exception ex)
        {
            Logger.WarnException(ex.Message, ex);

            IDictionary<string, object> dataTokens;
            var route = controllerContext.Request.GetRouteData().Route;
            var routeCollectionRoute = route as IReadOnlyCollection<IHttpRoute>;
            if (routeCollectionRoute != null)
            {
                dataTokens = routeCollectionRoute
                    .Select(r => r.DataTokens)
                    .SelectMany(dt => dt)
                    .ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
            }
            else
            {
                dataTokens = route.DataTokens;
            }

            var actionDescriptors = dataTokens
                .Select(dt => dt.Value)
                .Where(dt => dt is IEnumerable<HttpActionDescriptor>)
                .Cast<IEnumerable<HttpActionDescriptor>>()
                .SelectMany(r => r)
                .ToList();

            return actionDescriptors.FirstOrDefault();
        }

    }
}