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>中,

View Code
复制代码
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是这样的。

View Code
复制代码
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,不是我们要处理的请求,返回nullreturn null;//请求不是CategoryController发起的,不是我们要处理的请求,返回nullif (!values.ContainsKey("controller") || !values["controller"].ToString().Equals("category",StringComparison.OrdinalIgnoreCase))return null;//请求不是CategoryController.Showcategory(string id)发起的,不是我们要处理的请求,返回nullif (!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();//生成URLreturn new VirtualPathData(this, path.ToLowerInvariant());}
复制代码

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

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

View Code
复制代码
@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       

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

转载于:https://www.cnblogs.com/tonykan/archive/2012/12/18/2824045.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/474776.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode MySQL 580. 统计各专业学生人数

文章目录1. 题目2. 解题1. 题目 一所大学有 2 个数据表&#xff0c;分别是 student 和 department &#xff0c;这两个表保存着每个专业的学生数据和院系数据。 写一个查询语句&#xff0c;查询 department 表中每个专业的学生人数 &#xff08;即使没有学生的专业也需列出&a…

python数据抓取技术与实战训练_师傅带徒弟学Python:项目实战1:网络爬虫与抓取股票数据...

本视频基于**Python 3.X版本本视频课程是第四篇第一个实战项目&#xff0c;内容包括网络爬虫技术、使用urllib爬取数据、使用Selenium爬取数据、使用正则表达式、使用BeautifulSoup库、MySQL数据库、Python访问数据库、Lambda表达式和多线程。目录&#xff1a;22.1 网络爬虫技术…

gPodder 3.4 发布,播客接收器

gPodder 3.4 修复了 Youtube 和 Vimeo 下载的问题&#xff0c;更新了 Flattr 集成&#xff0c;性能和 UI 方面的改进等。 gPodder是播客接收器&#xff0c;采用Python和PyGTK开发。它帮你管理播客RSS供稿&#xff0c;并自动下载您想要的所有的播客许多资料。如果你对某个 供稿R…

LeetCode 1522. Diameter of N-Ary Tree(递归)

文章目录1. 题目2. 解题1. 题目 Given a root of an N-ary tree, you need to compute the length of the diameter of the tree. The diameter of an N-ary tree is the length of the longest path between any two nodes in the tree. This path may or may not pass thro…

python控制条件语句_Python条件控制语句

一、条件判断语句(if语句)执行的流程if语句在执行时&#xff0c;会先对条件表达式进行求值判断如果为True&#xff0c;则执行if后的语句如果为False&#xff0c;则不执行语法&#xff1a;if 条件表达式&#xff1a;代码块代码块代码中保留着一组代码&#xff0c;同一个代码块中…

LeetCode MySQL 1355. 活动参与者(any函数)

文章目录1. 题目2. 解题1. 题目 表: Friends ------------------------ | Column Name | Type | ------------------------ | id | int | | name | varchar | | activity | varchar | ------------------------ id 是朋友的 id 和该表的主…

python zipfile教程_Python中zipfile压缩文件模块的基本使用教程

zipfilePython 中 zipfile 模块提供了对 zip 压缩文件的一系列操作。fzipfile.ZipFile("test.zip",mode"") //解压是 r , 压缩是 w 追加压缩是 amode的几种&#xff1a;解压&#xff1a;r压缩&#xff1a;w追加压缩&#xff1a;a压缩一个文件创建一个压缩文…

asp不同编码下 UTF-8 GB2312转换收集

用于gb2312下接收新浪api 的json数据utf-8转gb2312 UTF-8字符转换成GB2312 Function UTF2GB(UTFStr) For Dig1 to len(UTFStr) if mid(UTFStr,Dig,1)"%" then if len(UTFStr) > Dig8 then GBStrGBStr & ConvChinese(mid(UTFStr,Dig,9)) DigDig8 else GBStrG…

LeetCode MySQL 578. 查询回答率最高的问题

