How to set a Default Route (To an Area) in MVC

Ok this has been asked before but there is no solid solution out there. So for purpose of myself and others who may find this useful.

In MVC2 (ASP.NET) I want it so when someone navigates to the website, there is a default area specified. So navigating to my site should send you to ControllerX ActionY in AreaZ.

Using the following route in the Global.asax

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

Now this works as in it does try to serve the correct page. However MVC proceeds to look for the View in the root of the site and not in the Area folder.

Is there a way to resolve this?

EDIT

There is a 'Solution' and that is in ControllerX, ActionY return the full path of the view. Bit of a hack but it does work. However I'm hoping there is a better solution.

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

Edit:

This also becomes an issue when having a HTML ActionLink of the page. If the area is not set the Action Link is output blank.

Is all of this by design or a flaw?

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

你试过吗?

定位不同的构建块是在请求生命周期中完成的。NET MVC 请求生命周期的第一步是将请求的 URL 映射到正确的控制器操作方法。这个过程称为路由。默认路由在 Global.asax 文件中初始化,并向 ASP.NET MVC Framework 描述如何处理请求。双击 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 }的形式响应 URL。{和}之间的变量用请求 URL 中的实际值填充,如果 URL 中没有重写,则用默认值填充。这个默认路由将根据默认路由参数映射到 Home 控制器和 Index 操作方法。我们不会对这张路线图采取任何其他行动。

默认情况下,所有可能的 URL 都可以通过这个默认路由进行映射。我们也可以创造自己的路线。例如,让我们将 URL http://www.example.com/Employee/Maarten映射到 Employee 控制器、 Show 操作和 firstname 参数。下面的代码片段可以插入到我们刚刚打开的 Global.asax 文件中。因为 ASP.NET MVC Framework 使用第一个匹配的路由,所以这个代码片段应该插入到默认路由的上方,否则路由将永远不会被使用。

routes.MapRoute(


"EmployeeShow",                    // Route name
"Employee/{firstname}",            // URL with parameters
new {                             // Parameter defaults
controller = "Employee",
action = "Show",
firstname = ""
}


);

现在,让我们为这条路线添加必要的组件。首先,在 Controller 文件夹中创建一个名为 EmployeeController 的类。您可以通过向项目中添加一个新项目并选择位于 Web | MVC 类别下的 MVC 控制器类模板来实现这一点。删除 Index 操作方法,并将其替换为名为 Show 的方法或操作。此方法接受 firstname 参数并将数据传递到 ViewData 字典中。视图将使用此字典来显示数据。

EmployeeController 类将向视图传递一个 Employee 对象。此 Employee 类应该添加到 Model 文件夹中(右键单击此文件夹,然后从上下文菜单中选择 Add | Class)。以下是 Employee 类的代码:

namespace MvcApplication1.Models {


public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
}


}

首先,您使用的是哪个版本的 MVC2? 从预览2到 RC 有很大的变化。

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

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

上面的代码将在默认情况下将用户发送到 ShopArea中的 MyRouteController

Using an empty string as a second parameter should throw an exception, because a controller must be specified.

当然,您必须更改 Global.asax中的默认路由,这样它就不会干扰这个默认路由,例如为主站点使用前缀。

也可以看看这个帖子和 Haack 的答案: MVC2区域注册路由订单

希望这个能帮上忙。

I guess you want user to be redirected to ~/AreaZ URL once (s)he has visited ~/ URL. 我将通过以下代码在您的根 HomeController中实现。

public class HomeController
{
public ActionResult Index()
{
return RedirectToAction("ActionY", "ControllerX", new { Area = "AreaZ" });
}
}

And the following route in Global.asax.

routes.MapRoute(
"Redirection to AreaZ",
String.Empty,
new { controller = "Home ", action = "Index" }
);

这个让我很感兴趣,我终于有机会调查一下。其他人显然不明白这是 finding the view的问题,而不是 路由本身的问题——这可能是因为你的问题标题表明它是关于路由的。

在任何情况下,因为这是一个视图相关的问题,获得您想要的东西的唯一方法是 覆盖默认视图引擎。通常,这样做的目的很简单,就是切换视图引擎(例如,切换到 Spark、 NHaml 等)。在这种情况下,我们需要覆盖的不是视图创建逻辑,而是 VirtualPathProviderViewEngine类中的 FindPartialViewFindView方法。

