ASP.NET WebForm 的路由

偷会闲, 看看博客园, 有筒子写了篇: ASP.NET的路由

我翻了翻两前的一份邮件, 是我当时在项目之余的时间研究的,那时还没用MVC,所有项目都是 WebForm 的.

该方案我觉得可行,但是某同志一句: 不是基于底层的路径映射,效率不高, 还是 URLRewrite 好. 我笑尿了. 算鸟,都是往事. 现在想想, 这位同志还是不错的, 和另外一些人一比, 还是很伟岸的(我真心这样认为).

正文

优化地址无非就两个选择 URLRewrite 和 MVC 里的路由(Route)

关于 URLRewrtie 和 Route 的区别,可参考:

http://www.infoq.com/cn/news/2008/11/urlrewriting

从 .NET 3.5 SP1 起, 微软把 MVC 路由单独抽出来,放到 System.Web.Routing 下, WebForm 程序从此可以用上路由了.

.NET 4 对路由做了改进, 使用起来很简单.. 我们的项目都是 .NET 4 的, SEO 以后肯定是要做的, 所以我试着对 Hotel.Online 做了一下路由.

现在把使用过程中的注意事项说一下.

1, Route 使URL 的层次目录改变了, 原来是 HotelInfo.aspx , 路由后,很可能是 HotelInfo/45956/2011-06-21 这一小小的改变,却带来了大的影响.

A, PostBack : 原来的 Form action = “HotelInfo.aspx” , 路由后变成 action=”2011-06-21” 了, PostBack 当然是不对的. 要避免这个问题,请在代码里加上这句:

this.Form.Action = Page.ResolveUrl("~/HotelInfo.aspx");

B, script 标签的问题 : 在页面里 link 标签的地址(href)会自动转换, 但是 script 标签的地址(src)却不会自动转换, 解决这个问题,有两个办法

l A, base , <base href=”/xxx” /> 如 <base href=”/xxx” /><script src=”main.js”></script> 就会自动指向 /xxx/main.js , 这个需要注意先后顺序, 如果 script 出现在 base 前, 则没有这个效果.

l 使用绝对地址. 为了简化, 和 ResourceSite 的处理方式一样, 我在 BasePage 里将 ~ 符号替换成了 http://xxx.xxx.xxxx/xxx , 所以, 只要继承 BasePage 的页面, 都可以直接写 <script src=”~/main.js”></script>

C, A标签的问题, 我看HTML代码里有很多 <a href=”###” onclick=”…” 的写法, 在不加 base 之前, ### 指向当前页, 但是加了 base 后, ### 就指向 base 设定的地址了. 为了避免这种情况, 应使用 href=”javascript:void()” onclick=”…” 或 href=”javascript:doSomething();return false;” 或干脆就不用 A 标签.

2, 使用Route 后访问地址从 HotelInfo.aspx?hid=45956&ds=2011-06-21&de=2011-06-25 变成了 HotelInfo/45956/2011-06-21/2011-06-25 , 使用 Request.QueryString 取不出 hid , ds, de 这些URL 参数了, 因为 hid, ds, de 就不是以 url 参数形式出现的. 要获取这些值, 就要使用另外一个东西: Page.RouteData.Values[key] 了, 这个 key 不区分大小写,和 QueryString 一样.

3, 脚本里的 POST / GET , 原来的页在都在同一个目录层次, 所以直接

var form = document.createElement("FORM");
form.action = "HotelList.aspx";
是没有问题的, 能找到 HotelList.aspx 这个地址, 但是现在目录层次改变了, 在这样写就会找不到地址, 就如第一点的 A 里所描述的. 要解决这个问题, 要使用 base 或绝对地址.

以上是我在对 Hotel.Online 做路由遇到的问题.

下面说说使用.

1, WebApplication 或 网站要是 .NET Framework 4 (也可以用 3.5 , 但是具体有什么不同,我没有去了解, 下面的示例代码是针对 4 的)

2, 引用 System.Web.Routing 这个命名空间.

3, 在网站启动的时候,注册路由. 一般是放到 Global 的 Application_Start 里. .NET 4 里,允许用另外的方法: PreApplicationStartMethod , 这个东西我以应用到 VMaster 里, 可参考 SharedMaster.Offline. VMasterInit

4, 注册路由是通过 RouteTable.Routes. MapPageRoute 或 Add 方法. MapPageRoute 是对 Add 方法的简化操作.

