2012-09-22 87 views
4

我遇到问题让Web API与基于子域的路由一起工作。简而言之,我已经找到了正确的控制器和方法,但子域中的数据标记没有被WebAPI拾取。ASP.NET Web API RTM和子域路由

我有这个在我的情况:

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上放置一个断点,它永远不会被调用。

我试着用反射器跟着代码路径,看看GetRouteData在HttpRouteCollection级别被调用的地方,但是看起来这个集合是空的。不确定如何桥接常规ASP.NET路由和WEb API路由之间的集成,但这让我感到困惑。

任何想法?

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

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

回答

1

想过这个之后,我为你解决了一个问题。解决方法的基本思路是使用从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; 
    } 
} 

}

+0

谢谢!只是好奇,RouteByPassingHandler的目的是什么? – woloski

+0

'RouteByPassingHandler'似乎在自主主机场景中破坏路由(我使用自托管来测试我的控制器)。有关如何解决问题而不添加对自主主机程序集的引用并在添加处理程序之前检查'HttpSelfHostConfiguration'的任何想法? – georgiosd

2

由于报告了该问题。我在https://github.com/woloski/AspNetWebApiWithSubdomains上使用了你的repro并做了一些调试。

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

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

+0

感谢。是的,我遵循了这条执行路线。我将提交错误。 – woloski