具有自己的一組控制器的多個 owin 偵聽器,以及用於 DI 的 Autofac
我正在嘗試使用多個程序內 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.\r\n\r\nThe request has found the following matching controller types: \r\nLib1.Controllers.ControllerA\r\nLib2.Controllers.ControllerB"在單獨的程序中執行 OWIN 偵聽器時,沒有問題。
我還嘗試使用多個 DI 容器,每個 OWIN 偵聽器一個,但這與 Web Api 2 衝突,因為它需要設置 GlobalConfiguration.Configuration.DependencyResolver。這與多個 DI 容器的概念相衝突。
有人可以指導我如何配置這樣的設置嗎?
使用
OWIN環境並自定義HttpControllerSelector使用
OWIN管道,您可以將有關請求的資訊傳遞給自定義HttpControllerSelector. 這使您可以選擇使用哪些控制器來匹配哪些路由。當然,這說起來容易做起來難。WebAPI 在路由方面的內部工作並不是很透明——原始碼通常是這方面最好的文件。
我無法
HttpControllerSelector完全工作,所以在CustomHttpActionSelector. 如果您需要做的只是將請求從一台主機轉發到另一台主機,這可能就足夠了。最終結果是:
GET返回http://localhost:1234/api/app/test“HellofromAController”(直接呼叫 AController)
GETtohttp://localhost:5678/api/app/testreturn “(FromBController): "HellofromAController"” (呼叫 BController,它將請求轉發給 AController)我保留了日誌程式碼,以防萬一它有用,但它與解決方案無關。
所以事不宜遲:
CustomHttpControllerSelector.cs:
使用特定於埠的 OWIN 環境變數
ApiControllersAssembly來過濾控制器。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,這僅用於解決 BController 的 ActionDescriptors 的問題。只要 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(); } } }程序.cs:
internal class Program { private static readonly ILog Logger; static Program() { Log.Logger = new LoggerConfiguration() .WriteTo .LiterateConsole() .MinimumLevel.Is(LogEventLevel.Verbose) .CreateLogger(); Logger = LogProvider.GetCurrentClassLogger(); } internal static void Main(string[] args) { var builder = new ContainerBuilder(); builder.RegisterModule(new LogRequestModule()); builder.RegisterApiControllers(typeof(AController).Assembly); builder.RegisterApiControllers(typeof(BController).Assembly); var container = builder.Build(); var config = GetHttpConfig(); config.DependencyResolver = new AutofacWebApiDependencyResolver(container); var options = new StartOptions(); options.Urls.Add("http://localhost:1234"); options.Urls.Add("http://localhost:5678"); var listener = WebApp.Start(options, app => { app.Use((ctx, next) => { if (ctx.Request.LocalPort.HasValue) { var port = ctx.Request.LocalPort.Value; string apiControllersAssemblyName = null; if (port == 1234) { apiControllersAssemblyName = typeof(AController).Assembly.FullName; } else if (port == 5678) { apiControllersAssemblyName = typeof(BController).Assembly.FullName; } ctx.Set("ApiControllersAssembly", apiControllersAssemblyName); Logger.Info($"{nameof(WebApp)}: Port = {port}, ApiControllersAssembly = {apiControllersAssemblyName}"); } return next(); }); app.UseAutofacMiddleware(container); app.UseAutofacWebApi(config); app.UseWebApi(config); }); Logger.Info(@"Press [Enter] to exit"); Console.ReadLine(); listener.Dispose(); ; } private static HttpConfiguration GetHttpConfig() { var config = new HttpConfiguration(); config.MapHttpAttributeRoutes(); config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Always; config.Services.Add(typeof(IExceptionLogger), new LogProviderExceptionLogger()); config.Formatters.Remove(config.Formatters.XmlFormatter); config.Services.Replace(typeof(IHttpControllerSelector), new CustomHttpControllerSelector(config)); config.Services.Replace(typeof(IHttpActionSelector), new CustomHttpActionSelector()); var traceSource = new TraceSource("LibLog") { Switch = { Level = SourceLevels.All } }; traceSource.Listeners.Add(new LibLogTraceListener()); var diag = config.EnableSystemDiagnosticsTracing(); diag.IsVerbose = false; diag.TraceSource = traceSource; return config; } }LibA\Controllers\AController.cs:
[RoutePrefix("api/app")] public class AController : ApiController { private static readonly ILog Logger; static AController() { Logger = LogProvider.GetCurrentClassLogger(); Logger.Debug($"{nameof(AController)}: Static Constructor"); } public AController() { Logger.Debug($"{nameof(AController)}: Constructor"); } [HttpGet, Route("test")] public async Task<IHttpActionResult> Get() { Logger.Debug($"{nameof(AController)}: Get()"); return Ok($"Hello from {nameof(AController)}"); } }LibB\Controllers\BController.cs:
[RoutePrefix("api/app")] public class BController : ApiController { private static readonly ILog Logger; static BController() { Logger = LogProvider.GetCurrentClassLogger(); Logger.Debug($"{nameof(BController)}: Static Constructor"); } public BController() { Logger.Debug($"{nameof(BController)}: Constructor"); } [HttpGet, Route("{*path}")] public async Task<IHttpActionResult> Get([FromUri] string path) { if (path == null) { path = Request.RequestUri.PathAndQuery.Split(new[] {"api/app/"}, StringSplitOptions.RemoveEmptyEntries)[1]; } Logger.Debug($"{nameof(BController)}: Get({path})"); using (var client = new HttpClient {BaseAddress = new Uri("http://localhost:1234/api/app/")}) { var result = await client.GetAsync(path); var content = await result.Content.ReadAsStringAsync(); return Ok($"(From {nameof(BController)}): {content}"); } } }當我有更多時間時,我可能會再嘗試一次。
如果你有任何進展,請告訴我!