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

Lia*_*amB 119 asp.net-mvc asp.net-mvc-2

好的,以前有人问过,但那里没有可靠的解决方案.所以为了我自己和其他可能觉得有用的人的目的.

在MVC2(ASP.NET)中我想要它,所以当有人导航到网站时,指定了一个默认区域.因此,导航到我的站点应该会将您发送到AreaZ中的ControllerX ActionY.

在Global.asax中使用以下路由

routes.MapRoute(
                "Area",
                "",
                new { area = "AreaZ", controller = "ControllerX ", action = "ActionY " }
            );
Run Code Online (Sandbox Code Playgroud)

现在,这样可以尝试提供正确的页面.但是,MVC继续在站点的根目录中查找View,而不是在Area文件夹中查找.

有办法解决这个问题吗?

编辑

有一个'解决方案',即在ControllerX中,ActionY返回视图的完整路径.一点点黑客但它确实有效.但是我希望有更好的解决方案.

         public ActionResult ActionY()
        {
            return View("~/Areas/AreaZ/views/ActionY.aspx");
        }
Run Code Online (Sandbox Code Playgroud)

编辑:

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

所有这些都是设计还是缺陷?

Aar*_*ght 98

这个让我感兴趣,我终于有机会研究它了.其他人显然没有理解这是找到视图的问题,而不是路由本身的问题- 这可能是因为你的问题标题表明它是关于路由的.

在任何情况下,因为这是与View相关的问题,获得所需内容的唯一方法是覆盖默认视图引擎.通常,当你这样做时,它只是为了切换视图引擎(即Spark,NHaml等)的简单目的.在这种情况下,我们不需要覆盖视图创建逻辑,而是类中的FindPartialViewFindView方法VirtualPathProviderViewEngine.

你可以感谢你的幸运星,这些方法实际上是虚拟的,因为它中的其他所有内容VirtualPathProviderViewEngine都是不可访问的 - 它是私有的,这使得覆盖查找逻辑非常烦人,因为你必须基本上重写已经有一半的代码如果您希望它与位置缓存和位置格式一起使用,那就已经写好了.经过Reflector的一些挖掘后,我终于找到了一个可行的解决方案.

我在这里所做的是首先创建一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,我们在标准中添加了一些条目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());
}
Run Code Online (Sandbox Code Playgroud)

...并注册默认路由:

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 = "" }
    );
}
Run Code Online (Sandbox Code Playgroud)

现在创建AreaController我们刚刚引用的:

DefaultController.cs(在〜/ Controllers /中)

public class DefaultController : Controller
{
    public ActionResult ActionY()
    {
        return View("TestView");
    }
}
Run Code Online (Sandbox Code Playgroud)

显然我们需要使用目录结构和视图 - 我们将保持这个非常简单:

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.
Run Code Online (Sandbox Code Playgroud)

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

在大多数情况下,你应该能够只取BaseAreaAwareViewEngineAreaAwareViewEngine其拖放到任何MVC项目,所以即使它花了很多的代码来完成这件事,你只需要编写一次.之后,只需编辑几行global.asax.cs并创建站点结构即可.

  • 我讨厌倒卖,但我真的不敢相信@Chris Alderson的下面的回答没有得到更多的选票.这是一个比这个更简单的解决方案,似乎可以解决边缘情况(ActionLinks等). (3认同)

小智 98

This is how I did it. I don't know why MapRoute() doesn't allow you to set the area, but it does return the route object so you can continue to make any additional changes you would like. I use this because I have a modular MVC site that is sold to enterprise customers and they need to be able to drop dlls into the bin folder to add new modules. I allow them to change the "HomeArea" in the AppSettings config.

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

Edit: You can try this as well in your AreaRegistration.RegisterArea for the area you want the user going to by default. I haven't tested it but AreaRegistrationContext.MapRoute does sets route.DataTokens["area"] = this.AreaName; for you.

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


Ser*_*usM 56

即使它已被回答 - 这是短语法(ASP.net 3,4,5):

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

  • 这对我很有用.我根本没有任何控制器,只使用区域.对于MVC 4,我将其替换为RouteConfig.cs中的默认值.谢谢! (6认同)
  • 我正在使用MVC4,这对我来说是最简单的解决方案.允许应用程序将特定区域内的索引视图用作站点的"主页". (2认同)
  • 此解决方案将来不会起作用(来自Asp.Net MVC6及更高版本). (2认同)

mne*_*syn 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,
        );
    }
Run Code Online (Sandbox Code Playgroud)

Application_Start(),请务必使用以下顺序;

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        RegisterRoutes(RouteTable.Routes);
    }
Run Code Online (Sandbox Code Playgroud)

在您所在地区注册,使用

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            "ShopArea_default",
            "{controller}/{action}/{id}",
            new { action = "Index", id = "", controller = "MyRoute" },
            new { controller = "MyRoute" }
        );
    }
Run Code Online (Sandbox Code Playgroud)

可以在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",
    };
Run Code Online (Sandbox Code Playgroud)

我相信你不遵守这个惯例.你发布了

public ActionResult ActionY() 
{ 
    return View("~/Areas/AreaZ/views/ActionY.aspx"); 
} 
Run Code Online (Sandbox Code Playgroud)

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

   return View("~/Areas/AreaZ/views/ControllerX/ActionY.aspx"); 
Run Code Online (Sandbox Code Playgroud)

但是,如果您不想遵循约定,您可能希望通过从WebFormViewEngine(例如,在MvcContrib中完成)派生来获取短路径,您可以在构造函数中设置查找路径,或者-a小hacky-通过这样指定你的约定Application_Start:

((VirtualPathProviderViewEngine)ViewEngines.Engines[0]).AreaViewLocationFormats = ...;
Run Code Online (Sandbox Code Playgroud)

当然,这应该稍加谨慎地进行,但我认为这表明了这个想法.这些字段publicVirtualPathProviderViewEngine的MVC 2 RC.

  • 关于`RegisterAreas`在`RegisterRoutes`之前的重要提示.想知道为什么我的代码突然停止工作并注意到重构;) (2认同)

Ant*_*kov 6

我想你希望用户在~/AreaZ访问过~/网址后重定向到网址一次.我将通过您的根目录中的以下代码实现HomeController.

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

以及以下路线Global.asax.

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