【问题标题】:ASP.NET Web API RTM and subdomain routesASP.NET Web API RTM 和子域路由
【发布时间】:2012-09-22 13:22:44
【问题描述】:

我在让 Web API 处理基于子域的路由时遇到问题。简而言之,我找到了正确的控制器和方法,但是 WebAPI 没有提取子域中的数据令牌。

我的场景中有这个:

contoso.myapp.com
fabrikam.myapp.com
{tenant}.myapp.com

全部解析到同一个 ApiController,我希望能够提取 {tenant} 令牌。

我使用了本文中的代码http://blog.maartenballiauw.be/post/2012/06/18/Domain-based-routing-with-ASPNET-Web-API.aspx

但是在撰写本文和 ASP.NET Web Api 退出测试版之间似乎发生了一些变化。文章中的代码依赖于RouteTable.Routes,而Web API 路由是通过HttpConfiguration.Routes 配置的,HttpRouteCollection 而不是通常的RouteCollection(它实际上是从RouteCollection 派生的)。

所以我将代码更改为从HttpRoute 派生而不是Route。代码如下:

https://gist.github.com/3766125

我这样配置路由

 config.Routes.Add(new HttpDomainRoute(
            name: "test",
            domain: "{tenant}.myapp.com",
            routeTemplate: "test",
            defaults: new { controller = "SomeController", action = "Test" }
        ));

我的请求被路由到正确的控制器。但是,租户数据令牌永远不会填充(如果我这样做 this.Request.GetRouteData() 我会看到控制器和操作令牌,但看不到租户)。如果我在GetRouteData 上设置断点,它永远不会被调用。

我尝试使用反射器跟踪代码路径并查看在 HttpRouteCollection 级别调用 GetRouteData 的位置,但似乎枚举的集合为空。不确定常规 ASP.NET 路由和 WEB API 路由之间的集成究竟是如何桥接的,但这让我感到困惑。

有什么想法吗?

我现在使用的解决方法是通过 Route 显式调用 GetRouteData

this.Request.GetRouteData().Route.GetRouteData(this.Request.RequestUri.ToString(), this.Request)

