RTM and ASP.NET Web API Subdomains

I'm having trouble getting the Web API to work with subdomain-based routes. In short, I get to the correct controller and method, but the data token from the subdomain is not matched by WebAPI.

I have this in my scenario:

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

All permissions on the same ApiController, and I want to be able to extract the {tenant} token.

I used the code in this article http://blog.maartenballiauw.be/post/2012/06/18/Domain-based-routing-with-ASPNET-Web-API.aspx

But there is something that seems to have changed between the time this article was written and the beta release of ASP.NET Web Api. The code in the article relies on RouteTable.Routes , while the web API routes are configured through HttpConfiguration.Routes , which is an HttpRouteCollection , not a regular RouteCollection (it actually comes from a RouteCollection ).

So, I changed the code to get HttpRoute instead of Route . Here is the code:

https://gist.github.com/3766125

I configure the route as follows

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

And my requests go to the right controller. However, the tenant's data token is never populated (if I do this.Request.GetRouteData() , I see controller tokens and actions, but not tenants). If I set a breakpoint on GetRouteData , it is never called.

I tried to execute the code path with a reflector and see where GetRouteData is called at the HttpRouteCollection level, but it seems that the collection is an enumeration empty. Not sure exactly how regular ASP.NET routing and WEB API routing connect, but that confuses me.

Any ideas?

The workaround I'm using now is calling GetRouteData explicitly on Route

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

+4
source share
2 answers

After thinking more about this, I have a workaround for you. The main idea of โ€‹โ€‹a workaround is to use the route that flows from the route and directly add it to the RouteCollection. Thus, the route we are passing will no longer be wrapped inside HttpWebRoute.

The RouteByPassing handler should work around another known issue. Hope this helps.

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

}

+1
source

Thanks for reporting the problem. I used your reprogramming at https://github.com/woloski/AspNetWebApiWithSubdomains and did some debugging.

That is why this is happening. HttpDomainRoute.GetRouteData not called because it was wrapped by an inner class named HttpWebRoute in the Web API. When you use the config.Routes.Add method to add your custom route, instead of calling HttpDomainRoute.GetRouteData it simply calls the implementation of System.Web.Routing.Route's GetRouteData . This is why you see that the rest of the options are displayed correctly, except for the tenant.

I can't think of any easy workaround on my head. I can submit a question to the codeplex website at http://aspnetwebstack.codeplex.com/ to track this issue.

+2
source

Source: https://habr.com/ru/post/1435663/


All Articles