问题
I have an ASP.NET MVC 4 app where I have two areas:
- Backstage
- Signup
I have a class called DomainRoute
that makes it possible to route an entire subdomain to an area:
using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Web.Mvc;
using System.Web.Routing;
namespace Admin.Web.PresentationLogic
{
/// <summary>
/// DomainRoute is an extension of the default Route, that makes it possible to route domains and subdomains the specific controllers.
/// </summary>
public class DomainRoute : Route
{
private string _subDomain;
private string[] _namespaces;
/// <summary>
/// Initializes a new instance of the <see cref="DomainRoute"/> class.
/// </summary>
/// <param name="subDomain">The sub domain.</param>
/// <param name="url">The URL format.</param>
/// <param name="defaults">The defaults.</param>
public DomainRoute(string subDomain, string url, RouteValueDictionary defaults)
: base(url, defaults, new MvcRouteHandler())
{
this._subDomain = subDomain;
}
/// <summary>
/// Initializes a new instance of the <see cref="DomainRoute" /> class.
/// </summary>
/// <param name="subDomain">The sub domain.</param>
/// <param name="url">The URL format.</param>
/// <param name="defaults">The defaults.</param>
/// <param name="namespaces">The namespaces.</param>
public DomainRoute(string subDomain, string url, RouteValueDictionary defaults, string[] namespaces)
: base(url, defaults, new MvcRouteHandler())
{
this._subDomain = subDomain;
this._namespaces = namespaces;
}
/// <summary>
/// Returns information about the requested route.
/// </summary>
/// <param name="httpContext">An object that encapsulates information about the HTTP request.</param>
/// <returns>
/// An object that contains the values from the route definition.
/// </returns>
public override RouteData GetRouteData(System.Web.HttpContextBase httpContext)
{
// Request information
string requestDomain = httpContext.Request.Headers["HOST"];
if (!string.IsNullOrEmpty(requestDomain))
{
if (requestDomain.IndexOf(":") > 0)
{
requestDomain = requestDomain.Substring(0, requestDomain.IndexOf(":"));
}
}
else
{
requestDomain = httpContext.Request.Url.Host;
}
var index = requestDomain.IndexOf(".");
if (index < 0)
{
return RouteTable.Routes["Default"].GetRouteData(httpContext);
}
var subDomain = requestDomain.Substring(0, index);
if (!String.IsNullOrWhiteSpace(subDomain))
{
if (this._subDomain.Equals(subDomain, StringComparison.InvariantCultureIgnoreCase))
{
RouteData data = new RouteData(this, this.RouteHandler);
// Add defaults first
if (Defaults != null)
{
foreach (KeyValuePair<string, object> item in Defaults)
{
data.Values[item.Key] = item.Value;
}
}
var pathRegex = this.CreateRegex(Url);
var requestPath = httpContext.Request.AppRelativeCurrentExecutionFilePath.Substring(2) + httpContext.Request.PathInfo;
// Match domain and route
Match pathMatch = pathRegex.Match(requestPath);
// 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;
}
}
}
}
if (!data.Values.ContainsKey("action"))
{
data.Values.Add("action", "Index");
}
data.DataTokens["Namespaces"] = this._namespaces;
data.DataTokens["area"] = data.Values["area"] ?? this._subDomain;
return data;
}
}
return RouteTable.Routes["Default"].GetRouteData(httpContext);
}
/// <summary>
/// Creates the regex.
/// </summary>
/// <param name="source">The source.</param>
/// <returns>Returns the Regex for the source.</returns>
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 + "$");
}
}
}
When I register the areas, I do this:
context.Routes.Add("Signup_default",
new DomainRoute("signup", "{controller}/{action}/{id}",
new RouteValueDictionary(new { area = "Signup", controller = "Home", action = "Index", id = UrlParameter.Optional }),
new string[] { "Admin.Web.Areas.Signup.Controllers" }));
So, the problem has to do with the way the DomainRoute
's GetRouteData
method is executed.
Whenever I try to access signup.localhost
the instance of the DomainRoute
class is the one that I used when registering the Backstage area.
I tried disabling the Backstage area, and then the Signup area worked.
It uses the instance of DomainRoute
that occurs first in the RouteTable
.
What am I missing?
回答1:
I was missing the concept of the RouteCollection
.
After step debugging into the .Net source, I realized that in my DomainRoute
, if the sub-domain doesn't match, instead of returning default route data I should return null
.
That's the way ASP.NET Routing determines which one to use -- by calling GetRouteData
with the HttpContext
and let the specific route figure out if "I'm a match for the HttpContext
?"
来源:https://stackoverflow.com/questions/12942021/mvc-4-domainroute-to-area-uses-wrong-instance-from-routetable