Điều này khiến tôi quan tâm, và cuối cùng tôi đã có cơ hội xem xét nó. Những người khác dường như không hiểu rằng đây là vấn đề với việc tìm kiếm chế độ xem , không phải là vấn đề với chính việc định tuyến - và đó có thể là do tiêu đề câu hỏi của bạn chỉ ra rằng đó là về định tuyến.
Trong mọi trường hợp, vì đây là vấn đề liên quan đến Chế độ xem, cách duy nhất để đạt được những gì bạn muốn là ghi đè công cụ chế độ xem mặc định . Thông thường, khi bạn làm điều này, mục đích đơn giản là chuyển đổi chế độ xem của bạn (tức là sang Spark, NHaml, v.v.). Trong trường hợp này, nó không phải là logic tạo View mà chúng ta cần ghi đè, mà là FindPartialView
và FindView
các phương thức trong VirtualPathProviderViewEngine
lớp.
Bạn có thể cảm ơn những ngôi sao may mắn của mình rằng những phương pháp này trên thực tế là ảo, bởi vì mọi thứ khác trong đó VirtualPathProviderViewEngine
thậm chí không thể truy cập được - nó riêng tư và điều đó khiến bạn rất khó chịu khi ghi đè logic tìm kiếm vì về cơ bản bạn phải viết lại một nửa đoạn mã đã có được viết nếu bạn muốn nó hoạt động tốt với bộ đệm ẩn vị trí và các định dạng vị trí. Sau khi đào sâu trong Reflector, cuối cùng tôi đã tìm ra một giải pháp hiệu quả.
Những gì tôi đã làm ở đây là đầu tiên tạo một bản tóm tắt AreaAwareViewEngine
dẫn xuất trực tiếp từ VirtualPathProviderViewEngine
nó thay vì WebFormViewEngine
. Tôi đã làm điều này để thay thế nếu bạn muốn tạo các khung nhìn Spark (hoặc bất cứ thứ gì), bạn vẫn có thể sử dụng lớp này làm loại cơ sở.
Đoạn mã dưới đây khá dài dòng, vì vậy, để cung cấp cho bạn một bản tóm tắt nhanh về những gì nó thực sự hoạt động: Nó cho phép bạn đặt một {2}
định dạng vị trí, tương ứng với tên khu vực, theo cách {1}
tương tự với tên bộ điều khiển. Đó là nó! Đó là những gì chúng tôi phải viết tất cả mã này cho:
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;
}
}
Như đã nói, đây không phải là một động cơ cụ thể, vì vậy bạn cũng phải tạo ra nó. May mắn thay, phần này dễ dàng hơn nhiều , tất cả những gì chúng ta cần làm là đặt các định dạng mặc định và thực sự tạo các chế độ xem:
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);
}
}
Lưu ý rằng chúng tôi đã thêm một số mục vào tiêu chuẩn ViewLocationFormats
. Đây là các {2}
mục nhập mới , nơi mục {2}
sẽ được ánh xạ tới mục area
chúng tôi đưa vào RouteData
. Tôi đã để MasterLocationFormats
yên, nhưng rõ ràng bạn có thể thay đổi điều đó nếu bạn muốn.
Bây giờ sửa đổi của bạn global.asax
để đăng ký công cụ xem này:
Global.asax.cs
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ViewEngines.Engines.Clear();
ViewEngines.Engines.Add(new AreaAwareViewEngine());
}
... và đăng ký tuyến đường mặc định:
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 = "" }
);
}
Bây giờ Tạo AreaController
chúng tôi vừa tham chiếu:
DefaultController.cs (trong ~ / Controllers /)
public class DefaultController : Controller
{
public ActionResult ActionY()
{
return View("TestView");
}
}
Rõ ràng là chúng ta cần cấu trúc thư mục và chế độ xem đi cùng với nó - chúng ta sẽ giữ điều này cực kỳ đơn giản:
TestView.aspx (trong ~ / Areas / AreaZ / Views / Default / hoặc ~ / Areas / AreaZ / Views / Shared /)
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<h2>TestView</h2>
This is a test view in AreaZ.
Và đó là nó. Cuối cùng, chúng tôi đã hoàn thành .
Đối với hầu hết các phần, bạn sẽ có thể chỉ cần đi BaseAreaAwareViewEngine
và AreaAwareViewEngine
và thả nó vào bất kỳ dự án MVC, vì vậy mặc dù phải mất rất nhiều mã để thực hiện điều này, bạn chỉ phải viết nó một lần. Sau đó, bạn chỉ cần chỉnh sửa một vài dòng global.asax.cs
và tạo cấu trúc trang web của mình.