如何在MVC中设置默认路由(到区域)

好吧,这已经被问过,但没有固定的解决scheme。 所以为了我自己和其他可能觉得有用的人的目的。

在MVC2(ASP.NET)中,我希望当有人浏览到网站时,有一个指定的默认区域。 所以导航到我的网站应该送你到AreaZ的ControllerX ActionY。

在Global.asax中使用以下路由

routes.MapRoute( "Area", "", new { area = "AreaZ", controller = "ControllerX ", action = "ActionY " } ); 

现在,它的工作原理就是尽力服务正确的页面。 但是,MVC继续查找站点根目录中的View,而不是在Area文件夹中。

有没有办法解决这个问题?

编辑

有一个'解决scheme',那是在ControllerX中,ActionY返回视图的完整path。 一个黑客的位,但它确实工作。 不过,我希望有一个更好的解决scheme。

  public ActionResult ActionY() { return View("~/Areas/AreaZ/views/ActionY.aspx"); } 

编辑:

当拥有页面的HTML ActionLink时,这也成为一个问题。 如果该区域未设置,则操作链接输出为空白。

这是所有这一切的devise或缺陷?

这个对我很感兴趣,而且我终于有机会去研究它了。 其他人显然没有理解,这是一个问题, 发现的意见 ,而不是路由本身的问题 – 这可能是因为你的问题标题表明,这是关于路由。

在任何情况下,因为这是一个视图相关的问题,得到你想要的唯一方法是覆盖默认的视图引擎 。 通常情况下,当你这样做的时候,就是为了简单的切换你的视图引擎(例如Spark,NHaml等等)。 在这种情况下,它不是我们需要重写的视图创build逻辑,而是VirtualPathProviderViewEngine类中的FindPartialViewFindView方法。

你可以感谢你幸运的星星,因为这些方法实际上是虚拟的,因为VirtualPathProviderViewEngine其他东西都是不可访问的 – 它是私有的,这使得重写查找逻辑非常烦人,因为你必须重写一半的代码已经被写入,如果你想要它与位置caching和位置格式很好玩。 在Reflector中进行了一些挖掘之后,我终于想出了一个可行的解决scheme。

我在这里所做的是首先创build一个抽象的AreaAwareViewEngine ,它直接从VirtualPathProviderViewEngine而不是WebFormViewEngine派生。 我这样做,所以如果你想创buildSpark视图(或其他),你仍然可以使用这个类作为基types。

下面的代码非常冗长,所以简要介绍一下它的实际作用:它可以让你把{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; } } 

如上所述,这不是一个具体的引擎,所以你也必须创build它。 幸运的是,这一部分容易得多 ,我们所要做的就是设置默认格式并实际创build视图:

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}将映射到我们放入RouteDataarea 。 我已经离开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 = "" } ); } 

现在创build我们刚刚引用的AreaController

DefaultController.cs(在〜/ Controllers /中)

 public class DefaultController : Controller { public ActionResult ActionY() { return View("TestView"); } } 

很明显,我们需要目录结构和视图来支持它 – 我们将保持这个超级简单:

TestView.aspx(在〜/ Areas / AreaZ / Views / Default /或〜/ Areas / AreaZ / Views / Shared /中)

 <%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" %> <h2>TestView</h2> This is a test view in AreaZ. 

就是这样。 最后,我们完成了

在大多数情况下,您应该只需将BaseAreaAwareViewEngineAreaAwareViewEngine放到任何MVC项目中,即使需要大量代码才能完成此任务,您只需编写一次即可。 之后,只需在global.asax.cs中编辑几行代码并创build您的网站结构即可。

我就是这么做的 我不知道为什么MapRoute()不允许你设置区域,但它确实返回路由对象,所以你可以继续进行任何你想要的更改。 我使用这个,因为我有一个模块化的MVC网站,出售给企业客户,他们需要能够将dll放入bin文件夹以添加新模块。 我允许他们更改AppSettingsconfiguration中的“HomeArea”。

 var route = routes.MapRoute( "Home_Default", "", new {controller = "Home", action = "index" }, new[] { "IPC.Web.Core.Controllers" } ); route.DataTokens["area"] = area; 

编辑:您也可以在您的AreaRegistration.RegisterArea中尝试这个默认情况下用户要去的区域。 我没有testing它,但AreaRegistrationContext.MapRoute没有设置route.DataTokens["area"] = this.AreaName; 为你。

 context.MapRoute( "Home_Default", "", new {controller = "Home", action = "index" }, new[] { "IPC.Web.Core.Controllers" } ); 

即使它已经被回答了 – 这是简短的语法(ASP.net 3,4,5):

 routes.MapRoute("redirect all other requests", "{*url}", new { controller = "UnderConstruction", action = "Index" }).DataTokens = new RouteValueDictionary(new { area = "Shop" }); 

感谢Aaron指出这是关于查看观点,我误解了这一点。

[更新]我刚刚创build了一个项目,将用户发送到一个区域默认情况下没有任何代码或查找path搞乱:

在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/find。;

