ASP.NET的地址重写“.NET研究”(URLRewriter)实现原理及代码示例

简介:   一、概述   访问者输入:http://wu-jian.cnbolgs.com/default.aspx,实际请求和响应的地址却是:http://www.cnblogs.com/wu-jian/default.aspx, 这就是UrlRewrite,除了实现二级域名功能,它在简化用户输入地址、SEO、网站版本迭代更新等多个方面发挥着重要作用。

  一、概述

  访问者输入:http://wu-jian.cnbolgs.com/default.aspx,实际请求和响应的地址却是:http://www.cnblogs.com/wu-jian/default.aspx, 这就是UrlRewrite,除了实现二级域名功能,它在简化用户输入地址、SEO、网站版本迭代更新等多个方面发挥着重要作用。

  微软曾在.net framework 1.1中提供过一个名为URLRewriter的小工具供开发人员轻松实现UrlRewrite,下载地址为:http://download.microsoft.com/download/0/4/6/0463611e-a3f9-490d-a08c-877a83b797cf/MSDNURLRewriting.msi

  本文以URLRewriter为例,在.net framework 2.0的环境下做了小部分优化调整,供大家学习和参考,能力有限,不足之处请大家及时指出。本文假设读者对URLRewriter、ASP.net的 Http管线有一定了解,否则请查阅相关资料。

  二、配置

  URLRewriter在web.config里通过自定义配置结合正则表达式来实现URL重写。

  自定义节点的声明:

 
 
< configSections >
< section name ="RewriterConfig"
type
="PaoTiao.PTRewriter.Config.RewriterConfigSerializerSectionHandler, PaoTiao.PTRewriter" />
</ configSections >

  自定义节点配置项:

 
 
< RewriterConfig >
< Rules >
< RewriterRule >
< LookFor > ^http://([a-zA-Z0-9]{4,16}).cnblogs.com/default.aspx$ </ LookFor >
< SendTo > /$1/default.aspx </ SendTo >
</ RewriterRule >

< RewriterRule >
< LookFor > ^http://www.cnblogs.com/([a-zA-Z0-9]{4,16})/$ </ LookFor >
< SendTo > /test/url.aspx?p=$1 </ SendTo >
</ RewriterRule >
</ Rules >
</ RewriterConfig >

  如上我配置了两个规则,以实例说明,第一个可将:http://wu-jian.cnblogs.com 重写到:/wu-jian/default.aspx

  第二个可将:http://www.cnblogs.com/wu-jian 重写到:/test/url.aspx?p=wu-jian

  但微软的URLRewriter LookFor并不支持到域名位置,它只能在根目录之后做文章,截选了它源码DEMO中的一段:

 
 
< RewriterRule >
< LookFor > ~/(\d{4})/(\d{2})/(\d{2})\.aspx </ LookFor >
< SendTo > ~/ShowBlogContent.aspx?year=$1 &amp; month=$2 &amp; day=$3 </ SendTo >
</ RewriterRule >

  可以发现,当需要使用二级域名或自定义级别更高的rewrite时,URLRewriter是不支持的,所以在此我将源代码作了一小部分优化,匹配与重写都使用LookFor和SendTo中的原始表达式,不做任何智能替换与修改。其实很多时候,在微软的产品中都能发现这种“画蛇添足”的影子。

  关于匹配与替换, 其实就是应用了正则表达式中的“反向引用”原理,在我的博客里有代码示例,不熟悉正则的朋友可去了解,此处不作详叙。

  三、源代码分析

  对自定义配置进行访问的类:

 
 
using System;
using System.Configuration;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.XPath;

namespace PaoTiao.PTRewriter.Config
{
/// <summary>
/// 实现IConfigurationSectionHandler接口,以对自定义节点进行访问
/// </summary>
public class RewriterConfigSerializerSectionHandler : IConfigurationSectionHandler
{
/// <summary>
/// 该方法无需主动调用
/// 它在ConfigurationManager.GetSection()被调用时根据改配置节声明中所定义的类名和路径自动实例化配置节处理类
/// </summary>
public object Create( object parent, object configContext, System.Xml.XmlNode section)
{
XmlSerializer ser
= new XmlSerializer( typeof (RewriterConfiguration));
return ser.Deserialize( new XmlNodeReader(section));
}

}
// end class
}

  之前一直写WEB程序,很少用到自定义节点,直到一次写Windows Service用到了app.config,发现要读取自定义节点,就需要实现IConfigurationSectionHandler接口。

 
 
using System;
using System.Web;
using System.Web.Caching;
using System.Configuration;
using System.Xml.Serialization;

