代码改变世界

大叔手记(15):在ASP.NET 4.0 Web form上使用Routing优化URL

2011-12-26 08:52  汤姆大叔  阅读(6156)  评论(24编辑  收藏

前言

大家都知道MVC里利用Routing的特性将地址映射到ControllerAction上,其实因为本身Routing是.Net 4.0内置的特性了,所以Web form上其实也可以适用的,今天我们就来看看如何做一下URL地址的优化,目的是将http://localhost/Customer.aspx?Id = 1优化成http://localhost/Custome/1的形式。

正文

首先,建立一个空的ASP.NET 4.0 Web form项目,建立Global.asax文件,在Glolal类里,我们添加如下代码:

namespace EasyURL
{
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RegisterRoutes(RouteTable.Routes);
}

public static void RegisterRoutes(RouteCollection routeCollection)
{
//routeCollection.MapPageRoute("RouteForCustomer", "Customer/{Id}", "~/Customer.aspx")

// 可以使用上面的,但是最好使用下面的,这样可以限制Id为数字
routeCollection.MapPageRoute("RouteForCustomer", "Customer/{Id}", "~/Customer.aspx", true, null, new RouteValueDictionary(new { Id = "\\d+" }));
}
}
}

然后添加一个Customer.aspx文件,代码非常简单,如下:

public partial class Customer : Page
{
protected void Page_Load(object sender, EventArgs e)
{
string id = Page.RouteData.Values["Id"].ToString();
Response.Write("<h1>Customer详细页</h1>");
Response.Write(string.Format("CustomerID : {0}", id));
}
}

注意我们是用Page.RouteData.Values["Id"]来取的值。

这样,当我们在访问http://localhost/customer/1的时候,就显示了我们预想的页面:

Customer详细页

CustomerID : 1

延伸一

其实上面的效果已经很简单的实现了,但是我们发现我们必须要用Page.RouteData.Values["Id"]这种形式来取值,而不是我们平时所用的Page.QueryString["Id"]来取,那我们能否做到这一点呢?

我们知道,在PageLoad之前Request的值都有初始化好的,所以如果我们要使用这种方式的时候,必须在这个周期之前将RouteData.Values的值都加到QueryString里,好,我们来试试,先建立一个PageBase基类(后面所有的页面都要继承此类),代码如下:

public abstract class PageBase : System.Web.UI.Page
{
protected override void OnInitComplete(EventArgs e)
{
base.OnInitComplete(e);

Page.RouteData.Values.Keys.ToList().ForEach(key =>
{
Request.QueryString.Add(key, Page.RouteData.Values[key].ToString());
});
}
}

运行之后,我们发现出了黄页:

Exception Details: System.NotSupportedException: Collection is read-only.

Source Error:

Line 20:             Page.RouteData.Values.Keys.ToList().ForEach(key =>
Line 21:             {
Line 22:                 Request.QueryString.Add(key, Page.RouteData.Values[key].ToString());
Line 23:             });
Line 24:         }

原来是QueryString这个集合是只读的,通过老赵的文章一个较完整的关键字过滤解决方案我们来改写一下代码:

public static class GlobalValues
{
public static PropertyInfo NameObjectCollectionBaseIsReadOnly;

static GlobalValues()
{
Type type = typeof(NameObjectCollectionBase);
NameObjectCollectionBaseIsReadOnly = type.GetProperty(
"IsReadOnly",
BindingFlags.Instance | BindingFlags.NonPublic);
}
}

public abstract class PageBase : System.Web.UI.Page
{
protected override void OnInitComplete(EventArgs e)
{
base.OnInitComplete(e);

// 将集合改成可写的
GlobalValues.NameObjectCollectionBaseIsReadOnly.SetValue(Request.QueryString, false, null);

Page.RouteData.Values.Keys.ToList().ForEach(key =>
{
// 添加RouteData.Values的name/value对添加到QueryString集合里
Request.QueryString.Add(key, Page.RouteData.Values[key].ToString());
});
}
}

然后使用我们平时用的代码:

protected void Page_Load(object sender, EventArgs e)
{
//string id = Page.RouteData.Values["Id"].ToString();
string id = Request.QueryString["Id"];
Response.Write("<h1>Customer详细页</h1>");
Response.Write(string.Format("CustomerID : {0}", id));
}

运行一下,完美,啊哈!OK,在aspx里添加一个form表单,点击一个button几次试试,完蛋了,浏览器地址栏的地址变成了:

http://localhost/Customer/1?Id=1&Id=1&Id=1

原来是,由于我们每次都往QueryString里添加Id这个key,结果每次回发的时候就附加到URL上了,得想办法在页面结束以后从QueryString里删除这个key,由于在OnUnload周期Request/Response已经被清空了,所以我们要在之前的周期内处理,代码如下:

protected override void OnPreRenderComplete(EventArgs e)
{
Page.RouteData.Values.Keys.ToList().ForEach(key =>
{
Request.QueryString.Remove(key);
});

base.OnPreRenderComplete(e);
}

照例运行,OK,完美,再试几下其它的case,也没什么问题。

注:由于好久不用web form做项目了,所以不太确信上述代码用的OnInitComplete/OnPreRenderComplete周期是否是合理的周期,目前运行起来是没有问题的,有时间再详细研究一下。

延伸二

延伸一的代码,颇为复杂,而且也不知道有没有副作用,其实实际项目中,一般我们都封装自己的GetParameter方法,所以其实我们可以这样用,相对就简单多了:

namespace EasyURL
{
public abstract class PageBase : System.Web.UI.Page
{
public string GetQueryString(string key, string defaultValue = "")
{
if (Page.RouteData.Values.Keys.Contains(key))
return Page.RouteData.Values[key].ToString();
else if (string.IsNullOrWhiteSpace(Request.QueryString[key]))
return Request.QueryString[key];
else
return defaultValue;
}
}

public partial class Customer : PageBase
{
protected void Page_Load(object sender, EventArgs e)
{
string id = this.GetQueryString("id");
Response.Write("<h1>Customer详细页</h1>");
Response.Write(string.Format("CustomerID : {0}", id));
}
}
}

运行,没问题,而且也不存在在URL上再次附加?Id=1的问题了。

同步与推荐

本文已同步至目录索引:《大叔手记全集》

大叔手记:旨在记录日常工作中的各种小技巧与资料(包括但不限于技术),如对你有用,请推荐一把,给大叔写作的动力。