• ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase玩转URL


    http://www.cnblogs.com/John-Connor/archive/2012/05/03/2478821.html

    引言--

      在初级篇中,我们介绍了如何利用基于ASP.NET MVC的Web程序中的Global文件来简单的重写路由。也介绍了它本身的局限性-依赖于路由信息中的键值对:

      如果键值对中没有的值,我们无法将其利用凑出我们想要的URL表达式。

      初级篇传送门:使用Global路由表定制URL

     

      在进阶篇中,我们将介绍ASP.NET 路由相关类的基类-抽象类RouteBase,并演示如何通过继承它,让URL重写和优化变成Free Style。

    一,老板的需求

      假设我们是手机销售网站的一名程序猿(承接初级篇),经过第一次的URL重写之后,我们的手机分类页面的URL的改变:

    http://www.xxx.com/category/showcategory?categoryid=0001&view=list&orderby=price&page=1
    =>
    http://www.xxx.com/category/0001

      现在老板又提出了新的需求,URL的语义化,从而更好的反应网站的结构:

    http://www.xxx.com/ca-categoryname

      比如Nokia是一个分类,那么对应URL为 /ca-nokia,如果是iPhone分类,URL则对应 /ca-iphone。ca前缀的意思是分类category。

      对于这个需求简单的配置Global文件是无法做到的。首先我们来介绍一下ASP.NET 路由的所有类的基类RouteBase。


    二,RouteBase类简介与运行机制

     

      1. RouteBase类位于System.Web.Routing命名空间,结构如下:

    复制代码
        public abstract class RouteBase
        {
            protected RouteBase();
            public abstract RouteData GetRouteData(HttpContextBase httpContext);
            public abstract VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values);
        }
    复制代码
    • GetRouteData:根据Http请求信息返回一个对象-包含路由定义的值(如果该路由与当前请求匹配)或 null(如果该路由与请求不匹配)。
    • GetVirtualPath:检查路由值是否与某个规则匹配,返回一个对象(包含生成的 URL 和有关路由的信息)或 null(如果路由与 values 不匹配)。 
    • RouteBase:初始化该类供继承的类实例使用。此构造函数只能由继承的类调用。

      看完以上定义,可能大家会晕忽忽。我们来弄一个简单的例子说明这几个方法是如何运作的。

      首先我们新建一个类库JohnConnor.Routing,并且继承抽象类RouteBase:

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Web.Mvc;//需要添加引用,请使用3.0以上版本 using System.Web.Routing; using JohnConnor.Models;

    namespace JohnConnor.Routing { public class CategoryUrlProvider:RouteBase { public override RouteData GetRouteData(System.Web.HttpContextBase httpContext) { return null;//断点1 } public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values) { return null;//断点2 } } }
    复制代码

      这样CategoryUrlProvider类就包含了用来处理路由映射的方法。

      首先我们需要在Web程序中添加JohnConnor.Routing类库的引用,然后我们把CategoryUrlProvider类注册到Global文件的路由表中。

    复制代码
        public static void RegisterRoutes(RouteCollection routes)
            {
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.Add(new JohnConnor.Routing.CategoryUrlProvider());//分类规则
    routes.MapRoute("Home", "", new { controller = "Home", action = "Index"});//主页
    }
    复制代码

      这里相当于添加了一条新的路由规则。重新生成一下Web程序在CategoryUrlProvider中打好断点,F5启动。

      2. GetRouteData()方法

      这时候相当与你在浏览器输入了http//localhost:1234/<假设本地端口号是1234>,此时程序需要判断这个URL匹配的是哪个路由值。

      自上而下的匹配,首先会尝试匹配我们新增的分类路由规则,此时会命中GetRouteData()方法中的断点。

      因为我们返回了null,意味着该请求与我们新增的分类路由规则不匹配,那程序将在路由表中继续自上而下的进行匹配。

      直到在主页这一条规则中与其URL表达式匹配,获取了对应的路由值-调用HomeController.Index()方法。


      如果你把GetRouteData()方法修改一下:

    复制代码
    public override RouteData GetRouteData(System.Web.HttpContextBase httpContext)
            {
                var data = new RouteData(this, new MvcRouteHandler());
                data.Values.Add("controller", "Home");
                data.Values.Add("action", "Index");
                return data;
            }
    复制代码

      你就会发现,无论你在http//localhost:1234/后面输入任何相对URL,都会被定向到HomeController.Index()方法。

      因为返回的是路由值而不是null,表示已经找到匹配项,就不会再往下匹配了。<这条规则覆盖了后面所有的规则>

      当然,请不要这样写。。。

      由此可以推断出GetRouteData()方法在路由映射中担任的角色:处理请求中的URL,返回相应的路由值,不处理或不匹配则返回null。

      3. VirtualPathData()方法 

      如果你在Razor页面有这样一段通过指定路由值来获取URL的代码

    <a href="@Url.Action("Index", "Home")">首页</a>

      当视图引擎渲染页面到这句代码时,HomeController.Index()方法会被解析为一个RouteValueDictionary类型的不分大小写的键值对<假设键值对对象为values>:

    values["controller"]="Home";
    values["action"]="Index";

      这个键值对表示了一个路由值。

      同样是在路由表中自上而下的匹配这个路由值,尝试第一条分类规则时,就会命中VirtualPathData()方法中的断点。

      我们返回一个null,表示不匹配,则程序进行下一个规则的匹配。

      直到找到主页规则的路由值与之匹配时,构造出相应的相对URL"",并返回该URL。

      显示为:

    <a href="http://localhost:1234/">首页</a>

      如果我们也改写一下VirtualPathData()方法:

      

     public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
            {
                return  new VirtualPathData(this, "This-is-a-Test-URL");
            }

      结果是你通过上述方法构造的URL不论请求来自哪里,全部都会显示成http://localhost:1234/This-is-a-Test-URL

      因为我们返回的是一个相对路径,而不是null,表示已经找到匹配项,则匹配不会往下继续。<同上这条规则覆盖了后面所有的规则>

      再一次提示,请不要这样写。。。

      由此可看出,VirtualPathData()在路由映射中的活:处理请求与路由键值对,生成相应URL,不处理或不匹配则返回null。

      4.方法重写的规则

      在上文中,我一再的用红色字体提示,请不要这样写。因为每一个URL的重写类,建议仅仅处理尽可能少的路由映射

      比如CategoryUrlProvider仅处理CategoryController.Show(string categoeyid)这一个Action方法的映射。凡是不是这个方法相关的映射,都返回null。

      继续去匹配别的规则。

    三,开始动手把~

      为了最快的说明问题,我们简化了网站的内容。以下内容有助于理解后面的程序,如果时间充裕,还是自己构建一个网站来尝试以下。

      首先我们在JohnConnor.Routing类库中创建Category.cs来保存分类模型,并把所有的分类显示的保存在List<Category>中,

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    namespace JohnConnor.Models
    {
        //分类模型
        public class Category
        {
            public string CategoeyID { get; set; }
            public string CategoeyName { get; set; }
        }
        public static class CategoryManager
        {
            //这里只显示创建了三个分类作为示例,实际中AllCategories可以从数据源读取。
            public static readonly List<Category> AllCategories = new List<Category>
            {
                new Category(){ CategoeyID="001", CategoeyName="Nokia"},
                new Category(){ CategoeyID="002", CategoeyName="iPhone"},
                new Category(){ CategoeyID="003", CategoeyName="Anycall"}
            };
        }
    }
    复制代码

      假设我们网站的CategoryController是这样的。

    复制代码
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Mvc;
    using JohnConnor.Models;
    namespace JohnConnor.Web.Controllers
    {
        public class CategoryController : Controller
        {
            public ActionResult ShowCategory(string id)
            {
                var category = CategoryManager.AllCategories.Find(c => c.CategoeyID == id);
                return View(category);
            }
        }
    }
    复制代码

      首先我们建议,VirtualPathData()GetRouteData()方法是成双成对出现的。一旦你制定了一条路由规则,比如分类规则/ca-categoryname,那么:

    • GetRouteData()必须处理与这条规则匹配的每一条URL,返回相同的路由值放弃与之不匹配的URL,返回null,让匹配继续。
    • VirtualPathData()必须处理与这条规则匹配的每一次路由请求,返回相同的URL;放弃与之不匹配的请求,返回null,让匹配继续。

      !!!两者相辅相成的完成了路由值和URL的相互映射,漏掉一个,就不能构成一个完成的路由规则。直接结果是出现404或生成URL地址错误。

      GetRouteData()的代码:

    复制代码
     public override RouteData GetRouteData(System.Web.HttpContextBase httpContext)
            {
                var virtualPath = httpContext.Request.AppRelativeCurrentExecutionFilePath + httpContext.Request.PathInfo;//获取相对路径
    
           virtualPath = virtualPath.Substring(2).Trim('/');//此时URL会是~/ca-categoryname,截取后面的ca-categoryname
    if (!virtualPath.StartsWith("ca-"))//判断是否是我们需要处理的URL,不是则返回null,匹配将会继续进行。 return null; var categoryname = virtualPath.Split('-').Last();//截取ca-前缀后的分类名称
           //尝试根据分类名称获取相应分类,忽略大小写 var category = CategoryManager.AllCategories.Find(c => c.CategoeyName.Equals(categoryname,StringComparison.OrdinalIgnoreCase)); if (category == null)//如果分类是null,可能不是我们要处理的URL,返回null,让匹配继续进行 return null; //至此可以肯定是我们要处理的URL了 var data = new RouteData(this, new MvcRouteHandler());//声明一个RouteData,添加相应的路由值 data.Values.Add("controller", "Category"); data.Values.Add("action", "ShowCategory"); data.Values.Add("id", category.CategoeyID); return data;//返回这个路由值将调用CategoryController.ShowCategory(category.CategoeyID)方法。匹配终止 }
    复制代码

      VirtualPathData()的代码

    复制代码
    public override VirtualPathData GetVirtualPath(RequestContext requestContext, RouteValueDictionary values)
            {
                //判断请求是否来源于CategoryController.Showcategory(string id),不是则返回null,让匹配继续
                var categoryId = values["id"] as string;
    
                if (categoryId == null)//路由信息中缺少参数id,不是我们要处理的请求,返回null
                    return null;
    
                //请求不是CategoryController发起的,不是我们要处理的请求,返回null
                if (!values.ContainsKey("controller") || !values["controller"].ToString().Equals("category",StringComparison.OrdinalIgnoreCase))
                    return null;
                //请求不是CategoryController.Showcategory(string id)发起的,不是我们要处理的请求,返回null
                if (!values.ContainsKey("action") || !values["action"].ToString().Equals("showcategory", StringComparison.OrdinalIgnoreCase))
                    return null;
    
                //至此,我们可以确定请求是CategoryController.Showcategory(string id)发起的,生成相应的URL并返回
                var category = CategoryManager.AllCategories.Find(c => c.CategoeyID == categoryId);
    
                if (category == null)
                    throw new ArgumentNullException("category");//找不到分类抛出异常
    
                var path = "ca-" + category.CategoeyName.Trim();//生成URL
    
                return new VirtualPathData(this, path.ToLowerInvariant());
            }
    复制代码

      至此,我们就把这条路由规则的映射处理完整了。如果你掌握了上述技术,任何的URL重写和优化需求,我相信你都能Hold住。

      如果我们的主页页面是这样<Razor视图引擎>:

    复制代码
    @model List<JohnConnor.Models.Category>
    @{
        ViewBag.Title = "主页";
    }
    
    <h2><a href="@Url.Action("Index", "Home")">首页</a></h2>
    <p>
    @foreach (var item in Model)
    {
          <a href="@Url.Action("ShowCategory", "Category", new { id = item.CategoeyID })">@item.CategoeyName</a>
    }
    </p>
    复制代码

      三个分类连接会得到这样的结果

    <a href="/ca-nokia">Nokia</a>
    <a href="/ca-iphone">iPhone</a>
    <a href="/ca-anycall">Anycall</a>

      点击每一个连接都会先进入我们的处理程序,生成相应的路由值-调用CategoryController.Showcategory(string id)方法根据id显示相应的分类页面。

      ------------------------------------------------------进阶篇完---------------------------------------------------

      

      这一篇我花费了不少时间去构思如何用简单的例子讲述继承RouteBase来进行URL重写与优化。

      希望能帮助到有用的人。

      需要程序源代码朋友点这里:JohnConnor.UrlRewrite.rar       

      如有任何问题,欢迎指正和讨论。

      

  • 相关阅读:
    设计模式学习总结系列应用实例
    【研究课题】高校特殊学生的发现及培养机制研究
    Linux下Oracle11G RAC报错:在安装oracle软件时报file not found一例
    python pro practice
    openstack python sdk list tenants get token get servers
    openstack api
    python
    git for windows
    openstack api users list get token get servers
    linux 流量监控
  • 原文地址:https://www.cnblogs.com/shiningrise/p/5572977.html
Copyright © 2020-2023  润新知