我的实现:

RouteItemBase.cs

 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Web.Routing;
6
7 namespace XXX.Frameworks.Route {
8 /// <summary>
9 ///
10 /// </summary>
11 public abstract class RouteItemBase {
12
13 /// <summary>
14 /// 路由名称,必须唯一
15 /// </summary>
16 public abstract string RouteName {
17 get;
18 }
19
20 /// <summary>
21 /// 路由地址
22 /// </summary>
23 public abstract string RouteUrl {
24 get;
25 }
26
27 /// <summary>
28 /// 物理地址
29 /// </summary>
30 public abstract string PhyicalUrl {
31 get;
32 }
33
34 /// <summary>
35 /// 路由的默认值
36 /// </summary>
37 public abstract RouteValueDictionary Default {
38 get;
39 }
40
41 /// <summary>
42 /// 约束
43 /// </summary>
44 public abstract RouteValueDictionary Constraint {
45 get;
46 }
47
48 /// <summary>
49 ///
50 /// </summary>
51 /// <param name="routes"></param>
52 /// <param name="item"></param>
53 public static void Map(RouteCollection routes, RouteItemBase item) {
54 routes.MapPageRoute(item.RouteName , item.RouteUrl, item.PhyicalUrl, false, item.Default, item.Constraint);
55 }
56
57 /// <summary>
58 ///
59 /// </summary>
60 /// <returns></returns>
61 public abstract object Format();
62 }
63 }

HotelInfoRoute

 1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Web.Routing;
6 using XXX.Frameworks.Const;
7 using XXX.Frameworks.Route;
8
9 namespace Hotel.Online.Route {
10
11 /// <summary>
12 ///
13 /// </summary>
14 public class HotelInfoRoute : RouteItemBase {
15
16 public object Hid {
17 get;
18 set;
19 }
20
21 public DateTime Ds {
22 get;
23 set;
24 }
25
26 public DateTime De {
27 get;
28 set;
29 }
30
31
32
33 #region RouteItemBase
34 public override string RouteName {
35 get {
36 return RouteNames.HotelInfo.ToString();
37 }
38 }
39
40
41 public override string RouteUrl {
42 get {
43 return "HotelInfo/{hid}/{ds}/{de}";
44 }
45 }
46
47 public override string PhyicalUrl {
48 get {
49 return "~/HotelInfo.aspx";
50 }
51 }
52
53 public override RouteValueDictionary Default {
54 get {
55 return new RouteValueDictionary(new {
56 Ds = DateTime.Now.AddDays(1).ToString(DateFormat.Date),
57 De = DateTime.Now.AddDays(2).ToString(DateFormat.Date)
58 });
59 }
60 }
61
62 public override RouteValueDictionary Constraint {
63 get {
64 return new RouteValueDictionary(new {
65 Hid = @"\d+",
66 Ds = @"\d{4}-\d{2}-\d{2}",
67 De = @"\d{4}-\d{2}-\d{2}"
68 });
69 }
70 }
71
72 public override object Format() {
73 return new {
74 Hid = this.Hid,
75 Ds = this.Ds.ToString(DateFormat.Date),
76 De = this.De.ToString(DateFormat.Date)
77 };
78 }
79 #endregion
80 }
81 }

注意 HotelInfoRoute 的 RouteUrl , HotelInfo/{hid}/{ds}/{de} Hid, ds ,de 即路由的参数, 不区分大小写.

为了编程方便( 强类型 ), 我同时在这个类里定义了 Hid, Ds , De 几个属性(注意:和路由参数完全一样, 不区分大小写), 这几个属性会在页面获取 Route Url 和获取路由参数里发挥作用,会面会说.

Global 里

