I\'m trying to build a "Tenant" Subdomain route that attaches to a MVC Area. In this case I have an Area called "Tenant" which has two controllers; Publi
The post that you linked to has a bug: When a constraint or the URL does not match, the base.GetRouteData
method will return null
. In this case, adding the subdomain name to the route dictionary will obviously throw an exception. There should be a null guard clause before that line.
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
if (routeData != null)
{
routeData.Values.Add("client", httpContext.Request.Url.Host.Split('.').First());
}
return routeData;
}
As should be the case with your route. You need to ensure you return null in the case where the base class returns null (which indicates either the URL or a constraint did not match, and we need to skip processing this route).
Also, I am not sure if it makes any difference than adding the data directly to the DataTokens
, but the MVC framework has an IRouteWithArea
that can be implemented to configure the Area the route applies to.
public class SubdomainRouteP : Route, IRouteWithArea
{
public string Area { get; private set; }
public SubdomainRouteP(string area, string url, RouteValueDictionary defaults): this(area, url, defaults, new MvcRouteHandler())
{
}
public SubdomainRouteP(string area, string url, object defaults): this(area, url, new RouteValueDictionary(defaults), new MvcRouteHandler())
{
}
public SubdomainRouteP(string area, string url, object defaults, IRouteHandler routeHandler): this(area, url, new RouteValueDictionary(defaults), routeHandler)
{
}
public SubdomainRouteP(string area, string url, RouteValueDictionary defaults, IRouteHandler routeHandler): base(url, defaults, routeHandler)
{
this.Area = area;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
var routeData = base.GetRouteData(httpContext);
// This will ignore anything where the URL or a constraint doesn't match
// in the call to base.GetRouteData().
if (routeData != null)
{
var subdomain = httpContext.Request.Url.Host.Split('.').First();
string[] blacklist = { "www", "mydomain", "localhost" };
// This will ignore anything that is not a client tenant prefix
if (blacklist.Contains(subdomain))
{
return null; // Continue to the next route
}
routeData.DataTokens["UseNamespaceFallback"] = bool.FalseString;
routeData.Values.Add("subdomain", subdomain);
}
// IMPORTANT: Always return null if there is no match.
// This tells .NET routing to check the next route that is registered.
return routeData;
}
}
I can't figure out what you are trying to do with the domain
parameter. The URL will most likely return something for domain. So, it seems like you should have a constraint in the first "{controller}/{action}/{id}"
route or you will never have a case that will pass through to the default route. Or, you could use an explicit segment in the URL so you can differentiate it (the same way you did with your admin route).
routes.Add("Admin_Subdomain", new SubdomainRouteP(
"Tenant",
"admin/{action}/{id}",
new { controller = "Admin", action = "Index", id = UrlParameter.Optional }));
routes.Add("Public_Subdomain", new SubdomainRouteP(
"Tenant",
"public/{action}/{id}",
new { controller = "Public", action = "Index", id = UrlParameter.Optional }));
// This is the MVC default Route
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
Another option would be to add another constructor parameter to pass in an explicit list of valid domains to check against.