路由系统非常灵活,但是如果这还不能满足你的 需求,那么,你可以定制路由系统。
创建基于RouteBase 的接口
如果你不喜欢标准路由对象匹配URL的方式,或者你想实现一些特殊的接口,你可以从RouteBase中继承一个类。让你可以控制URL匹配,参数如何解析,URL链接如何生成。从RouteBase继承,你需要实现2个方法:
GetRouteData(HttpContextBase httpContext):这是一个URL匹配工作机制。framework依次在每个RouteTable.Routes调用这个方法。直到其中一个返回non-null值。
GetVirtualPath(RequestContext requestContext, RouteValueDictionary values): 这是生成对外URL的工作机制。
在此,我们演示这种自定义方式,我们创建一个RouteBase类,该类会处理继承来的URL请求。假设我们从一个已经存在的application上移植到一个MVC Framework,但是一些用户已经收藏了之前的URL地址,并且在脚本中硬编码了。我们希望能继续支持老的URL。我们可以通过常规的路由系统处理,但是这里介绍一种更好的方法。
开始之前。我们需要建立一个controller,这个controller能接受之前的request。我们把它命名为LegacyController,如下:
- using System.Web.Mvc;
- namespace URLsAndRoutes.Controllers {
- public class LegacyController : Controller {
- public ActionResult GetLegacyURL(string legacyURL) {
- return View((object)legacyURL);
- }
- }
- }
这是一个很简单的controller,GetLegacyURL action方法会将参数传递给View。如果我们要实现这个controller,我们需要使用这个方法来接受我们请求的文件,但是现在是简单的把这个URL展现在view中。
注意,上面我们已经为View方法转换了参数,View方法的其中一个重载方法接受一个string参数,该参数指定要显示的view的名字,如果不转变,那么C#编译器会认为我们是调用这个重载函数,为了避免这种情况,我们把它转换成object,这样的话我们可以调用调用另一个重载函数,该重载函数使用默认的view,并且传递view model值。我们也可以使用另一个重载方法,同时指定view name和view model,但这里,我们不希望action方法和view直接显式的关联。
GetLegacyURL.cshtml是和这个action关联的view,显示如下:
- @model string
- @{
- ViewBag.Title = "GetLegacyURL";
- Layout = null;
- }
- <h2>GetLegacyURL</h2>
- The URL requested was: @Model
这个例子非常简单,我们只是演示自定义路由行为,所以我们不会去创建复杂的action和view。
路由接收到的URL
创建LegacyRoute类,如下:
using System;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
namespace URLsAndRoutes.Infrastructure
{
public class LegacyRoute : RouteBase
{
private string[] urls;
public LegacyRoute(params string[] targetUrls)
{
urls = targetUrls;
}
public override RouteData GetRouteData(HttpContextBase httpContext)
{
RouteData result = null;
string requestedURL =
httpContext.Request.AppRelativeCurrentExecutionFilePath;
if (urls.Contains(requestedURL, StringComparer.OrdinalIgnoreCase))
{
result = new RouteData(this, new MvcRouteHandler());
result.Values.Add("controller", "Legacy");
result.Values.Add("action", "GetLegacyURL");
result.Values.Add("legacyURL", requestedURL);
}
return result;
}
public override VirtualPathData GetVirtualPath(RequestContext requestContext,
RouteValueDictionary values)
{
return null;
}
}
}
此类的构造函数,接受一个string数组,表示路由类将要支持的URL,之后,我们会在注册路由的时候指定它。上例中的GetRouteData方法,路由系统会调用它,以此判断是否要处理收到的URL。如果我们不处理这个请求,那么返回null,路由系统继续判断路由表中的下一个记录。如果可以处理,返回一个RouteData类的实例,该实例包含了controller和action变量。
当创建RouteData对象,我们需要在handler中传递值,我们使用标准的MvcRouteHandler类,此类指定了controller和 action的值:
result = new RouteData(this, new MvcRouteHandler());
对大多数的MVC应用程序来说,这个类是必须的,因为此类连结了路由系统和controller/action model。但是你可以实现一个类代替MvcRouteHandler。之后会讲到。
在这个路由实现中,我们路由了任何传递到构造函数的URL请求。当得到一个URL请求,我们为RouteValues的controller和action方法硬编码了一些值,传递了请求的URL作为legacyURL的属性。注意,属性的名字和我们action方法的参数名一致,这样保证了我们生成的值会通过参数传递给action方法。最后一步是用我们的RouteBase子类注册一个新的路由,如下代码:
public static void RegisterRoutes(RouteCollection routes) {
routes.Add(new LegacyRoute(
"~/articles/Windows_3.1_Overview.html",
"~/old/.NET_1.0_Class_Library"));
routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
我们实现了该类的实例,把我们想要路由的URL传给它。然后加入到RouteCollection集合。现在当我面请求一个我们定义的legacy URL,这个请求由我们的自定义类路由出来,并且定位到我们的controller。如下:
生成对外的URL
要支持对外URL的生成,我们需要实现GetVirtualPath方法。同样的,如果不能处理请求的,就通过返回null让路由系统。否则就返回VirtualPathData的实例。
public override VirtualPathData GetVirtualPath(RequestContext requestContext,RouteValueDictionary values)
{
VirtualPathData result = null;
if (values.ContainsKey("legacyURL") &&
urls.Contains((string)values["legacyURL"], StringComparer.OrdinalIgnoreCase))
{
result = new VirtualPathData(this,
new UrlHelper(requestContext)
.Content((string)values["legacyURL"]).Substring(1));
}
return result;
}
我们使用匿名类型传递了片段变量和其他参数,但是这背后的事情是,路由系统会将这些值转换成RouteValueDictionary对象。所以,比如,我们下面view中的代码:
@Html.ActionLink("Click me", "GetLegacyURL", new { legacyURL =
"~/articles/Windows 3.1 Overview.html" })
和legacyURL属性一起生产的匿名类型转换成RouteValueDictionary类,包含相同的名称。在此类中,我们可以处理一个对外的URL请求,如果有个key命名为legacyURL,并且它的值是之前传递到构造函数的URL值中的一个。我们能进一步指定并检测controller 和action的值,但是对这个例子来说,也已经足够了。
如果我们得到一个匹配,创建一个新 VirtualPathData的实例,在引用中传递给当前对象和对外的URL。我们使用了UrlHelper类的Content方法,转换相对URL,使之能被浏览器处理。可惜,路由系统会预先加了一个额外的/在URL上,所以我们必须小心的处理掉这个/。
创建自定义路由Handler
在路由中,我们依赖的是MvcRouteHandler,因为它连结了routing system和MVC FrameWork。路由系统允许我们自定义我们的路由handler,通过实现IRouteHandler 接口。如下:
using System.Web;
using System.Web.Routing;
namespace URLsAndRoutes.Infrastructure
{
public class CustomRouteHandler : IRouteHandler
{
public IHttpHandler GetHttpHandler(RequestContext requestContext)
{
return new CustomHttpHandler();
}
}
public class CustomHttpHandler : IHttpHandler
{
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
context.Response.Write("Hello");
}
}
}
IRouteHandler接口的目的是提供一个生成IHttpHandler接口的方法,其中IHttpHandler的作用是处理请求的。在此例中,非常简单,只是输出Hello到Client。我们在定义路由的时候可以注册自定义的handler。如下:
public static void RegisterRoutes(RouteCollection routes) {
routes.Add(new Route("SayHello", new CustomRouteHandler()));
routes.MapRoute("MyRoute", "{controller}/{action}/{id}",
new { controller = "Home", action = "Index", id = UrlParameter.Optional });
}
当请求URL/SayHello,我们的handler会处理该请求。
你可以实现自定义路由handler意味着你自己要对那些常用的方法负责,比如controller和action的处理方式,但是这也给你更多自由。