我最终通过修改 Phil Haack 的 AreaRouteHelper 扩展解决了这个问题:
public static class AreaRouteHelper
{
public static void MapAreas(this RouteCollection routes, string url, string rootNamespace, string[] areas)
{
Array.ForEach(areas, area =>
{
Route route = new Route("{area}/" + url, new MvcRouteHandler());
route.Constraints = new RouteValueDictionary(new { area });
string areaNamespace = rootNamespace + ".Areas." + area + ".Controllers";
route.DataTokens = new RouteValueDictionary(new { namespaces = new string[] { areaNamespace } });
route.Defaults = new RouteValueDictionary(new { action = "Index", controller = "Landing", id = "" });
routes.Add(route);
});
}
public static void MapRootArea(this RouteCollection routes, string url, string rootNamespace, object defaults)
{
Route route = new Route(url, new MvcRouteHandler());
route.DataTokens = new RouteValueDictionary(new { namespaces = new string[] { rootNamespace + ".Controllers" } });
route.Defaults = new RouteValueDictionary(new { area = "", action = "Index", controller = "Lobby", id = "" });
routes.Add(route);
}
}
(基本上我必须从默认的 RootArea 默认值中删除“root”一词,并将默认值更改为“Lobby”和“Landing”,这是我们的标准而不是 Home。当我将他的默认“root”留在那里时,它添加了“ root”到不起作用的文件夹结构)
然后我从我的 RegisterRoutes 中删除了后两条路由并替换为:
routes.MapAreas("{controller}/{action}/{id}", "WebUI", new[] { "Admin", "Analysis" });
routes.MapRootArea("{controller}/{action}/{id}",
"WebUI",
new { controller = "Lobby", action = "Index", id = "" });
此修复需要区域感知视图引擎。Haack 项目中的一个是一个 1.0 的 webform 视图引擎,我已经有一个区域感知剃须刀视图引擎,我从某个地方的另一篇文章中抓取了这个引擎,这个引擎可以工作。老实说,我不记得我在哪个帖子中找到它,或者我会直接链接到它以归功于作者。但这就是它的样子(如果这里有人知道是谁写的,我会很高兴地将它归于那个人)。请注意,这里的映射与我的项目相匹配,如果不进行一些调整,可能不会与其他任何人匹配!
public class RazorAreaAwareViewEngine :RazorViewEngine
{
private static readonly string[] EmptyLocations = { };
public RazorAreaAwareViewEngine()
{
MasterLocationFormats = new string[]
{
"~/Views/{1}/{0}.master",
"~/Views/Shared/{0}.master"
};
ViewLocationFormats = new string[]
{
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/{2}/{0}.cshtml",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.cshtml",
};
PartialViewLocationFormats = ViewLocationFormats;
}
public override ViewEngineResult FindView(ControllerContext controllerContext, string viewName,string masterName, bool useCache)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (string.IsNullOrEmpty(viewName))
{
throw new ArgumentNullException(viewName,
"Value cannot be null or empty.");
}
string area = GetArea(controllerContext);
return FindAreaView(controllerContext, area, viewName,
masterName, useCache);
}
public override ViewEngineResult FindPartialView(ControllerContext controllerContext, string partialViewName,bool useCache)
{
if (controllerContext == null)
{
throw new ArgumentNullException("controllerContext");
}
if (string.IsNullOrEmpty(partialViewName))
{
throw new ArgumentNullException(partialViewName,
"Value cannot be null or empty.");
}
string area = GetArea(controllerContext);
return FindAreaPartialView(controllerContext, area,
partialViewName, useCache);
}
protected string GetArea(ControllerContext controllerContext)
{
object area = null;
if (controllerContext.RouteData.DataTokens.ContainsKey("area"))
{
area = controllerContext.RouteData.DataTokens["area"];
}
if (area == null)
{
controllerContext.RouteData.Values.TryGetValue("area", out area);
}
if(area !=null)
{
return area.ToString();
}
return null;
}
protected virtual ViewEngineResult FindAreaView(ControllerContext controllerContext, string areaName, string viewName,string masterName, bool useCache)
{
string controllerName =
controllerContext.RouteData.GetRequiredString("controller");
string[] searchedViewPaths;
string viewPath = GetPath(controllerContext, ViewLocationFormats,
"ViewLocationFormats", viewName, controllerName, areaName, "View",
useCache, out searchedViewPaths);
string[] searchedMasterPaths;
string masterPath = GetPath(controllerContext, MasterLocationFormats,
"MasterLocationFormats", masterName, controllerName, areaName,
"Master", useCache, out searchedMasterPaths);
if (!string.IsNullOrEmpty(viewPath) &&
(!string.IsNullOrEmpty(masterPath) ||
string.IsNullOrEmpty(masterName)))
{
return new ViewEngineResult(CreateView(controllerContext, viewPath,
masterPath), this);
}
return new ViewEngineResult(
searchedViewPaths.Union<string>(searchedMasterPaths));
}
protected virtual ViewEngineResult FindAreaPartialView(ControllerContext controllerContext, string areaName,string viewName, bool useCache)
{
string controllerName =
controllerContext.RouteData.GetRequiredString("controller");
string[] searchedViewPaths;
string partialViewPath = GetPath(controllerContext,
ViewLocationFormats, "PartialViewLocationFormats", viewName,
controllerName, areaName, "Partial", useCache,
out searchedViewPaths);
if (!string.IsNullOrEmpty(partialViewPath))
{
return new ViewEngineResult(CreatePartialView(controllerContext,
partialViewPath), this);
}
return new ViewEngineResult(searchedViewPaths);
}
protected string CreateCacheKey(string prefix, string name,
string controller, string area)
{
return string.Format(CultureInfo.InvariantCulture,
":ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:",
base.GetType().AssemblyQualifiedName,
prefix, name, controller, area);
}
protected string GetPath(ControllerContext controllerContext,string[] locations, string locationsPropertyName, string name,
string controllerName, string areaName, string cacheKeyPrefix,bool useCache, out string[] searchedLocations)
{
searchedLocations = EmptyLocations;
if (string.IsNullOrEmpty(name))
{
return string.Empty;
}
if ((locations == null) || (locations.Length == 0))
{
throw new InvalidOperationException(string.Format("The property " +
"'{0}' cannot be null or empty.", locationsPropertyName));
}
bool isSpecificPath = IsSpecificPath(name);
string key = CreateCacheKey(cacheKeyPrefix, name,
isSpecificPath ? string.Empty : controllerName,
isSpecificPath ? string.Empty : areaName);
if (useCache)
{
string viewLocation = ViewLocationCache.GetViewLocation(
controllerContext.HttpContext, key);
if (viewLocation != null)
{
return viewLocation;
}
}
if (!isSpecificPath)
{
return GetPathFromGeneralName(controllerContext, locations, name,
controllerName, areaName, key, ref searchedLocations);
}
return GetPathFromSpecificName(controllerContext, name, key,
ref searchedLocations);
}
protected string GetPathFromGeneralName(ControllerContext controllerContext,string[] locations, string name, string controllerName,
string areaName, string cacheKey, ref string[] searchedLocations)
{
string virtualPath = string.Empty;
searchedLocations = new string[locations.Length];
for (int i = 0; i < locations.Length; i++)
{
if (string.IsNullOrEmpty(areaName) && locations[i].Contains("{2}"))
{
continue;
}
string testPath = string.Format(CultureInfo.InvariantCulture,
locations[i], name, controllerName, areaName);
if (FileExists(controllerContext, testPath))
{
searchedLocations = EmptyLocations;
virtualPath = testPath;
ViewLocationCache.InsertViewLocation(
controllerContext.HttpContext, cacheKey, virtualPath);
return virtualPath;
}
searchedLocations[i] = testPath;
}
return virtualPath;
}
protected string GetPathFromSpecificName(ControllerContext controllerContext, string name, string cacheKey,ref string[] searchedLocations)
{
string virtualPath = name;
if (!FileExists(controllerContext, name))
{
virtualPath = string.Empty;
searchedLocations = new string[] { name };
}
ViewLocationCache.InsertViewLocation(controllerContext.HttpContext,
cacheKey, virtualPath);
return virtualPath;
}
protected static bool IsSpecificPath(string name)
{
char ch = name[0];
if (ch != '~')
{
return (ch == '/');
}
return true;
}
}
进行这些更改后,内部和外部区域的操作链接都已正确生成,我可以在内部和外部区域进行路由。