用路由 Route 优化页面地址

系统 1952 0

用路由 Route 优化页面地址

刚看到某人发的关于 Route 的文章,就把我今天下午发到团队的一封邮件转发过吧。

全文如下,有修改:

优化地址无非就两个选择 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 。

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

我的实现:

RouteItemBase.cs

      using System;
    
      using System.Collections.Generic;
    
      using System.Linq;
    
      using System.Text;
    
      using System.Web.Routing;
    
    
      namespace XXX.Frameworks.Route {
    
          /// <summary>
    
          /// 
    
          /// </summary>
    
          public abstract class RouteItemBase {
    
    
              /// <summary>
    
              /// 路由名称,必须唯一
    
              /// </summary>
    
              public abstract string RouteName {
    
                  get;
    
              }
    
    
              /// <summary>
    
              /// 路由地址
    
              /// </summary>
    
              public abstract string RouteUrl {
    
                  get;
    
              }
    
    
              /// <summary>
    
              /// 物理地址
    
              /// </summary>
    
              public abstract string PhyicalUrl {
    
                  get;
    
              }
    
    
              /// <summary>
    
              /// 路由的默认值
    
              /// </summary>
    
              public abstract RouteValueDictionary Default {
    
                  get;
    
              }
    
    
              /// <summary>
    
              /// 约束
    
              /// </summary>
    
              public abstract RouteValueDictionary Constraint {
    
                  get;
    
              }
    
    
              /// <summary>
    
              /// 
    
              /// </summary>
    
              /// <param name="routes"></param>
    
              /// <param name="item"></param>
    
              public static void Map(RouteCollection routes, RouteItemBase item) {
    
                  routes.MapPageRoute(item.RouteName , item.RouteUrl, item.PhyicalUrl, false, item.Default, item.Constraint);
    
              }
    
    
              /// <summary>
    
              /// 
    
              /// </summary>
    
              /// <returns></returns>
    
              public abstract object Format();
    
          }
    
      }
    

HotelInfoRoute

      using System;
    
      using System.Collections.Generic;
    
      using System.Linq;
    
      using System.Text;
    
      using System.Web.Routing;
    
      using XXX.Frameworks.Const;
    
      using XXX.Frameworks.Route;
    
    
      namespace Hotel.Online.Route {
    
    
          /// <summary>
    
          /// 
    
          /// </summary>
    
          public class HotelInfoRoute : RouteItemBase {
    
    
              public object Hid {
    
                  get;
    
                  set;
    
              }
    
    
              public DateTime Ds {
    
                  get;
    
                  set;
    
              }
    
    
              public DateTime De {
    
                  get;
    
                  set;
    
              }
    
    
       
    
       
    
              #region RouteItemBase
    
              public override string RouteName {
    
                  get {
    
                      return RouteNames.HotelInfo.ToString();
    
                  }
    
              }
    
    
       
    
              public override string RouteUrl {
    
                  get {
    
                      return "HotelInfo/{hid}/{ds}/{de}";
    
                  }
    
              }
    
    
              public override string PhyicalUrl {
    
                  get {
    
                      return "~/HotelInfo.aspx";
    
                  }
    
              }
    
    
              public override RouteValueDictionary Default {
    
                  get {
    
                      return new RouteValueDictionary(new {
    
                          Ds = DateTime.Now.AddDays(1).ToString(DateFormat.Date),
    
                          De = DateTime.Now.AddDays(2).ToString(DateFormat.Date)
    
                      });
    
                  }
    
              }
    
    
              public override RouteValueDictionary Constraint {
    
                  get {
    
                      return new RouteValueDictionary(new {
    
                          Hid = @"\d+",
    
                          Ds = @"\d{4}-\d{2}-\d{2}",
    
                          De = @"\d{4}-\d{2}-\d{2}"
    
                      });
    
                  }
    
              }
    
    
              public override object Format() {
    
                  return new {
    
                      Hid = this.Hid,
    
                      Ds = this.Ds.ToString(DateFormat.Date),
    
                      De = this.De.ToString(DateFormat.Date)
    
                  };
    
              }
    
              #endregion
    
          }
    
      }
    

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

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

Global 里

              void Map(RouteCollection routes) {
    
                  routes.RouteExistingFiles = true;
    
                  routes.Ignore("{resource}.axd/{*pathInfo}");
    
                  routes.Ignore("{*AllRes}", new {
    
                      AllRes = @".*?\.(?!aspx)(.*)"
    
                  });
    
                  
    
                  //RouteItemBase.Map(routes, new HotelInfoRoute());
    
                  RouteHelper.AutoLoadRoutes(routes, typeof(HotelInfoRoute).Assembly);
    
              }
    
    
              void Application_Start(object sender, EventArgs e) {
    
                  Map(RouteTable.Routes);
    

….

….

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

页面里

      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> 做了一个扩展方法.
    
       
    
              public static T ToEntity<T>(this IDictionary<string, object> dict) where T : new() {
    
                  var t = typeof(T);
    
                  var ps = t.GetProperties();
    
                  T tmp = new T();
    
    
                  foreach (var p in ps) {
    
                      if (!p.CanWrite)
    
                          continue;
    
                      //var v = dict.Get(p.Name);
    
                      if (dict.ContainsKey(p.Name)) {
    
                          p.SetValue(tmp, Convert.ChangeType(dict[p.Name], p.PropertyType), null);
    
                      }
    
                  }
    
                  return tmp;
    
              }
    
       
    

然后直接用:

      this.RouteData = this.Page.RouteData.Values.ToEntity<HotelInfoRoute>(); 就把该页面的路由参数提取出来了
    
      var HotelID 直接等于 RouteData.Hid 就是了.
    
       
    
      获取路由后的地址, 被我简化成如下了, 
    
              public static string GetRoutedUrl<T>(T routeData)
    
                  where T : RouteItemBase {
    
                  return RouteTable.Routes.GetVirtualPath(HttpContextHelper.Current.Request.RequestContext, routeData.RouteName.ToString(), new RouteValueDictionary(
    
                      routeData.Format()
    
                      )).VirtualPath;
    
              }
    
       
    
      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和用户不会有任何影响. 为了达到这个效果,我加了一个方法:
    
       
    
              public static void RedirectToRouted(this Page page, string routeName) {
    
                  if (page.RouteData.Route == null) {
    
                      var context = HttpContextHelper.Current;
    
                      var querys = context.Request.QueryString;
    
                      var keys = querys.Cast<string>().ToList();
    
    
                      RouteValueDictionary datas = new RouteValueDictionary();
    
                      keys.ForEach((k) => {
    
                          datas.Add(k, querys[k]);
    
                      });
    
    
                      var vp = RouteTable.Routes.GetVirtualPath(context.Request.RequestContext, routeName, new RouteValueDictionary(datas));
    
                      context.Response.Status = "301 Moved Permanently";
    
                      context.Response.RedirectLocation = vp.VirtualPath;
    
                      context.Response.End();
    
                  }
    
              }
    
       
    
      在需要将老地址转成新地址的页面里,加上:
    
              protected override void OnPreInit(EventArgs e) {
    
                  this.Page.RedirectToRouted( RouteNames.HotelInfo.ToString() );
    
    
                  base.OnPreInit(e);
    
              }
    
分类: C#

用路由 Route 优化页面地址


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论