Цей мене зацікавив, і я нарешті мав шанс заглянути в це. Інші люди, мабуть, не зрозуміли, що це проблема пошуку точки зору , а не проблема самої маршрутизації - і це, мабуть, тому, що назва вашого запитання вказує, що мова йде про маршрутизацію.
У будь-якому випадку, оскільки це проблема, пов’язана з переглядом, єдиний спосіб отримати те, що ви хочете, - це перемогти механізм подання за замовчуванням . Зазвичай, коли ви робите це, це просте завдання переключити ваш двигун перегляду (тобто на Spark, NHaml тощо). У цьому випадку переосмислювати нам не логіку створення створення, а методи FindPartialView
та FindView
методи в VirtualPathProviderViewEngine
класі.
Ви можете подякувати своїм щасливим зіркам, що ці методи насправді є віртуальними, тому що все інше у VirtualPathProviderViewEngine
навіть недоступному - воно приватне, і це дуже дратує перекриття логіки пошуку, оскільки вам доведеться переписати половину коду, який вже є було написано, якщо ви хочете, щоб це добре грало з кешем розташування та форматами розташування. Після деякого копання в Reflector я нарешті придумав робоче рішення.
Що я тут зробив, це спершу створити конспект, AreaAwareViewEngine
який походить безпосередньо VirtualPathProviderViewEngine
замість цього WebFormViewEngine
. Я зробив це так, що якщо ви хочете створити вигляд Іскри замість (або будь-якого іншого), ви все одно можете використовувати цей клас як базовий тип.
Код, наведений нижче, є досить затятим, тому для швидкого підсумку того, що він насправді робить: він дозволяє вводити {2}
формат розташування, який відповідає імені області, так само, {1}
як і ім'я контролера. Це воно! Ось для чого нам довелося написати весь цей код:
BaseAreaAwareViewEngine.cs
public abstract class BaseAreaAwareViewEngine : VirtualPathProviderViewEngine
{
private static readonly string[] EmptyLocations = { };
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 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 string getArea(ControllerContext controllerContext)
{
// First try to get area from a RouteValue override, like one specified in the Defaults arg to a Route.
object areaO;
controllerContext.RouteData.Values.TryGetValue("area", out areaO);
// If not specified, try to get it from the Controller's namespace
if (areaO != null)
return (string)areaO;
string namespa = controllerContext.Controller.GetType().Namespace;
int areaStart = namespa.IndexOf("Areas.");
if (areaStart == -1)
return null;
areaStart += 6;
int areaEnd = namespa.IndexOf('.', areaStart + 1);
string area = namespa.Substring(areaStart, areaEnd - areaStart);
return area;
}
protected static bool IsSpecificPath(string name)
{
char ch = name[0];
if (ch != '~')
{
return (ch == '/');
}
return true;
}
}
Як уже було сказано, це не конкретний двигун, тому вам доведеться це створити. Ця частина, на щастя, є набагато простіше, все, що нам потрібно зробити, це встановити формати за замовчуванням і фактично створити перегляди:
AreaAwareViewEngine.cs
public class AreaAwareViewEngine : BaseAreaAwareViewEngine
{
public AreaAwareViewEngine()
{
MasterLocationFormats = new string[]
{
"~/Areas/{2}/Views/{1}/{0}.master",
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.master",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Views/{1}/{0}.master",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.master"
"~/Views/Shared/{0}.cshtml"
};
ViewLocationFormats = new string[]
{
"~/Areas/{2}/Views/{1}/{0}.aspx",
"~/Areas/{2}/Views/{1}/{0}.ascx",
"~/Areas/{2}/Views/{1}/{0}.cshtml",
"~/Areas/{2}/Views/Shared/{0}.aspx",
"~/Areas/{2}/Views/Shared/{0}.ascx",
"~/Areas/{2}/Views/Shared/{0}.cshtml",
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Views/{1}/{0}.cshtml",
"~/Views/Shared/{0}.aspx"
"~/Views/Shared/{0}.ascx"
"~/Views/Shared/{0}.cshtml"
};
PartialViewLocationFormats = ViewLocationFormats;
}
protected override IView CreatePartialView(
ControllerContext controllerContext, string partialPath)
{
if (partialPath.EndsWith(".cshtml"))
return new System.Web.Mvc.RazorView(controllerContext, partialPath, null, false, null);
else
return new WebFormView(controllerContext, partialPath);
}
protected override IView CreateView(ControllerContext controllerContext,
string viewPath, string masterPath)
{
if (viewPath.EndsWith(".cshtml"))
return new RazorView(controllerContext, viewPath, masterPath, false, null);
else
return new WebFormView(controllerContext, viewPath, masterPath);
}
}
Зауважте, що ми до стандарту додали кілька записів ViewLocationFormats
. Це нові {2}
записи, де {2}
заповіт буде відображено до area
вкладеного намиRouteData
. Я залишив MasterLocationFormats
одного, але, очевидно, ви можете це змінити, якщо хочете.
Тепер модифікуйте свої global.asax
щоб зареєструвати цей механізм перегляду:
Global.asax.cs
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new AreaAwareViewEngine());
}
... і зареєструйте маршрут за замовчуванням:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Area",
"",
new { area = "AreaZ", controller = "Default", action = "ActionY" }
);
routes.MapRoute(
"Default",
"{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = "" }
);
}
Тепер створіть AreaController
ми тільки посилалися:
DefaultController.cs (в ~ / Контролери /)
public class DefaultController : Controller
{
public ActionResult ActionY()
{
return View("TestView");
}
}
Очевидно, що нам потрібна структура каталогів і перегляд, щоб працювати з нею - це буде дуже просто:
TestView.aspx (у ~ / Області / ПлощаZ / Перегляди / за замовчуванням / або ~ / Області / ПлощаZ / Перегляди / Спільне /)
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<h2>TestView</h2>
This is a test view in AreaZ.
І це все. Нарешті, ми закінчили .
Здебільшого, ви повинні бути в змозі просто взяти BaseAreaAwareViewEngine
і AreaAwareViewEngine
і помістіть його в будь-який проект MVC, так що не дивлячись на те, що було потрібно багато коду , щоб це зробити, вам потрібно тільки написати його один раз. Після цього, лише питання редагування декількох рядків global.asax.cs
та створення структури вашого сайту.