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

时间:2010-01-26 15:02:52

标签: 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 " }
            );

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

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

编辑

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

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

编辑:

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

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

13 个答案:

答案 0 :(得分:95)

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

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

你可以感谢你的幸运星,这些方法实际上是虚拟的,因为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;
    }
}

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

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}将映射到我们放入area的{​​{1}}。我已经单独留下了RouteData,但显然如果你愿意,你可以改变它。

现在修改您的MasterLocationFormats以注册此视图引擎:

的Global.asax.cs

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

DefaultController.cs(在〜/ Controllers /中)

AreaController

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

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

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)

我为此付出的努力如下:

  1. 我在root / Controllers文件夹中创建了一个默认控制器。我将我的控制器命名为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});
    
  4. 所有这一切背后的诀窍是我创建了一个默认构造函数,每次我的应用程序启动时它始终是启动控制器。当它命中默认控制器时,它将重定向到我在默认索引操作中指定的任何控制器。在我的情况下是

      

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