如何在 MVC 中设置默认路由(到一个区域) [英] How to set a Default Route (To an Area) in MVC

查看:33
本文介绍了如何在 MVC 中设置默认路由(到一个区域)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

好的,以前有人问过这个问题,但没有固定的解决方案.所以为了我自己和其他可能觉得这很有用的人.

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

在 Global.asax 中使用以下路由

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

现在这可以正常工作,因为它确实尝试提供正确的页面.然而,MVC 继续在站点的根目录中而不是在区域文件夹中查找视图.

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

编辑

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

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

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

这一切是设计还是缺陷?

解决方案

这个我很感兴趣,我终于有机会研究它.其他人显然不明白这是寻找视图的问题,而不是路由本身的问题——这可能是因为你的问题标题表明它是关于路由.

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

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

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

下面的代码非常冗长,所以让您快速总结一下它的实际作用:它允许您将 {2} 放入与区域相对应的位置格式中名称,与 {1} 对应的控制器名称相同.就是这样!这就是我们必须编写所有这些代码的目的:

BaseAreaAwareViewEngine.cs

公共抽象类 BaseAreaAwareViewEngine : VirtualPathProviderViewEngine{私有静态只读字符串[] EmptyLocations = { };公共覆盖 ViewEngineResult FindView(ControllerContext 控制器上下文,字符串视图名称,字符串 masterName, bool useCache){如果(控制器上下文 == 空){throw new ArgumentNullException(controllerContext");}if (string.IsNullOrEmpty(viewName)){抛出新的 ArgumentNullException(viewName,值不能为空或为空.");}字符串区域 = getArea(controllerContext);返回 FindAreaView(controllerContext, area, viewName,masterName, useCache);}公共覆盖 ViewEngineResult FindPartialView(ControllerContext controllerContext,字符串partialViewName,bool useCache){如果(控制器上下文 == 空){throw new ArgumentNullException(controllerContext");}if (string.IsNullOrEmpty(partialViewName)){抛出新的 ArgumentNullException(partialViewName,值不能为空或为空.");}字符串区域 = getArea(controllerContext);返回 FindAreaPartialView(controllerContext, area,局部视图名称,使用缓存);}受保护的虚拟 ViewEngineResult FindAreaView(ControllerContext controllerContext,字符串 areaName,字符串 viewName,字符串 masterName, bool useCache){字符串控制器名称 =controllerContext.RouteData.GetRequiredString(控制器");string[] searchedViewPaths;字符串 viewPath = GetPath(controllerContext, ViewLocationFormats,ViewLocationFormats",视图名称,控制器名称,区域名称,视图",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))){返回新的 ViewEngineResult(CreateView(controllerContext, viewPath,masterPath), this);}返回新的 ViewEngineResult(searchedViewPaths.Union(searchedMasterPaths));}受保护的虚拟 ViewEngineResult FindAreaPartialView(ControllerContext 控制器上下文,字符串 areaName,字符串视图名称,布尔 useCache){字符串控制器名称 =controllerContext.RouteData.GetRequiredString(控制器");string[] searchedViewPaths;string partialViewPath = GetPath(controllerContext,ViewLocationFormats, "PartialViewLocationFormats", viewName,控制器名称,区域名称,部分",使用缓存,out searchedViewPaths);if (!string.IsNullOrEmpty(partialViewPath)){返回新的 ViewEngineResult(CreatePartialView(controllerContext,partialViewPath), this);}返回新的 ViewEngineResult(searchedViewPaths);}受保护的字符串 CreateCacheKey(字符串前缀,字符串名称,字符串控制器,字符串区域){返回 string.Format(CultureInfo.InvariantCulture,:ViewCacheEntry:{0}:{1}:{2}:{3}:{4}:",base.GetType().AssemblyQualifiedName,前缀、名称、控制器、区域);}受保护的字符串 GetPath(ControllerContext controllerContext,字符串[] 位置,字符串locationsPropertyName,字符串名称,字符串控制器名称、字符串区域名称、字符串缓存密钥前缀、bool useCache, out string[] searchedLocations){searchedLocations = EmptyLocations;if (string.IsNullOrEmpty(name)){返回字符串.空;}if ((locations == null) || (locations.Length == 0)){throw new InvalidOperationException(string.Format("属性" +'{0}' 不能为 null 或为空.",locationsPropertyName));}bool isSpecificPath = IsSpecificPath(name);string key = CreateCacheKey(cacheKeyPrefix, name,是特定路径?string.Empty : 控制器名称,是特定路径?string.Empty : areaName);如果(使用缓存){字符串 viewLocation = ViewLocationCache.GetViewLocation(controllerContext.HttpContext, key);如果(视图位置!= null){返回视图位置;}}如果 (!isSpecificPath){返回 GetPathFromGeneralName(controllerContext, location, name,控制器名称、区域名称、键、参考搜索位置);}返回 GetPathFromSpecificName(controllerContext, name, key,参考搜索位置);}受保护的字符串 GetPathFromGeneralName(ControllerContext controllerContext,字符串[] 位置、字符串名称、字符串控制器名称、字符串区域名称,字符串缓存键,参考字符串[] searchedLocations){字符串 virtualPath = string.Empty;searchedLocations = 新字符串[locations.Length];for (int i = 0; i 

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

AreaAwareViewEngine.cs

公共类 AreaAwareViewEngine : BaseAreaAwareViewEngine{公共区域AwareViewEngine(){MasterLocationFormats = 新字符串[]{~/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 = 新字符串[]{~/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;}受保护的覆盖 IView CreatePartialView(ControllerContext controllerContext,字符串partialPath){如果(partialPath.EndsWith(.cshtml"))返回新 System.Web.Mvc.RazorView(controllerContext, partialPath, null, false, null);别的返回新的 WebFormView(controllerContext, partialPath);}受保护的覆盖 IView CreateView(ControllerContext controllerContext,字符串视图路径,字符串主路径){如果(viewPath.EndsWith(.cshtml"))返回新的 RazorView(controllerContext, viewPath, masterPath, false, null);别的返回新的 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}");路线.MapRoute(区域",",new { area = "AreaZ", controller = "Default", action = "ActionY";});路线.MapRoute(默认",{控制器}/{动作}/{id}",new { controller = Home",action = Index",id = ";});}

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

DefaultController.cs(在 ~/Controllers/中)

public class DefaultController : Controller{公共 ActionResult ActionY(){返回视图(测试视图");}}

显然我们需要目录结构和视图来配合它——我们会保持这个超级简单:

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

<%@ Page Title="";语言=C#"继承 =System.Web.Mvc.ViewPage"%><h2>TestView</h2>这是AreaZ 中的测试视图.

就是这样.终于,我们完成了.

在大多数情况下,您应该能够只使用 BaseAreaAwareViewEngineAreaAwareViewEngine 并将其放入任何 MVC 项目中,因此即使它占用了大量代码要完成此操作,您只需编写一次.之后,只需在 global.asax.cs 中编辑几行并创建站点结构即可.

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?

解决方案

This one interested me, and I finally had a chance to look into it. Other folks apparently haven't understood that this is an issue with finding the view, not an issue with the routing itself - and that's probably because your question title indicates that it's about routing.

In any case, because this is a View-related issue, the only way to get what you want is to override the default view engine. Normally, when you do this, it's for the simple purpose of switching your view engine (i.e. to Spark, NHaml, etc.). In this case, it's not the View-creation logic we need to override, but the FindPartialView and FindView methods in the VirtualPathProviderViewEngine class.

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 very 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.

What I've done here is to first create an abstract AreaAwareViewEngine that derives directly from VirtualPathProviderViewEngine instead of WebFormViewEngine. I did this so that if you want to create Spark views instead (or whatever), you can still use this class as the base type.

The code below is pretty long-winded, so to give you a quick summary of what it actually does: It lets you put a {2} into the location format, which corresponds to the area name, the same way {1} corresponds to the controller name. That's it! That's what we had to write all this code for:

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;
    }
}

Now as stated, this isn't a concrete engine, so you have to create that as well. This part, fortunately, is much easier, all we need to do is set the default formats and actually create the views:

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);
    }
}

Note that we've added few entries to the standard ViewLocationFormats. These are the new {2} entries, where the {2} will be mapped to the area we put in the RouteData. I've left the MasterLocationFormats alone, but obviously you can change that if you want.

Now modify your global.asax to register this view engine:

Global.asax.cs

protected void Application_Start()
{
    RegisterRoutes(RouteTable.Routes);
    ViewEngines.Engines.Clear();
    ViewEngines.Engines.Add(new AreaAwareViewEngine());
}

...and register the default route:

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 = "" }
    );
}

Now Create the AreaController we just referenced:

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:

TestView.aspx (in ~/Areas/AreaZ/Views/Default/ or ~/Areas/AreaZ/Views/Shared/)

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

And that's it. Finally, we're done.

For the most part, you should be able to just take the BaseAreaAwareViewEngine and AreaAwareViewEngine and drop it into any MVC project, so even though it took a lot of code to get this done, you only have to write it once. After that, it's just a matter of editing a few lines in global.asax.cs and creating your site structure.

这篇关于如何在 MVC 中设置默认路由(到一个区域)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