65.9K
CodeProject 正在变化。 阅读更多。
Home

ASP.NET MVC:第 2 部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.47/5 (6投票s)

2008 年 7 月 5 日

CPOL

7分钟阅读

viewsIcon

60034

downloadIcon

1074

继续探讨 ASP.NET MVC 和 URL 路由。

引言

本文是 ASP.NET MVC 第 1 部分 的续篇,我将尝试详细阐述 MVC 的 URL 路由部分。不过,请注意,上一篇文章基于 ASP.NET MVC 的 Preview 2 版本;而本文使用的是 Preview 3 版本。这两个版本之间有一些变化。

必备组件

模型-视图-控制器模式回顾

为了回顾本文第一部分的一些内容,我们简要讨论一下 MVC 模式。MVC 是一种将应用程序划分为独立职责区域的模式:模型、视图和控制器。

  • 模型:模型负责维护应用程序的状态,通常通过使用数据库。
  • 视图:这些组件严格用于显示数据,除了格式化数据以供显示外,不提供任何功能。
  • 控制器:控制器是 MVC 应用程序中的中央通信机制。它们将视图的操作传递给模型,然后再传回。

MVC 模式的主要一点是模型和视图之间没有直接通信。本文的目的是详细说明这种通信如何通过 URL 路由进行。

URL 路由

URL 路由是 MVC Web 应用程序框架的一部分。但是,Microsoft 选择将其也作为 .NET Framework 3.5 Service Pack 1 的一部分,尽管其功能可能与 MVC 中的实现有所不同。我们可以看看 URL 路由在 MVC 中是如何实现的,以便了解它如何在非 MVC Web 应用程序中实现。URL 路由组件(恰当地)位于 System.Web.Routing 命名空间中。下图显示了每个命名空间的两个版本。

URL 路由利用下面描述的 HTTPHandlers 和 HTTPModules 来执行所需的操作,我们将看到。

路由 HTTPModule

简要回顾一下,HTTPModules 被插入到 ASP.NET 管道中,作为预处理请求的一种方式。用于身份验证、会话状态等的 HTTPModules 已经内置于 ASP.NET 应用程序中。由于这些模块在请求到达页面级别之前对其进行处理,因此它是 URL 路由的完美解决方案;请求由模块处理,然后适当地路由。将以下条目添加到 web.config 文件中,在 httpModules 元素下,将 UrlRoutingModule 放入 Web 应用程序的管道中。

<add name="UrlRoutingModule" 
  type="System.Web.Routing.UrlRoutingModule, 
        System.Web.Routing, Version=3.5.0.0, 
        Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> 

提醒一下,Preview 3 使用以下方法来避免与 SP1 冲突

<add name="UrlRoutingModule" 
   type="System.Web.Routing.UrlRoutingModule, 
         System.Web.Routing, Version=0.0.0.0, Culture=neutral, 
         PublicKeyToken=31BF3856AD364E35"/>

该模块在其 Init 方法中注册了两个事件处理程序:OnApplicationPostMapRequestHandlerOnApplicationPostMapRequestHandler。后者当然用于处理 HttpApplication 对象上的 PostMapRequestHandler 事件。此事件在 ASP.NET 引擎将请求映射到事件处理程序后触发。前者用于处理 PostResolveRequestCache 事件,该事件在 ASP.NET 引擎绕过事件处理程序从缓存中提供请求时发生。此事件的处理程序是我们可以看到 URL 路由发生魔力的地方。在这里,创建了一个新的 RequestData 对象并将其添加到 HttpContext 集合中。添加了一个 IRouteHandler 接口的实例,并使用 HttpContext.RewritePath 将请求重定向到 UrlRouting.axd

RequestData data2 = new RequestData();
data2.OriginalPath = context.Request.Path;
data2.HttpHandler = httpHandler;
context.Items[_requestDataKey] = data2;
context.RewritePath("~/UrlRouting.axd");

路由 HttpHandler

