好的,之前有人问过这个问题,但没有可靠的解决方案。所以为了我自己和其他可能觉得有用的人的目的。
在MVC2(ASP.NET)中我想要它,所以当有人导航到网站时,会指定一个默认区域。因此,导航到我的站点应该会将您发送到AreaZ中的ControllerX ActionY。
在Global.asax中使用以下路线
routes.MapRoute(
"Area",
"",
new { area = "AreaZ", controller = "ControllerX ", action = "ActionY " }
);
现在这样可以尝试提供正确的页面。但是,MVC继续在站点的根目录中查找View,而不是在Area文件夹中查找。
有没有办法解决这个问题?
编辑
有一个'解决方案',即在ControllerX中,ActionY返回视图的完整路径。一点点黑客但它确实有效。但是我希望有更好的解决方案。
public ActionResult ActionY()
{
return View("~/Areas/AreaZ/views/ActionY.aspx");
}
编辑:
当拥有页面的HTML ActionLink时,这也成为一个问题。如果未设置该区域,则“操作链接”将输出为空白。
所有这些都是设计还是缺陷?
答案 0 :(得分:95)
这个人对我感兴趣,我终于有机会研究它了。其他人显然不明白这是找到视图的问题,而不是路由本身的问题 - 这可能是因为你的问题标题表明它是关于路由。
在任何情况下,由于这是与视图相关的问题,获得所需内容的唯一方法是覆盖默认视图引擎。通常,当你这样做时,它只是为了切换视图引擎(即Spark,NHaml等)的简单目的。在这种情况下,我们不需要覆盖视图创建逻辑,而是FindPartialView
类中的FindView
和VirtualPathProviderViewEngine
方法。
你可以感谢你的幸运星,这些方法实际上是虚拟的,因为VirtualPathProviderViewEngine
中的其他所有内容都不是可访问的 - 它是私有的,这使得非常讨厌覆盖查找逻辑,因为如果你希望它与位置缓存和位置格式一起使用,你必须基本上重写已经编写过的一半代码。在Reflector中进行了一些挖掘之后,我终于找到了一个可行的解决方案。
我在这里所做的是首先创建一个直接从AreaAwareViewEngine
而不是VirtualPathProviderViewEngine
派生的抽象WebFormViewEngine
。我这样做是为了让你想要创建Spark视图(或者其他),你仍然可以使用这个类作为基类型。
下面的代码非常冗长,所以为了快速总结一下它实际做了什么:它允许你将{2}
放入位置格式,这对应于区域名称,同样的方式{1}
对应于控制器名称。而已!这就是我们编写所有这些代码的原因:
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;
}
}
现在如上所述,这不是一个具体的引擎,所以你也必须创建它。幸运的是,这部分很多更容易,我们需要做的就是设置默认格式并实际创建视图:
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
的{{1}}。我已经单独留下了RouteData
,但显然如果你愿意,你可以改变它。
现在修改您的MasterLocationFormats
以注册此视图引擎:
global.asax
...并注册默认路线:
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
显然我们需要使用目录结构和视图 - 我们会保持这个非常简单:
public class DefaultController : Controller
{
public ActionResult ActionY()
{
return View("TestView");
}
}
就是这样。 最后,我们已经完成了。
在大多数情况下,您应该能够将<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %>
<h2>TestView</h2>
This is a test view in AreaZ.
和BaseAreaAwareViewEngine
放到任何MVC项目中,所以即使完成这项工作需要大量代码,只需要写一次。之后,只需在AreaAwareViewEngine
中编辑几行并创建网站结构即可。
答案 1 :(得分:95)
这就是我做的。我不知道为什么MapRoute()不允许您设置区域,但它确实返回路径对象,因此您可以继续进行任何其他想要的更改。我使用它是因为我有一个模块化的MVC站点,销售给企业客户,他们需要能够将dll放入bin文件夹以添加新模块。我允许他们更改AppSettings配置中的“HomeArea”。
var route = routes.MapRoute(
"Home_Default",
"",
new {controller = "Home", action = "index" },
new[] { "IPC.Web.Core.Controllers" }
);
route.DataTokens["area"] = area;
编辑:您也可以在AreaRegistration.RegisterArea中尝试使用默认情况下您希望用户访问的区域。我没有测试过,但是AreaRegistrationContext.MapRoute会为你设置route.DataTokens["area"] = this.AreaName;
。
context.MapRoute(
"Home_Default",
"",
new {controller = "Home", action = "index" },
new[] { "IPC.Web.Core.Controllers" }
);
答案 2 :(得分:55)
即使已经回答了 - 这是短语法(ASP.net 3,4,5):
routes.MapRoute("redirect all other requests", "{*url}",
new {
controller = "UnderConstruction",
action = "Index"
}).DataTokens = new RouteValueDictionary(new { area = "Shop" });
答案 3 :(得分:16)
感谢Aaron指出它是关于定位视图的,我误解了这一点。
[更新]我刚刚创建了一个项目,默认情况下将用户发送到区域,而不会弄乱任何代码或查找路径:
在global.asax中,照常注册:
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = ""} // Parameter defaults,
);
}
在Application_Start()
中,请务必使用以下顺序;
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
在您注册的地区,请使用
public override void RegisterArea(AreaRegistrationContext context)
{
context.MapRoute(
"ShopArea_default",
"{controller}/{action}/{id}",
new { action = "Index", id = "", controller = "MyRoute" },
new { controller = "MyRoute" }
);
}
可以找到一个例子 http://www.emphess.net/2010/01/31/areas-routes-and-defaults-in-mvc-2-rc/
我真的希望这就是你要求的......
////
我不认为在这种情况下编写伪ViewEngine
是最佳解决方案。 (缺乏声誉,我无法发表评论)。 WebFormsViewEngine
可识别区域,并包含AreaViewLocationFormats
,默认情况下定义为
AreaViewLocationFormats = new[] {
"~/Areas/{2}/Views/{1}/{0}.aspx",
"~/Areas/{2}/Views/{1}/{0}.ascx",
"~/Areas/{2}/Views/Shared/{0}.aspx",
"~/Areas/{2}/Views/Shared/{0}.ascx",
};
我相信你不遵守这个惯例。你发布了
public ActionResult ActionY()
{
return View("~/Areas/AreaZ/views/ActionY.aspx");
}
作为一个工作黑客,但那应该是
return View("~/Areas/AreaZ/views/ControllerX/ActionY.aspx");
但是,如果您不想遵循约定,您可能希望通过从WebFormViewEngine
派生(例如在MvcContrib中完成)来设置查找的短路径构造函数中的路径,或者 - 有点hacky-在Application_Start
上指定这样的约定:
((VirtualPathProviderViewEngine)ViewEngines.Engines[0]).AreaViewLocationFormats = ...;
当然,这应该稍加谨慎一点,但我认为这表明了这个想法。这些字段在MVC 2 RC中的public
中为VirtualPathProviderViewEngine
。
答案 4 :(得分:6)
我猜您希望用户在访问~/AreaZ
网址后重定向到~/
网址。
我将通过您的根HomeController
中的以下代码实现。
public class HomeController
{
public ActionResult Index()
{
return RedirectToAction("ActionY", "ControllerX", new { Area = "AreaZ" });
}
}
Global.asax
中的以下路线。
routes.MapRoute(
"Redirection to AreaZ",
String.Empty,
new { controller = "Home ", action = "Index" }
);
答案 5 :(得分:2)
首先,您使用的是什么版本的MVC2?从preview2到RC已经发生了重大变化。
假设您使用RC,我认为路由映射应该看起来不同。在您所在地区的AreaRegistration.cs
中,您可以注册某种默认路由,例如
context.MapRoute(
"ShopArea_default",
"{controller}/{action}/{id}",
new { action = "Index", id = "", controller="MyRoute" }
);
上述代码会将用户发送到我们MyRouteController
的{{1}}默认设置。
使用空字符串作为第二个参数应抛出异常,因为必须指定控制器。
当然,您必须更改ShopArea
中的默认路由,以便它不会干扰此默认路由,例如通过使用主站点的前缀。
另见这个帖子和Haack的回答:MVC 2 AreaRegistration Routes Order
希望这有帮助。
答案 6 :(得分:2)
将以下内容添加到我的Application_Start对我有用,虽然我不确定你是否在RC中有这个设置:
var engine = (WebFormViewEngine)ViewEngines.Engines.First();
// These additions allow me to route default requests for "/" to the home area
engine.ViewLocationFormats = new string[] {
"~/Views/{1}/{0}.aspx",
"~/Views/{1}/{0}.ascx",
"~/Areas/{1}/Views/{1}/{0}.aspx", // new
"~/Areas/{1}/Views/{1}/{0}.ascx", // new
"~/Areas/{1}/Views/{0}.aspx", // new
"~/Areas/{1}/Views/{0}.ascx", // new
"~/Views/{1}/{0}.ascx",
"~/Views/Shared/{0}.aspx",
"~/Views/Shared/{0}.ascx"
};
答案 7 :(得分:1)
我为此付出的努力如下:
在控制器中我添加了以下代码:
namespace MyNameSpace.Controllers {
public class DefaultController : Controller {
// GET: Default
public ActionResult Index() {
return RedirectToAction("Index", "ControllerName", new {area = "FolderName"});
}
} }
在我的RouterConfig.cs中,我添加了以下内容:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new {controller = "Default", action = "Index", id = UrlParameter.Optional});
所有这一切背后的诀窍是我创建了一个默认构造函数,每次我的应用程序启动时它始终是启动控制器。当它命中默认控制器时,它将重定向到我在默认索引操作中指定的任何控制器。在我的情况下是
www.myurl.com/FolderName/ControllerName
答案 8 :(得分:0)
routes.MapRoute(
"Area",
"{area}/",
new { area = "AreaZ", controller = "ControlerX ", action = "ActionY " }
);
你试过吗?
答案 9 :(得分:0)
在请求生命周期中完成定位不同的构建块。 ASP.NET MVC请求生命周期的第一步是将请求的URL映射到正确的控制器操作方法。此过程称为路由。默认路由在Global.asax文件中初始化,并向ASP.NET MVC框架描述如何处理请求。双击MvcApplication1项目中的Global.asax文件将显示以下代码:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing;
namespace MvcApplication1 {
public class GlobalApplication : System.Web.HttpApplication
{
public static void RegisterRoutes(RouteCollection routes)
{
routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index",
id = "" } // Parameter defaults
);
}
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
}
}
}
在编译应用程序或重新启动Web服务器时触发的Application_Start()事件处理程序中,将注册路由表。默认路由名为Default,并以http://www.example.com/ {controller} / {action} / {id}的形式响应URL。 {和}之间的变量使用请求URL中的实际值填充,如果URL中不存在覆盖,则使用默认值填充。根据默认路由参数,此默认路由将映射到Home控制器和Index操作方法。我们不会对此路由映射采取任何其他操作。
默认情况下,可以通过此默认路由映射所有可能的URL。也可以创建我们自己的路线。例如,让我们将URL http://www.example.com/Employee/Maarten映射到Employee控制器,Show动作和firstname参数。可以在我们刚刚打开的Global.asax文件中插入以下代码段。因为ASP.NET MVC框架使用第一个匹配的路由,所以此代码段应插入默认路由之上;否则路线永远不会被使用。
routes.MapRoute(
"EmployeeShow", // Route name
"Employee/{firstname}", // URL with parameters
new { // Parameter defaults
controller = "Employee",
action = "Show",
firstname = ""
}
);
现在,让我们为这条路线添加必要的组件。首先,在Controllers文件夹中创建一个名为EmployeeController的类。您可以通过向项目添加新项并选择位于Web |下的MVC Controller类模板来完成此操作MVC类别。删除Index操作方法,并将其替换为名为Show的方法或操作。此方法接受firstname参数并将数据传递到ViewData字典中。视图将使用此词典来显示数据。
EmployeeController类将Employee对象传递给视图。此Employee类应添加到Models文件夹中(右键单击此文件夹,然后从上下文菜单中选择Add | Class)。这是Employee类的代码:
namespace MvcApplication1.Models {
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}
}
答案 10 :(得分:0)
嗯,虽然创建自定义视图引擎可以为此工作,但您仍然可以选择其他方法:
public override void RegisterArea(AreaRegistrationContext context) { //this makes it work for the empty url (just domain) to act as current Area. context.MapRoute( "Area_empty", "", new { controller = "Home", action = "Index", id = UrlParameter.Optional }, namespaces: new string[] { "Area controller namespace" } ); //other routes of the area }
干杯!
答案 11 :(得分:0)
此问题的已接受解决方案是,虽然在总结如何创建自定义视图引擎时是正确的,但却没有正确回答问题。这里的问题是 Pino错误地指定了他的默认路线。特别是他的“区域”定义是不正确的。 “Area”通过DataTokens集合进行检查,并应按原样添加:
var defaultRoute = new Route("",new RouteValueDictionary(){{"controller","Default"},{"action","Index"}},null/*constraints*/,new RouteValueDictionary(){{"area","Admin"}},new MvcRouteHandler());
defaultRoute.DataTokens.Add("Namespaces","MyProject.Web.Admin.Controller");
routes.Add(defaultRoute);
默认对象中指定的“区域”将被忽略。上面的代码创建了一个默认路由,它捕获对站点根目录的请求,然后调用管理区域中的默认控制器,索引操作。还请注意添加到DataTokens的“命名空间”键,仅当您有多个具有相同名称的控制器时才需要此键。使用Mvc2和Mvc3 .NET 3.5 / 4.0
验证此解决方案答案 12 :(得分:-1)
routes.MapRoute("Default", "{*id}",
new { controller = "Home"
, action = "Index"
, id = UrlParameter.Optional
}
);