我真的希望这是你所要求的…

////

我不认为在这种情况下写一个伪ViewEngine是最好的解决scheme。 (缺乏声誉,我不能评论)。 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中完成)中获取一个简短path,您可以在其中设置构造函数中的查找path,或者 -通过在Application_Start上指定你的约定,

 ((VirtualPathProviderViewEngine)ViewEngines.Engines[0]).AreaViewLocationFormats = ...; 

当然,这应该更加谨慎一点,但是我认为这表明了这个想法。 这些字段在MVC 2 RC的VirtualPathProviderViewEngine中是public

我想你希望用户被redirect到~/AreaZ URL一次他访问过~/ URL。 我会实现通过您的根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" } ); 

首先,你使用的是什么版本的MVC2? 从preview2到RC有很大的变化。

假设你使用RC,我认为你的路由映射应该看起来不一样。 在您所在地区的AreaRegistration.cs中,您可以注册某种默认路由,例如

  context.MapRoute( "ShopArea_default", "{controller}/{action}/{id}", new { action = "Index", id = "", controller="MyRoute" } ); 

上面的代码会将用户默认发送到MyRouteController中的ShopArea

使用空string作为第二个参数应该抛出一个exception,因为必须指定一个控制器。

当然,你将不得不改变Global.asax的默认路由,所以它不会干扰这个默认路由,例如通过使用主站点的前缀。

也看到这个线程和哈克的回答: MVC 2区域注册路线顺序

希望这可以帮助。

将以下内容添加到我的Application_Start中,尽pipe我不确定在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" }; 

我做了什么来实现这个工作如下:

  1. 我在root / Controllers文件夹中创build了一个默认的控制器。 我命名我的控制器DefaultController。
  2. 在控制器中,我添加了以下代码:

     namespace MyNameSpace.Controllers { public class DefaultController : Controller { // GET: Default public ActionResult Index() { return RedirectToAction("Index", "ControllerName", new {area = "FolderName"}); } } } 
  3. 在我的RouterConfig.cs中,我添加了以下内容:

     routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new {controller = "Default", action = "Index", id = UrlParameter.Optional}); 

所有这一切背后的诀窍是,我做了一个默认的构造函数,每当我的应用程序启动时,它始终是启动控制器。 当它达到默认控制器时,它将redirect到我在默认索引操作中指定的任何控制器。 在我的情况是这样的

http://www.myurl.com/FolderName/ControllerName

 routes.MapRoute( "Area", "{area}/", new { area = "AreaZ", controller = "ControlerX ", action = "ActionY " } ); 

你尝试过吗?

在请求生命周期中定位不同的构build块。 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); } } } 

在Application_Start()事件处理程序中,每当编译应用程序或重新启动Web服务器时,都会触发一个路由表。 默认路由名为Default,并以http://www.example.com/ {controller} / {action} / {id}的forms对url进行响应。 如果url中没有覆盖,则{和}之间的variables将填充来自请求URL的实际值或默认值。 此默认路由将根据默认路由参数映射到Home控制器和Index操作方法。 这个路由映射我们不会有任何其他动作。

默认情况下,所有可能的URL都可以通过这个默认路由映射。 也可以创build我们自己的路线。 例如,让我们将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文件夹中创build一个名为EmployeeController的类。 您可以通过向项目添加新项目并select位于Web |下的MVC控制器类模板来完成此操作 MVC类别。 删除Index操作方法,并将其replace为名为Show的方法或操作。 该方法接受firstname参数并将数据传递到ViewData字典中。 该字典将被视图用来显示数据。

EmployeeController类将把一个Employee对象传递给视图。 这个Employee类应该添加到Models文件夹中(右键单击该文件夹,然后从上下文菜单中selectAdd | Class)。 以下是Employee类的代码:

 namespace MvcApplication1.Models { public class Employee { public string FirstName { get; set; } public string LastName { get; set; } public string Email { get; set; } } } 

那么,虽然创build自定义视图引擎可以为此工作,仍然可以有一个替代scheme:

  • 决定默认情况下需要显示的内容。
  • 那东西有控制器和行动(和区域),对不对?
  • 打开该区域注册并添加如下内容:
 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 } 

干杯!

对这个问题的接受的解决scheme是正确的总结如何创build一个自定义的视图引擎,不正确地回答这个问题。 这里的问题是皮诺错误地指定了他的默认路线 。 特别是他的“区域”定义是不正确的。 “区域”是通过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); 

默认值对象中指定的“区域”将被忽略 。 上面的代码创build一个默认路由,它捕获到您的网站的根的请求,然后调用pipe理区域中的默认控制器,索引操作。 还请注意“名称空间”键被添加到DataTokens,这只有当你有多个同名的控制器时才需要。 这个解决scheme通过Mvc2和Mvc3 .NET 3.5 / 4.0进行validation

嗯,我不知道为什么所有这些编程,我认为原来的问题很容易通过指定此默认路由解决…

 routes.MapRoute("Default", "{*id}", new { controller = "Home" , action = "Index" , id = UrlParameter.Optional } );