Несколько слушателей 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();
}
}
}