再次简要回顾一下,HttpHandlers 仅用于接收请求并进行处理。在 URL 路由的情况下,UrlRoutingHandler 将用于处理来自我们上面看到的 UrlRouting.axd 的请求。在 UrlRoutingHandlerProcessRequest 方法中,尝试从 IControllerFactory 实例创建正在处理的请求的控制器实例,然后调用 Execute 方法。

string controllerName = RequestContext.RouteData.GetRequiredString("controller");
// Instantiate the controller and call Execute
IControllerFactory factory = ControllerBuilder.GetControllerFactory();
IController controller = 
   factory.CreateController(RequestContext, controllerName);
...
controller.Execute(controllerContext);

控制器 (Controller)

正如我们在第 1 部分中看到的,控制器就像 ASP.NET MVC Web 应用程序的中间件。它们接收对某个操作的请求,执行任何处理,例如从模型检索数据,然后调用视图。如上所述,找到请求的控制器,然后调用其 Execute 方法。在 Execute 方法中,找到请求的 Action 并尝试调用它。

string actionName = RouteData.GetRequiredString("action");
if (!InvokeAction(actionName))
{
    HandleUnknownAction(actionName);
}

InvokeAction 方法使用一些反射来查找操作的方法并传递任何必要的参数。

这是对 URL 路由机制内部工作原理的简要介绍。更详细的介绍可以在这里找到:http://www.cnblogs.com/shanyou/archive/2008/03/22/1117573.html

定义和创建路由

现在我们已经了解了幕后发生的事情,我们可以专注于创建和定义要在应用程序中使用的路由。

路由在 Application_Start 事件中添加到应用程序的 RouteTable 中。

