多个owin监听器,带有自己的控制器组,带有Autofac for DI

时间:2016-09-29 13:13:10

标签: asp.net-web-api owin asp.net-web-api-routing

我正在尝试使用多个进程内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容器的概念相冲突。

有人可以指导我如何配置这样的设置吗?

1 个答案:

答案 0 :(得分:3)

使用OWIN环境并自定义HttpControllerSelector

使用OWIN管道,您可以将有关请求的信息传递给自定义HttpControllerSelector。这使您可以选择使用哪些控制器来匹配哪些路径。

当然,这说起来容易做起来难。 WebAPI在路由方面的内部工作方式不是很透明 - source code通常是该领域的最佳文档。

我无法使HttpControllerSelector完全正常工作,因此CustomHttpActionSelector中有一个丑陋的解决方法。如果你需要做的就是将请求从一个主机转发到另一个主机,这可能仍然是足够的。

最终结果是:

GEThttp://localhost:1234/api/app/test返回“HellofromAController”(直接调用AController)

GEThttp://localhost:5678/api/app/test返回“(FromBController):\”HellofromAController \“”(调用BController,将请求转发给AController)

请参阅full source on github

我保留了日志记录代码,以防它有用,但它与解决方案无关。

所以不用多说:

<强> CustomHttpControllerSelector.cs

使用特定于端口的OWIN env变量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();
        }

    }
}

<强> Program.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 \控制器\ 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 \控制器\ 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}");
        }
    }
}

当我有更多时间的时候,我可能会再次参与其中。

如果你取得任何进展,请告诉我!