文章目录1. 题目2. 解题1. 题目 从 survey_log 表中获得回答率最高的问题&#xff0c; survey_log 表包含这些列&#xff1a;id, action, question_id, answer_id, q_num, timestamp。 id 表示用户 id&#xff1b; action 有以下几种值&#xff1a;"show"&#xff…

mysql的cpu飙升到500_[MySQLCPU]线上飙升800%,load达到12的解决过程

接到报警通知&#xff0c;负载过高&#xff0c;达到800%&#xff0c;load也过高&#xff0c;有11了。MySQL版本为5.6.12-log1 top 之后&#xff0c;确实是mysqld进程占据了所有资源。2 查看error日志&#xff0c;无任何异常3 show eninge innodb status\G&#xff0c;没有死锁信…

【分享】Android JNI实例​

【分享】Android JNI实例​ Android的SDK中没有包括JNI的支持&#xff0c;而且对如何支持JNI也没有任何文档说明。不过既然整个Android平台是开源的&#xff0c;我们可以通过Google发布的源代码来找到一些线索&#xff08;比如frameworks/base/media/jni/目录&#xff09;&…

LeetCode MySQL 1098. 小众书籍

文章目录1. 题目2. 解题1. 题目 书籍表 Books&#xff1a; ------------------------- | Column Name | Type | ------------------------- | book_id | int | | name | varchar | | available_from | date | ------------------------- book_…

mysql linux 还原_linux下java还原mysql数据库

pageEncoding"UTF-8" import"java.lang.*,java.io.*,java.util.*"%>Runtime runtime Runtime.getRuntime();// 将/data/backup.sql 还原到 192.168.0.96 上的demo库 账号密码为 root rootString restore "mysql -u root -proot -h 192.168.0.96 …

解决eclipse无法解析导入org.eclipse.swt库

右键点击项目 -> Build Path -> Config Build Path...然后选择Libraries标签页&#xff0c;点击Add External JARs...再到你的eclipse安装目录下的plugins目录下寻找 org.eclipse.swt.win32.win32.x86_XXXXX.jar&#xff08;XXXXX是版本号&#xff09;。 我的对应库是org…

LeetCode MySQL 1107. 每日新用户统计

文章目录1. 题目2. 解题1. 题目 Traffic 表&#xff1a; ------------------------ | Column Name | Type | ------------------------ | user_id | int | | activity | enum | | activity_date | date | ------------------------ 该表没有主键&a…

mysql gui 有哪些_推荐五款较好的MySQLGUI工具

大多数数据库都是由两个截然不同的部分组成的&#xff1a;后端(存储数据的地方)和前端(一个用于连接数据组件的用户界面)。这种架构可以大多数数据库都是由两个截然不同的部分组成的&#xff1a;后端(存储数据的地方)和前端(一个用于连接数据组件的用户界面)。这种架构可以把用…

共享打印机的方法

共享打印机的方法 1、在计算机上连接好打印机&#xff0c;并安装好打印机的驱动&#xff0c;测试是否在本机上可以打印 &#xff1b; 2、在打印服务器上&#xff0c;共享该打印机&#xff1b; 3、保证打印服务器上的共享资源可以正常地访问&#xff1b; 4、在PC机运行“\\打印服…

LeetCode MySQL 1149. 文章浏览 II

文章目录1. 题目2. 解题1. 题目 Table: Views ------------------------ | Column Name | Type | ------------------------ | article_id | int | | author_id | int | | viewer_id | int | | view_date | date | ---------------------…

调试lua代码

lua没有调试器&#xff0c;但是它提供了很强的调试功能&#xff08;debug 库&#xff09;&#xff0c; 所以其实一些常用的调试功能都能很方便的使用&#xff0c;就像使用一个命令行调试器一样&#xff1a; 【例子程序】 -- debug.luagvar1 100 function foo()local var2 10f…

php增加mysql用户_mysql 增加用户

1. 新增用户 Sql代码 mysql insert into mysql. user (Host, User , Password ) values ( localhost , lionbule , password ( hello1234 )); mysqlflush privileges ; 或者 CREATEUSER usernamehost IDENTIFIEDBYpassword; 例子:CREATEUSERdoglo1. 新增用户Sql代码mysql>i…