protected void Application_Start(object sender, EventArgs e)
{
    RegisterRoutes(RouteTable.Routes);
}
public static void RegisterRoutes(RouteCollection routes)
{
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

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

这是 ASP.NET MVC 应用程序的默认实现。路由按照它们进入 RouteTable 的顺序进行评估。第一个条目 IgnoreRoute 是 MVC 提供的 RouteCollection 上的扩展方法。

public static void IgnoreRoute(this RouteCollection routes, string url, object constraints)
{
    if (routes == null)
    {
        throw new ArgumentNullException("routes");
    }
    if (url == null)
    {
        throw new ArgumentNullException("url");
    }
    Route route2 = new Route(url, new StopRoutingHandler());
    route2.Constraints = new RouteValueDictionary(constraints);
    Route item = route2;
    routes.Add(item);
}

此方法创建一个新的 Route,它使用 StopRoutingHandler 停止处理 HTTPHandlers 的请求。这对于在 MVC 中处理请求至关重要,因为正如我们上面看到的,请求被重定向到 UrlRouting.axd。路由使用 MapRoute 添加到集合中,MapRoute 是 MVC 提供的 RouteCollection 的扩展方法,具有三个重载。

public static void MapRoute(this RouteCollection routes, string name, string url)
public static void MapRoute(this RouteCollection routes, string name, 
                            string url, object defaults)
public static void MapRoute(this RouteCollection routes, string name, 
                            string url, object defaults, object constraints)

前两个重载调用最后一个,它创建一个使用 MvcRouteHandlerRoute,然后将其添加到集合中。

Route route = new Route(url, new MvcRouteHandler())
{
    Defaults = new RouteValueDictionary(defaults),
    Constraints = new RouteValueDictionary(constraints)
};

if(String.IsNullOrEmpty(name))
{
    // Add unnamed route if no name given
    routes.Add(route);
}
else
{
    routes.Add(name, route);
}

如果它是一个不使用 MvcRouteHandlerRoute,当然可以直接将其添加到集合中。

routes.Add(new Route("Default.aspx", 
   new{ controller = "Home", action = "Index"}, new MyRouteHandler);

路由实战

通过 URL 路由,您可以接受诸如 http://www.mysite.com/Products/Bikes/Schwinn 之类的请求,并让它显示由 Schwinn 制造的所有自行车。与使用查询字符串参数相比,这会生成一个更简洁的 URL,并且路由详细信息可以对普通用户隐藏。ASP.NET MVC 期望 URL 至少包含两个元素:controlleraction。在 URL http://www.mysite.com/Home/Index 中,Home 是应该使用的控制器,Index 是要调用的操作。为此的 Route 将如下所示

Route("{controller/{action}", 
new RouteValueDictionary( new { controller = "Home", action = "Index" }), 
                          new MvcRouteHandler());

第一个参数是用于匹配请求的 URL 模式。第二个参数是一个 RouteValueDictionary,如果请求的 URL 中未找到,则可用于提供默认值。对 http://www.mysite.com 的请求将被重写为 http://www.mysite.com/Home/Index。对 http://www.mysite.com/Home 的请求也将被重写为 http://www.mysite.com/Home/Index

路由按照它们添加到 RouteCollection 的顺序进行评估。例如,给定按此顺序添加的路由

routes.MapRoute("DefaultRoute", "{controller}/{action}/{id}",
    new { controller = "Home", action = "Index", id = 0 }
);

routes.MapRoute("NameRoute", "Artist/{name}",
    new { controller = "Music", action = "ArtistsByName", name = "AC/DC" }
);

诸如 http://www.mysite.com/Artist/Elvis 之类的 URL 将由第一个路由匹配,尽管它包含单词 Artist。第一个 Route 假定 Artist 应该是 controllerElvisaction,并将插入默认 ID 0。为了获得所需的结果,需要反转集合中的路由。给定下面的两个路由,问题在于确定哪个用于诸如 http://www.mysite.com/Artist/1394http://www.mysite.com/Artist/U2 之类的请求。

routes.MapRoute("ArtistByID", "Artist/{id}",
    new { controller = "Artist", action = "AlbumsByArtistId", id = 0 }
);

routes.MapRoute("ArtistByName", "Artist/{name}",
    new { controller = "Artist", action = "AlbumsByArtistName", name = "" }
);

在前一个 URL 中,显然提供的是一个 ID;然而,在后一个 URL 中,引擎假定 U2 是一个 ID,并将尝试按照 AlbumsByArtistId 操作方法的要求将其转换为整数。如果路由颠倒,1394 将根据 AlbumsByArtistName 的要求转换为字符串。为了解决这个问题,我们可以在创建 Route 时使用 Constraints 参数。此参数是一个 RouteValueDictionary,用作正则表达式,用于评估请求 URL 中的指定参数。

protected virtual bool ProcessConstraint(HttpContextBase httpContext, 
          object constraint, string parameterName, 
          RouteValueDictionary values, RouteDirection routeDirection)
{
    object obj2;
    IRouteConstraint constraint2 = constraint as IRouteConstraint;
    if (constraint2 != null)
    {
        return constraint2.Match(httpContext, this, 
               parameterName, values, routeDirection);
    }
    string str = constraint as string;
    if (str == null)
    {
        throw new InvalidOperationException(
              string.Format(CultureInfo.CurrentUICulture, 
              RoutingResources.Route_ValidationMustBeStringOrCustomConstraint, 
              new object[] { parameterName, this.Url }));
    }
    values.TryGetValue(parameterName, out obj2);
    string input = Convert.ToString(obj2, CultureInfo.InvariantCulture);
    string pattern = "^(" + str + ")$";
    return Regex.IsMatch(input, pattern, 
           RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
}

上面的路由可以修改如下

routes.MapRoute("ArtistByID", "Artist/{id}",
    new { controller = "Artist", action = "AlbumsByArtistId", id = 0 },
    new { id = @"\d{1,}" }
);

routes.MapRoute("ArtistByName", "Artist/{name}",
    new { controller = "Artist", action = "AlbumsByArtistName", name = "" },
    new { name = @"[a-zA-Z]{1,}" }
);

现在,对 http://www.mysite.com/Artist/1394 的请求将被评估为匹配 ArtistByID Route,而对 http://www.mysite.com/Artist/U2 的请求将被 ArtistByName Route 匹配,无论它们在 RouteTable 中出现的顺序如何。

未完待续...

ASP.NET MVC 是一项非常丰富的技术,无法在一篇文章中涵盖。希望本文已经阐明了基本概念,并可用于评估该技术的潜力。本系列的未来文章将涵盖更多方面,例如单元测试和表单。

参考文献

© . All rights reserved.