前两天看了老赵的重提URL Rewrite(4):不同级别URL Rewrite的一些细节与特点,并且有人留言问怎样自动输出重写后的URL。我看到之后非常有想法,事实上在我以前研究URL重写的时候也想过这个问题,如果有这种方法的话,将会给我们开发带来大大的方便。正好不久前我刚整理了一下HTML页面生成的方法,里面的Response.Filter给了我很大的灵感。好的,说干就干,花了昨天半天时间,捣鼓出来一个简单的例子,所以也就有了本文。
一、思考
我们在项目中使用URL重写技术,来达到对搜索引擎的友好、人性化URL、清晰的组织架构、新老系统整合等等效果,但同时也给我们的开发带来了一些不便之处。比如VS就会提醒您这个URL并不存在,也给我们的调试工作加大了难度。如果是老系统升级使用URL重写功能的话,那整个URL替换一下也是个不小的工程。如果我们能在HTML产生之后输出之前做些小动作的话,也许能够满足我们的需求。
二、准备
- 生成好的HTML。这点简单,使用Response.Filter就可以了。如果对Response.Filter还不了解到话,请看这,本文就不多说了。
- 替换。这点也不难,但需要考虑的是怎么样达到最优化,还请大家来讨论,下面就来说说我的实现。
三、实现
- URL替换规则
要使用URL重写,那我们在Config里肯定对重写规则有所定义。简单的方法就是定义四条,两条对应URL重写,两条对应URL替换,但好像比较烦哦,正是我们程序员不屑做的事。而且我们这里的URL替换规则正好是前面的这个重写规则的反向,那就用程序来实现吧。我的实现代码:
//Get URL rewrite configs
UrlsCollection rules = UrlsConfig.GetConfig().Urls;
string[] virtualUrl = new string[rules.Count];
string[] destinationUrl = new string[rules.Count];
for (int j = 0; j < rules.Count; j++)
{
//Remove ~/
string lookFor = rules[j].VirtualUrl.Substring(2);
string sendTo = rules[j].DestinationUrl.Substring(2);
//Replace ? with \\? for Regex
//Replace & which is used in XML files
sendTo = sendTo.Replace("?", "\\?").Replace("&", "&");
string[] array_lookFor = lookFor.Split(new string[] { "(.*)" }, StringSplitOptions.None);
string[] array_sendTo = sendTo.Split('$');
//Prepare new lookFor and sendTo
for (int i = 0; i < array_lookFor.Length - 1; i++)
{
string initial;
if (array_sendTo[i + 1].Length == 1)
{
initial = array_sendTo[i + 1];
array_sendTo[i + 1] = "";
}
else
{
initial = array_sendTo[i + 1].Remove(1);
array_sendTo[i + 1] = array_sendTo[i + 1].Substring(1, array_sendTo[i + 1].Length - 1);
}
array_lookFor[i + 1] = initial + array_lookFor[i + 1];
}
//Join with new Regex characters
virtualUrl[j] = "^" + string.Join("(.*)", array_sendTo) + "$";
destinationUrl[j] = string.Join("$", array_lookFor);
}
我做了张图,时间紧,比较丑,但足以说明事情了。
做了如上图的替换之后,在用新的分隔符链接起来,就变成了我们所需要的正则表达式了。
- 开始替换
有了正则表达式,替换就简单了。我的实现代码:
string[] body = Encoding.UTF8.GetString(data).Split(new string[] { "href=" }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < body.Length; i++)
{
string s = body[i];
for (int j = 0; j < virtualUrl.Length; j++)
{
Regex re = new Regex(virtualUrl[j], RegexOptions.IgnoreCase);
//<a href=".....">
if (s.StartsWith("\""))
{
string href = s.Substring(1, s.Substring(1).IndexOf('"'));
if (re.IsMatch(href))
{
string newhref = re.Replace(href, destinationUrl[j]);
body[i] = "\"" + path + newhref + s.Substring(s.Substring(1).IndexOf('"') + 1);
}
}
//<a href='.....'>
else if (s.StartsWith("'"))
{
string href = s.Substring(1, s.Substring(1).IndexOf('\''));
if (re.IsMatch(href))
{
string newhref = re.Replace(href, destinationUrl[j]);
body[i] = "'" + path + newhref + s.Substring(s.Substring(1).IndexOf('\'') + 1);
}
}
//<a href=.....>
else
{
//The operation is not implemented.
}
}
}
首先用"href="来分割一下,然后再来判断URL是由 "" 或 '' 或“什么都没有”来包住URL的,其中第三个比较麻烦,我也就没有具体来实现它。同时我也没有做Trim、去掉换行等操作。
- 输出
替换完了HTML,我们再用"href="组合起来,就可以输出了。
//Get formative HTML
byte[] outdata = Encoding.UTF8.GetBytes(string.Join("href=", body));
//Render to browser
m_sink.Write(outdata, 0, outdata.GetLength(0));
四、小结
至此,我们这个简单的URL自动重写功能就实现了,当然,其中还有很多需要完善和考虑到地方,比如如何处理Javascript中的location.href等等。由于本例纯属“自动重写URL”这种方法的可行性研究,所以使用的都是假设的场景,并没有在实践项目中应用,在此仅作块砖头,有玉的尽管来砸吧。
PS:由于下午要回家过年,所以写的比较匆忙,还请大家见谅。本人第一次发首页帖,因为没有看到过类似的文章,所以就发在了首页上,其实没什么技术性,如有觉得不妥,我立马撤下:)。