【问题讨论】:

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


    【解决方案1】:

    感谢您报告问题。我在https://github.com/woloski/AspNetWebApiWithSubdomains 使用了您的repro 并进行了一些调试。

    这就是它发生的原因。 HttpDomainRoute.GetRouteData 未被调用,因为它被 Web API 中名为 HttpWebRoute 的内部类包装。当您使用config.Routes.Add 方法添加您的自定义路由时,它不会调用HttpDomainRoute.GetRouteData,而是简单地调用GetRouteDataSystem.Web.Routing.Route's 实现。这就是为什么您会看到除租户之外的其余参数被正确映射的原因。

    我想不出任何简单的解决方法。我可以在http://aspnetwebstack.codeplex.com/ 的 codeplex 站点提交问题以跟踪此问题。

    【讨论】:

    • 谢谢。是的,我遵循了那条执行路径。我会提交错误。
    【解决方案2】:

    在考虑了更多之后,我有一个解决方法给你。解决方法的基本思想是使用从 Route 派生的路由并将其直接添加到 RouteCollection。这样,我们通过的路由将不再被包裹在 HttpWebRoute 中。

    RouteByPassing 处理程序用于解决另一个已知问题。希望这会有所帮助。

    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
    
            RouteTable.Routes.Add("test", new HttpDomainRoute(
                domain: "{tenant}.auth10.com",
                routeTemplate: "test",
                defaults: new { controller = "Values", action = "GetTenant" }
            ));
    
            config.MessageHandlers.Add(new RouteByPassingHandler());
        }
    }
    
    public class RouteByPassingHandler : DelegatingHandler
    {
        protected override System.Threading.Tasks.Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
        {
            HttpMessageInvoker invoker = new HttpMessageInvoker(new HttpControllerDispatcher(request.GetConfiguration()));
            return invoker.SendAsync(request, cancellationToken);
        }
    }
    
    public class HttpDomainRoute
        : Route
    {
        private Regex domainRegex;
        private Regex pathRegex;
    
        public HttpDomainRoute(string domain, string routeTemplate, object defaults, object constraints = null)
            : base(routeTemplate, new RouteValueDictionary(defaults), new RouteValueDictionary(constraints), new RouteValueDictionary(), HttpControllerRouteHandler.Instance)
        {
            this.Domain = domain;
        }
    
        public string Domain { get; set; }
    
        public override RouteData GetRouteData(HttpContextBase context) 
        {   
            // Build regex
            domainRegex = CreateRegex(this.Domain);
            pathRegex = CreateRegex(this.Url);
    
            // Request information
            string requestDomain = context.Request.Headers["Host"];
            if (!string.IsNullOrEmpty(requestDomain))
            {
                if (requestDomain.IndexOf(":") > 0)
                {
                    requestDomain = requestDomain.Substring(0, requestDomain.IndexOf(":"));
                }
            }
            else
            {
                requestDomain = context.Request.Url.Host;
            }
    
            string requestPath = context.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + context.Request.PathInfo;
    
            // Match domain and route
            Match domainMatch = domainRegex.Match(requestDomain);
            Match pathMatch = pathRegex.Match(requestPath);
    
            // Route data
            RouteData data = null;
            if (domainMatch.Success && pathMatch.Success)
            {
                data = base.GetRouteData(context);
    
                // Add defaults first
                if (Defaults != null)
                {
                    foreach (KeyValuePair<string, object> item in Defaults)
                    {
                        data.Values[item.Key] = item.Value;
                    }
                }
    
                // Iterate matching domain groups
                for (int i = 1; i < domainMatch.Groups.Count; i++)
                {
                    Group group = domainMatch.Groups[i];
                    if (group.Success)
                    {
                        string key = domainRegex.GroupNameFromNumber(i);
    
                        if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0))
                        {
                            if (!string.IsNullOrEmpty(group.Value))
                            {
                                data.Values[key] = group.Value;
                            }
                        }
                    }
                }
    
                // Iterate matching path groups
                for (int i = 1; i < pathMatch.Groups.Count; i++)
                {
                    Group group = pathMatch.Groups[i];
                    if (group.Success)
                    {
                        string key = pathRegex.GroupNameFromNumber(i);
    
                        if (!string.IsNullOrEmpty(key) && !char.IsNumber(key, 0))
                        {
                            if (!string.IsNullOrEmpty(group.Value))
                            {
                                data.Values[key] = group.Value;
                            }
                        }
                    }
                }
            }
    
            return data;
        }
    
        public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) 
        {
            return base.GetVirtualPath(requestContext, RemoveDomainTokens(values));
        }
    
        private Regex CreateRegex(string source)
        {
            // Perform replacements
            source = source.Replace("/", @"\/?");
            source = source.Replace(".", @"\.?");
            source = source.Replace("-", @"\-?");
            source = source.Replace("{", @"(?<");
            source = source.Replace("}", @">([a-zA-Z0-9_-]*))");
    
            return new Regex("^" + source + "$");
        }
    
        private RouteValueDictionary RemoveDomainTokens(RouteValueDictionary values)
        {
            Regex tokenRegex = new Regex(@"({[a-zA-Z0-9_-]*})*-?\.?\/?({[a-zA-Z0-9_-]*})*-?\.?\/?({[a-zA-Z0-9_-]*})*-?\.?\/?({[a-zA-Z0-9_-]*})*-?\.?\/?({[a-zA-Z0-9_-]*})*-?\.?\/?({[a-zA-Z0-9_-]*})*-?\.?\/?({[a-zA-Z0-9_-]*})*-?\.?\/?({[a-zA-Z0-9_-]*})*-?\.?\/?({[a-zA-Z0-9_-]*})*-?\.?\/?({[a-zA-Z0-9_-]*})*-?\.?\/?({[a-zA-Z0-9_-]*})*-?\.?\/?({[a-zA-Z0-9_-]*})*-?\.?\/?");
            Match tokenMatch = tokenRegex.Match(Domain);
            for (int i = 0; i < tokenMatch.Groups.Count; i++)
            {
                Group group = tokenMatch.Groups[i];
                if (group.Success)
                {
                    string key = group.Value.Replace("{", "").Replace("}", "");
                    if (values.ContainsKey(key))
                        values.Remove(key);
                }
            }
    
            return values;
        }
    }
    

    }

    【讨论】:

    • 谢谢!只是好奇,RouteByPassingHandler 的目的是什么?
    • RouteByPassingHandler 似乎在自托管场景中破坏了路由(我使用自托管来测试我的控制器)。关于如何在不添加对自宿主程序集的引用并在添加处理程序之前检查 HttpSelfHostConfiguration 的情况下解决问题的任何想法?
    猜你喜欢
    • 2013-07-10
    • 1970-01-01
    • 1970-01-01
    • 2013-02-20
    • 2013-10-17
    • 2018-08-28
    • 1970-01-01
    • 2016-12-01
    • 2011-03-17
    相关资源
    最近更新 更多