问题
I am having issues getting Web API to work with subdomain-based routes. In short, I am getting to the right controller and method but the data token out of the subdomain is not being picked up by WebAPI.
I have this in my scenario:
contoso.myapp.com
fabrikam.myapp.com
{tenant}.myapp.com
All resolving to 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 seem to have changed between the time the article was written and ASP.NET Web Api went out of beta. The code in the article is relying on RouteTable.Routes while on Web API routes are configured through HttpConfiguration.Routes which is an HttpRouteCollection and not the usual RouteCollection (it derives from RouteCollection actually).
So I changed the code to derive from HttpRoute instead of Route. Here is the code:
https://gist.github.com/3766125
I configure a route like this
config.Routes.Add(new HttpDomainRoute(
name: "test",
domain: "{tenant}.myapp.com",
routeTemplate: "test",
defaults: new { controller = "SomeController", action = "Test" }
));
And my requests are routed to the right controller. However, the tenant data token is never filled (if I do this.Request.GetRouteData() I see the controller and action tokens but not tenant). If I put a breakpoint on GetRouteData it is never called.
I tried to follow the code path with reflector and see where GetRouteData is being called at the HttpRouteCollection level, but it seems that the collection is enumarating is empty. Not sure exactly how the integration between regular ASP.NET routing and WEb API routing is bridged but it is confusing me.
Any ideas?
The workaround I am using for now is calling explicitly GetRouteData over the Route
this.Request.GetRouteData().Route.GetRouteData(this.Request.RequestUri.ToString(), this.Request)
回答1:
After thinking of this more, I have a workaround for you. The basic idea of the workaround is to use a route that derives from Route and add it to the RouteCollection directly. That way, the route we are passing will no longer be wrapped inside HttpWebRoute.
The RouteByPassing handler is to workaround 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;
}
}
}
回答2:
Thanks for reporting the issue. I used your repro at https://github.com/woloski/AspNetWebApiWithSubdomains and did some debugging.
Here is why it is happening. The HttpDomainRoute.GetRouteData is not being called because it was wrapped by an internal class called HttpWebRoute in Web API. When you use config.Routes.Add method to add your custom route, instead of calling the HttpDomainRoute.GetRouteData, it will simply call the System.Web.Routing.Route's implementation of GetRouteData. That is why you see the rest of parameters being mapped correctly except the tenant.
I can't think of any easy workaround on top of my head. I can file a issue at the codeplex site at http://aspnetwebstack.codeplex.com/ to track this issue.
来源:https://stackoverflow.com/questions/12544150/asp-net-web-api-rtm-and-subdomain-routes