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中提示已过期的方法,不得不感叹时光流水,对以前曾花了时间和精力的东西做个整理和备忘吧,同时希望给有需要的人带来帮助。

目录
相关文章
|
3月前
|
API
【Azure 媒体服务】Media Service的编码示例 -- 创建缩略图子画面的.NET代码调试问题
【Azure 媒体服务】Media Service的编码示例 -- 创建缩略图子画面的.NET代码调试问题
|
18天前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
16 1
|
27天前
|
前端开发 JavaScript C#
CodeMaid:一款基于.NET开发的Visual Studio代码简化和整理实用插件
CodeMaid:一款基于.NET开发的Visual Studio代码简化和整理实用插件
|
3月前
|
Kubernetes 监控 Devops
【独家揭秘】.NET项目中的DevOps实践:从代码提交到生产部署,你不知道的那些事!
【8月更文挑战第28天】.NET 项目中的 DevOps 实践贯穿代码提交到生产部署全流程,涵盖健壮的源代码管理、GitFlow 工作流、持续集成与部署、容器化及监控日志记录。通过 Git、CI/CD 工具、Kubernetes 及日志框架的最佳实践应用,显著提升软件开发效率与质量。本文通过具体示例,助力开发者构建高效可靠的 DevOps 流程,确保项目成功交付。
73 0
|
3月前
|
XML 开发框架 .NET
.NET框架:软件开发领域的瑞士军刀,如何让初学者变身代码艺术家——从基础架构到独特优势,一篇不可错过的深度解读。
【8月更文挑战第28天】.NET框架是由微软推出的统一开发平台,支持多种编程语言,简化应用程序的开发与部署。其核心组件包括公共语言运行库(CLR)和类库(FCL)。CLR负责内存管理、线程管理和异常处理等任务,确保代码稳定运行;FCL则提供了丰富的类和接口,涵盖网络、数据访问、安全性等多个领域,提高开发效率。此外,.NET框架还支持跨语言互操作,允许开发者使用C#、VB.NET等语言编写代码并无缝集成。这一框架凭借其强大的功能和广泛的社区支持,已成为软件开发领域的重要工具,适合初学者深入学习以奠定职业生涯基础。
99 1
|
3月前
|
API
【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret)
【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret)
|
3月前
|
开发框架 JSON .NET
ASP.NET Core 标识(Identity)框架系列(三):在 ASP.NET Core Web API 项目中使用标识(Identity)框架进行身份验证
ASP.NET Core 标识(Identity)框架系列(三):在 ASP.NET Core Web API 项目中使用标识(Identity)框架进行身份验证
|
3月前
|
微服务 API Java
微服务架构大揭秘!Play Framework如何助力构建松耦合系统?一场技术革命即将上演!
【8月更文挑战第31天】互联网技术飞速发展,微服务架构成为企业级应用主流。微服务将单一应用拆分成多个小服务,通过轻量级通信机制交互。高性能Java Web框架Play Framework具备轻量级、易扩展特性,适合构建微服务。本文探讨使用Play Framework构建松耦合微服务系统的方法。Play采用响应式编程模型,支持模块化开发,提供丰富生态系统,便于快速构建功能完善的微服务。
47 0
|
3月前
|
SQL 开发框架 .NET
代码更简洁,开发更高效:从零开始使用Entity Framework Core与传统ADO.NET构建数据持久化层的比较
【8月更文挑战第31天】在.NET平台上开发数据驱动应用时,选择合适的ORM框架至关重要。本文通过对比传统的ADO.NET和现代的Entity Framework Core (EF Core),展示了如何从零开始构建数据持久化层。ADO.NET虽强大灵活,但需要大量手写代码;EF Core则简化了数据访问,支持LINQ查询,自动生成SQL命令,提升开发效率。从创建.NET Core项目、定义数据模型、配置`DbContext`到执行数据库操作,EF Core提供了一套流畅的API,使数据持久化层的构建变得简单直接。
34 0
|
3月前
|
存储 Linux 网络安全
【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Linux/Linux Container)
【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Linux/Linux Container)