You can thank your lucky stars that these methods are in fact virtual, because everything else in the VirtualPathProviderViewEngine is not even accessible - it's private, and that makes it 非常 annoying to override the find logic because you have to basically rewrite half of the code that's already been written if you want it to play nice with the location cache and the location formats. After some digging in Reflector I finally managed to come up with a working solution.

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

下面的代码非常冗长,所以为了快速总结一下它的实际功能: 它允许您将 {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}将被映射到我们放在 RouteData中的 area。我已经离开了 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 (in ~/Controllers/)

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

Obviously we need the directory structure and view to go with it - we'll keep this super simple:

Aspx (在 ~/地区/区域/视图/默认/或 ~/地区/区域/视图/共享/)

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

就是这样 终于结束了

在大多数情况下,您应该能够只采用 BaseAreaAwareViewEngineAreaAwareViewEngine并将其放入任何 MVC 项目,所以即使需要很多代码来完成这一点,您只需要编写一次。之后,只需要编辑 global.asax.cs中的几行代码并创建站点结构。

感谢亚伦指出,这是关于定位的意见,我误解了。

[更新]我刚刚创建了一个项目,它将用户发送到每个默认区域,而不会干扰任何代码或查找路径:

在 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",
};

I believe you don't adhere to this convention. You posted

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

作为一个工作黑客,但应该是

   return View("~/Areas/AreaZ/views/ControllerX/ActionY.aspx");

然而,如果你不想遵循惯例,你可以从构造函数中设置查找路径的 WebFormViewEngine(例如,在 MvcContrib 就是这样做的)派生一个简短的路径,或者——有点古怪——在 Application_Start上指定你的惯例,如下所示:

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

当然,这个过程应该更加小心,但是我认为它表明了这个想法。这些字段是 MVC 2 RC 中 VirtualPathProviderViewEngine中的 public

我就是这么做的。我不知道为什么 MapRoute ()不允许您设置这个区域,但是它确实返回了 path 对象,这样您就可以继续进行您想要的任何其他更改。我使用这个是因为我有一个模块化的 MVC 站点,这个站点销售给企业客户,他们需要能够将 dls 放入 bin 文件夹来添加新的模块。我允许他们在 AppSettings 配置中更改“ HomeArea”。

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

编辑: 你也可以在你的区域注册中尝试这个。您希望用户在默认情况下访问的区域的 RegisterArea。我还没有测试它,但地区注册上下文。MapRoute 为您设置 route.DataTokens["area"] = this.AreaName;

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

添加以下到我的 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"
};

好吧,虽然创建一个自定义视图引擎可以解决这个问题,但是您仍然可以有一个替代方案:

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

干杯!

嗯,我不知道为什么所有这些编程,我认为原来的问题很容易解决指定这个默认路线..。

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

这个问题的公认解决方案是,虽然总结如何创建自定义视图引擎是正确的,但是不能正确地回答这个问题。问题是 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);

默认对象中指定的“ area”将被忽略 。上面的代码创建了一个默认路由,该路由捕获到站点根目录的请求,然后在管理区域调用 Default controller,Index action。还请注意,“ Namespaces”键正被添加到 DataTokens 中,只有在多个控制器具有相同名称时才需要这样做。该解决方案使用 Mvc2和 Mvc3进行了验证。NET 3.5/4.0

即使这个问题已经得到了回答——这是一个简短的语法(ASP.net 3,4,5) :

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

我做了以下几件事:

  1. I created a default controller in the root/Controllers folder. I named my controller DefaultController.
  2. 在控制器中,我添加了以下代码:

    namespace MyNameSpace.Controllers {
    public class DefaultController : Controller {
    // GET: Default
    public ActionResult Index() {
    return RedirectToAction("Index", "ControllerName", new {area = "FolderName"});
    }
    } }
    
  3. In my RouterConfig.cs I added the following:

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

The trick behind all this is that I made a default constructor which will always be the startup controller every time my app starts. When it hits that default controller it will redirect to any controller I specify in the default Index Action. Which in my case is

www.myurl.com/FolderName/ControllerName

.