Asp.net-Web-Api

具有自己的一組控制器的多個 owin 偵聽器,以及用於 DI 的 Autofac

  • January 28, 2022

我正在嘗試使用多個程序內 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)

GETto http://localhost:5678/api/app/testreturn “(FromBController): "HellofromAController"” (呼叫 BController,它將請求轉發給 AController)

查看github 上的完整原始碼

我保留了日誌程式碼,以防萬一它有用,但它與解決方案無關。

所以事不宜遲:

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}");
       }
   }
}

當我有更多時間時,我可能會再嘗試一次。

如果你有任何進展,請告訴我!

引用自:https://stackoverflow.com/questions/39771327