RouteHelper.AutoLoadRoutes 是用来自动发现某个 Assembly 下所有继承自 RouteItemBase 的类, 并自动注册到当前的路由表中, 这样,就不用每加个路由,都要手动修改 Global了.

 1 void Map(RouteCollection routes) {
2 routes.RouteExistingFiles = true;
3 routes.Ignore("{resource}.axd/{*pathInfo}");
4 routes.Ignore("{*AllRes}", new {
5 AllRes = @".*?\.(?!aspx)(.*)"
6 });
7
8 //RouteItemBase.Map(routes, new HotelInfoRoute());
9 RouteHelper.AutoLoadRoutes(routes, typeof(HotelInfoRoute).Assembly);
10 }
11
12 void Application_Start(object sender, EventArgs e) {
13 Map(RouteTable.Routes);
14 ….
15
16 ….

页面里

var routeData = this.Page.RouteData.Values;
var HotelID = (routeData["hid"] ?? "").ToString().ToInt(-110),

这样写,有些隐患, 假如路由参数 {hid} 变成了 {hotelID} 了, 而你在页面里还在傻傻的用 hid , 当然是取不到值的, 怎么办呢? 用强类型可以很好的避免这个问题. 怎么用?

Page.RouteData.Values 是一个 RouteValueDictionary 类型, 继承自 IDictionary<string, object> , 我针对 IDctionary<string, object> 做了一个扩展方法.

 1 public static T ToEntity<T>(this IDictionary<string, object> dict) where T : new() {
2 var t = typeof(T);
3 var ps = t.GetProperties();
4 T tmp = new T();
5
6 foreach (var p in ps) {
7 if (!p.CanWrite)
8 continue;
9 //var v = dict.Get(p.Name);
10 if (dict.ContainsKey(p.Name)) {
11 p.SetValue(tmp, Convert.ChangeType(dict[p.Name], p.PropertyType), null);
12 }
13 }
14 return tmp;
15 }

然后直接用:

this.RouteData = this.Page.RouteData.Values.ToEntity<HotelInfoRoute>(); 就把该页面的路由参数提取出来了
var HotelID 直接等于 RouteData.Hid 就是了.

获取路由后的地址, 被我简化成如下了,

1 public static string GetRoutedUrl<T>(T routeData)
2 where T : RouteItemBase {
3 return RouteTable.Routes.GetVirtualPath(HttpContextHelper.Current.Request.RequestContext, routeData.RouteName.ToString(), new RouteValueDictionary(
4 routeData.Format()
5 )).VirtualPath;
6 }

HttpContextHelper 的定义在 XXX.Frameworks.Extends.HttpContextHelper 中定义, 为什么不用 HttpContext? 因为单元测试的时候会有问题.

return RouteHelper.GetRoutedUrl<HotelInfoRoute>(new HotelInfoRoute() {
Hid = hotelID.ToString(),
Ds = this.BeginDate,
De = this.EndDate
});

还是强类型, 不怕改.

将以有地址重定向到新地址
HotelInfo.aspx 这个地址肯定以经被搜索引擎收录, 现在如果弃用这样的地址, 对SEO和以有用户来说, 是最不希望出现的.
如果把老地址301 到新地址,对SEO和用户不会有任何影响. 为了达到这个效果,我加了一个方法:

 1 public static void RedirectToRouted(this Page page, string routeName) {
2 if (page.RouteData.Route == null) {
3 var context = HttpContextHelper.Current;
4 var querys = context.Request.QueryString;
5 var keys = querys.Cast<string>().ToList();
6
7 RouteValueDictionary datas = new RouteValueDictionary();
8 keys.ForEach((k) => {
9 datas.Add(k, querys[k]);
10 });
11
12 var vp = RouteTable.Routes.GetVirtualPath(context.Request.RequestContext, routeName, new RouteValueDictionary(datas));
13 context.Response.Status = "301 Moved Permanently";
14 context.Response.RedirectLocation = vp.VirtualPath;
15 context.Response.End();
16 }
17 }

在需要将老地址转成新地址的页面里,加上:

1 protected override void OnPreInit(EventArgs e) {
2 this.Page.RedirectToRouted( RouteNames.HotelInfo.ToString() );
3
4 base.OnPreInit(e);
5 }
 
 
 
标签: RouteURLRewrtie

ASP.NET WebForm 的路由的更多相关文章

  1. 一、ASP.NET MVC 路由(一)--- ASP.NET WebForm路由模拟

    ASP.NET WebForm 应用,用户请求的是物理文件,其中包括静态页面和动态页面,在Url中的显示都是服务器中一个物理文件的相对路径.但是ASP.NET MVC就不同了,用户请求的是Contro ...

  2. ASP.NET WebForm路由模拟

    一.ASP.NET MVC 路由(一)--- ASP.NET WebForm路由模拟 2014-11-08 11:49 by 郝喜路, 232 阅读, 0 评论, 收藏, 编辑 ASP.NET Web ...

  3. ASP.NET WebForms MapPageRoute 路由配置

    MapPageRoute 应该是 ASP.NET 4.0 中的东西,但现在我是第一次使用它,使用场景是:MVC 混合使用 WebForm,然后对 WebForm 进行路由配置,当然也可以使用 ISAP ...

  4. 【深入ASP.NET原理系列】--Asp.Net Mvc和Asp.Net WebForm共用一套ASP.NET请求管道

    .NET FrameWork4在系统全局配置文件(如在如下目录中C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config) 中添加了一个名字叫Url ...

  5. IIS 7.5 + asp.net MVC4 设置路由处理URL请求

    使用asp.net MVC4开发的网站,在本地的VS012环境下运行,一切正常.但当发布到Windows 2008 R2(IIS7.5 + Framework4.5)上时,访问相关网页时,出现有下面的 ...

  6. 解析ASP.NET WebForm和Mvc开发的区别

    因为以前主要是做WebFrom开发,对MVC开发并没有太深入的了解.自从来到创新工场的新团队后,用的技术都是自己以前没有接触过的,比如:MVC 和EF还有就是WCF,压力一直很大.在很多问题都是不清楚 ...

  7. ASP.NET MVC的路由

    好久没写博文了,感觉最近好像少了点动力.唉!这回就看看这个MVC的路由. 说这个路由机制其实不是MVC里面特有的,ASP.NET里面本身就有的,只不过在WebForm里面一般比较少用,而在MVC里就是 ...

  8. ASP.NET Web API 路由

    路由系统是请求消息进入ASP.NET Web API消息处理管道的第一道屏障,其根本目的是利用注册的路由表(RouteTable)对请求的URI进行解析以确定目标HttpController和Acti ...

  9. ASP.NET WebForm与ASP.NET MVC的不同点

    ASP.NET WebForm ASP.NET MVC ASP.NET Web Form 遵循传统的事件驱动开发模型 ASP.NET MVC是轻量级的遵循MVC模式的请求处理响应的基本开发模型 ASP ...

随机推荐

  1. C#将Word,Excel与Html,PDF互转

    public class OfficeHelper { /// <summary> /// word转成html /// </summary> /// <param na ...

  2. Python学习笔记21:数据库操作(sqlite3)

    Python自带一个轻量级的关系型数据库SQLite.这一数据库使用SQL语言. SQLite作为后端数据库,能够搭配Python建站点,或者制作有数据存储需求的工具. SQLite还在其他领域有广泛 ...

  3. mysql_SQL_按照日统计微博数

    主要备忘: DATE_FORMAT 函数 1:微博对比图(按日统计) SELECT DATE_FORMAT(tw.article_publish_time, '%Y-%m-%d'),count(pag ...

  4. CocoaPods 建立私有仓库

    CocoaPods是iOS,Mac下优秀的第三方包管理工具,类似于java的maven,给我们项目管理带来了极大的方便. [个人或公司在开发过程中,会积累很多可以复用的代码包,有些我们不想开源,又想像 ...

  5. 【百度地图API】如何在地图上添加标注?——另有:坐标拾取工具+打车费用接口介绍

    原文:[百度地图API]如何在地图上添加标注?--另有:坐标拾取工具+打车费用接口介绍 摘要: 在这篇文章中,你将学会,如何利用百度地图API进行标注.如何使用API新增的打车费用接口. ------ ...

  6. POJ 2187: Beauty Contest(旋转卡)

    id=2187">Beauty Contest Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 27218   ...

  7. IOS程序创建view

    在IOS程序中创建view有六种方式 首先创建一个GLViewController类,继承UIViewController. 然后进入GLAppDelegate.m,在- (BOOL)applicat ...

  8. php设计模式(二):结构模式

    上一篇我们介绍了设计模式的特性并且详细讲解了4种创建型模式,创建型模式是负责如何产生对象实例的,现在我们继续来给大家介绍结构型模式. 一.什么是结构型模式? 结构型模式是解析类和对象的内部结构和外部组 ...

  9. 十六进制颜色与Color对象的互相转换[C#]

    原文:十六进制颜色与Color对象的互相转换[C#] C#十六进制颜色与Color对象的互相转换 把十六进制颜色转化为color对象ColorTranslator.FromHtml("#FF ...

  10. OCP-1Z0-051-题目解析-第7题

    7. Which two  statements are true regarding the USING and ON clauses in table joins? (Choose two.) A ...