namespace PaoTiao.PTRewriter.Config
{
[Serializable()]
[XmlRoot(
" RewriterConfig " )]
public class RewriterConfiguration
{
private Rewrit上海企业网站制作erRuleCollection rules;

/// <summary>
/// 该方法从web.config中读取规则集合,并使用了Cache以避免频繁IO操作
/// </summary>
/// <returns></returns>
public static RewriterConfiguration GetConfig()
{
// 使用缓存
if (HttpContext.Current.Cache[ " RewriterConfig " ] == null )
HttpContext.Current.Cache.Insert(
" RewriterConfig " , ConfigurationManager.GetSection( " RewriterConfig " ));

return (RewriterConfiguration)HttpContext.Current.Cache[ " RewriterConfig " ];
}

public RewriterRuleCollection Rules
{
get { return rules; }
set { rules = value; }
}

}
// end class
}    

  我想使用UrlRewrite的站点绝大部分都是面向公众用户的,面向公众用户就面临着大的流量和并发,谁也不愿意为每个请求去读取一次web.config吧,那么在此处使用Cache是明智之举。另外更换了已过期的ConfigurationSettings.GetConfig()方法为ConfigurationManager.GetSection()方法。

  如下两个类完成类似的Model功能。

 
 
using System;
using System.Collections;

namespace PaoTiao.PTRewriter.Config
{
/// <summary>
/// 规则集合
/// </summary>
[Serializable()]
public class RewriterRuleCollection : CollectionBase
{
/// <summary>
/// 向集合中添加新规则
/// </summary>
/// <param name="r"> RewriterRule对象 </param>
public virtual void Add(RewriterRule r)
{
this .InnerList.Add(r);
}

/// <summary>
/// 获取或设置项
/// </summary>
public RewriterRule this [ int index]
{
get { return (RewriterRule) this .InnerList[index]; }
set { this .InnerList[index] = value; }
}

}
// end class
}    
 
 
using System;

namespace PaoTiao.PTRewriter.Config
{
/// <summary>
/// 重写规则的数据对象
/// </summary>
[Serializable()]
public class RewriterRule
{
private string mLookFor;
private string mSendTo;

/// <summary>
/// 查找规则
/// </summary>
public string LookFor{
get { return this .mLookFor; }
set { this .mLookFor = value; }
上海企业网站设计与制作 }

/// <summary>
/// 重写规则
/// </summary>
public string SendTo{
get { return this .mSendTo; }
set { this .mSendTo = value; }
}

}
// end class
} // end namespace

  使用HttpModule实现地址重写:

 
 
using System;
using System.Web;

namespace PaoTiao.PTRewriter
{
/// <summary>
/// 实现IHttpModule的抽象类
/// </summary>
public abstract class BaseModuleRewriter : IHttpModule
{
public virtual void Dispose() { }

public virtual void Init(HttpApplication app)
{
app.BeginRequest
+= new EventHandler( this .BaseModuleRewriter_BeginRequest);
}

protected virtual void BaseModuleRewriter_BeginRequest( object sender, EventArgs e)
{
HttpApplication app
= (HttpApplication)sender;
Rewrite(app);
}

/// <summary>
/// 地址重写抽象函数
/// </summary>
/// <param name="app"></param>
protected abstract void Rewrite(HttpApplication app);

}
// end class
}

  在Http模块中进行核心逻辑处理,源代码是在AuthorizeRequest事件中,此处我使用了BeginRequest事件。

  对抽象方法Rewrite的实现。大家可以发现URL重写其实就一个核心方法:HttpContext.RewritePath

  看看MSDN中对该方法的描述:指定内部重写路径,并允许请求的 URL 与资源的内部路径不同。

 
 
using System;
using System.Text.RegularExpressions;
using System.Configuration;

using System.IO;

namespace PaoTiao.PTRewriter
{
public class ModuleRewriter : BaseModuleRewriter
{
/// <summary>
/// 地址重写函数
/// </summary>
/// <param name="app"></param>
protected override void Rewrite(System.Web.HttpApplication app)
{
// 开始跟踪日志
app.Context.Trace.Write( " ModuleRewriter " , " Entering ModuleRewriter " );

// 获取规则集合
Config.RewriterRuleCollection rules = Config.RewriterConfiguration.GetConfig().Rules;

for ( int i = 0 ; i < rules.Count; i ++ )
{
string lookFor = rules[i].LookFor;
Regex reg
= new Regex(lookFor, RegexOptions.IgnoreCase);

if (reg.IsMatch(app.Request.Url.ToString()))
{
// 获取目的URL
string sendToUrl = reg.Replace(app.Request.Url.ToString(), rules[i].SendTo);

// 跟踪日志
app.Context.Trace.Write( " ModuleRewriter " , " Rewriting URL to " + sendToUrl);
// 地址重写
app.Context.RewritePath(sendToUrl);

// Temp code for debug
// using (StreamWriter sw = new StreamWriter(@"c:\test\rr.txt", true, System.Text.Encoding.UTF8))
// {
// sw.WriteLine(app.Request.Url.ToString());
// sw.WriteLine("--------------------------------------");
// sw.Clo上海徐汇企业网站设计与制作se();
// }

// 退出for循环
break ;
}
}

// 结束跟踪日志
app.Context.Trace.Write( " 上海闵行企业网站设计与制作> ModuleRewriter " , " Exiting ModuleRewriter " );
}

}
//上海闵行企业网站制作 end class
}

  最后在web.config中注册自定义的Http模块:

 
 
< httpModules >
< add name ="ModuleRewriter" type ="PaoTiao.PTRewriter.ModuleRewriter, PaoTiao.PTRewriter" />
</ httpModules >

  四、应用

  回到前面的示例, http://wu-jian.cnblogs.com --> /wu-jian/default.aspx

  wu-jian所在的位置为域名前缀,或叫二级域名,这就需要在DNS上做一个 *.cnblogs.com 的泛解析。

  第二个示例是将目录解析到某一地址:http://www.cnblogs.com/wu-jian --> /test/url.aspx?p=wu-jian

  很明显,这里的关键点在于怎样让IIS把这种格式的请求交由.net进程来处理,一旦进入.net framwork,我们就能随心所欲了。OK,通过如下的操作过程即可:

  IIS管理-->站点-->属性-->主目录标签-->配置-->通配符应用程序映射-->插入

  1、选择 C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\aspnet_isapi.dll

  2、不勾选“确认文件是否存在”

  五、总结

  关于Url Rewrite的介绍很多,第三方组件也很多,比如isapi rewrite、比如iirf,他们通过IIS底层接口实现所以效率更高,Url Rewriter在.Net进程内实现,也是微软在Framework1.1时代提供的解决方案,其实这篇文章也是多年前写的,只是最近准备换用IIRF,看着那些在.Net Framework4.0中提示已过期的方法,不得不感叹时光流水,对以前曾花了时间和精力的东西做个整理和备忘吧,同时希望给有需要的人带来帮助。

目录
相关文章
|
12天前
|
开发框架 前端开发 JavaScript
盘点72个ASP.NET Core源码Net爱好者不容错过
盘点72个ASP.NET Core源码Net爱好者不容错过
82 0
|
12天前
|
开发框架 .NET
ASP.NET Core NET7 增加session的方法
ASP.NET Core NET7 增加session的方法
44 0
|
7月前
|
存储 开发框架 前端开发
asp.net与asp.net优缺点及示例
asp.net与asp.net优缺点及示例
|
12天前
|
机器学习/深度学习 算法 数据可视化
MATLAB基于深度学习U-net神经网络模型的能谱CT的基物质分解技术研究
MATLAB基于深度学习U-net神经网络模型的能谱CT的基物质分解技术研究
|
12天前
|
开发框架 前端开发 .NET
进入ASP .net mvc的世界
进入ASP .net mvc的世界
34 0
|
12天前
|
开发框架 中间件 .NET
C# .NET面试系列七:ASP.NET Core
## 第一部分:ASP.NET Core #### 1. 如何在 controller 中注入 service? 在.NET中,在ASP.NET Core应用程序中的Controller中注入服务通常使用<u>依赖注入(Dependency Injection)</u>来实现。以下是一些步骤,说明如何在Controller中注入服务: 1、创建服务 首先,确保你已经在应用程序中注册了服务。这通常在Startup.cs文件的ConfigureServices方法中完成。例如: ```c# services.AddScoped<IMyService, MyService>(); //
107 0
|
12天前
|
开发框架 前端开发 .NET
C# .NET面试系列六:ASP.NET MVC
<h2>ASP.NET MVC #### 1. MVC 中的 TempData\ViewBag\ViewData 区别? 在ASP.NET MVC中,TempData、ViewBag 和 ViewData 都是用于在控制器和视图之间传递数据的机制,但它们有一些区别。 <b>TempData:</b> 1、生命周期 ```c# TempData 的生命周期是短暂的,数据只在当前请求和下一次请求之间有效。一旦数据被读取,它就会被标记为已读,下一次请求时就会被清除。 ``` 2、用途 ```c# 主要用于在两个动作之间传递数据,例如在一个动作中设置 TempData,然后在重定向到另
125 5
|
12天前
|
SQL 开发框架 .NET
ASP.NET Web——GridView完整增删改查示例(全篇幅包含sql脚本)大二结业考试必备技能
ASP.NET Web——GridView完整增删改查示例(全篇幅包含sql脚本)大二结业考试必备技能
39 0
|
12天前
|
SQL 开发框架 JavaScript
分享33个ASP.NET电子商务源码和40个ASP.NET控件组件源码,总有一款适合您
分享33个ASP.NET电子商务源码和40个ASP.NET控件组件源码,总有一款适合您
35 0
|
12天前
|
开发框架 安全 搜索推荐
分享105个NET源码ASP源码,总有一款适合您
分享105个NET源码ASP源码,总有一款适合您
31 4