Dottext需要映射全部不存在的文件到blog应用程序,实际上是需要IIS对于该应用下的问不进行处理,而是交给dottext程序处理,而dottext则利用一系列的handler来进行配置,对应不同的文件类型,或者匹配特定的文件,实现整个blog的URL 重写的。
首先,是通过
<httpHandlers>
<addverb="*"path="*.asmx"type="System.Web.Services.Protocols.WebServiceHandlerFactory, System.Web.Services, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"validate="false"/>
<addverb="*"path="Error.aspx"type="System.Web.UI.PageHandlerFactory"/>
<addverb="*"path="*"type="Dottext.Common.UrlManager.UrlReWriteHandlerFactory,Dottext.Common"/>
</httpHandlers>
确保了任何对blog所在应用程序的访问都会被以上3个handler处理,如果是扩展名.asmx的http请求,会被系统缺省的处理程序处理;而对于错误处理(大多数都是转到error.aspx)会转入到系统的缺省aspx处理程序,其他任何请求都会转到Dottext.Common.UrlManager.UrlReWriteHandlerFactory,Dottext.Common 所以我们首先来看看这个处理句柄:
这是一个工厂类型的执行句柄,他自身并不进行处理。而是负责将请求根据不同的类别进行分别派遣,调出不同的处理程序进行执行,而这构成了dottext高效处理整个blog运行的精妙设计部分。
protected virtual HttpHandler[] GetHttpHandlers(HttpContext context)
{
return HandlerConfiguration.Instance().HttpHandlers;//这是个收集
}
是IhttpHandler接口的实现,用于返回处理http请求的全部句柄。而句柄配置在web.config中,所以dottext是这样获得全部handler的。而HandlerConfiguration.Instance()类似我们前面分析的配置处理体系那里的处理过程:
public static HandlerConfiguration Instance()
{
return ((HandlerConfiguration)ConfigurationSettings.GetConfig("HandlerConfiguration"));
}
是从配置文件的xml片断中,获得产生具体的类实例,并经过反序列化后(请看看HandlerConfiguration的属性定义),得到一个句柄的数组,返回给UrlReWriteHandlerFactory的调用函数。具体为:
public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string url, string path)
{
HttpHandler[] items = GetHttpHandlers(context);
if(items != null)
{
int count = items.Length;
string appStr=Dottext.Framework.Util.Globals.RemoveAppFromPath(context.Request.Path,context.Request.ApplicationPath);//得到访问哪一个应用程序的哪一个具体文件
for(int i = 0; i<count; i++)
{
//定向到特定的aspx文件
if(items[i].IsMatch(appStr)) //看看是否匹配系统配置中的者则表达式
{
//throw new Exception();
switch(items[i].HandlerType)
{
case HandlerType.Page://默认是Page
return ProccessHandlerTypePage(items[i],context,requestType,url);
case HandlerType.Direct: HandlerConfiguration.SetControls(context,items[i].BlogControls);
return (IHttpHandler)items[i].Instance();
case HandlerType.Factory: return ((IHttpHandlerFactory)items[i].Instance()).GetHandler(context,requestType,url,path);
default:
throw new Exception("Invalid HandlerType: Unknown");
}
}
}
}
//如果请求的页面不匹配任何一个句柄,就使用ASP.NET的
return PageHandlerFactory.GetHandler(context,requestType,url, path);
}
获得全部web.config指定的handler以及正则表达式后,就进行匹配当前http访问请求的处理分析,如果请求的URL字符串匹配一个Page类型正则表达式(HttpHandler是一个实体类,通过反序列化后获得了type和Pattern属性,如果请求的资源是aspx或者html的,那么会进行正则式判断是否符合句柄的模式,)如果符合,那么同时就知道了句柄的类型,根据HandlerType.Page 、HandlerType.Direct、HandlerType.Factory进行分别处理。
如果是Page类型(某个aspx页面),那么执行:
private IHttpHandler ProccessHandlerTypePage(HttpHandler item, HttpContext context, string requestType, string url)
{
string pagepath = item.FullPageLocation;
if(pagepath == null)
{
pagepath = HandlerConfiguration.Instance().FullPageLocation;
}
HandlerConfiguration.SetControls(context,item.BlogControls);
IHttpHandler myhandler=PageParser.GetCompiledPageInstance(url,pagepath,context);
return myhandler;
}
HandlerConfiguration.Instance()的代码如下:
public static HandlerConfiguration Instance()
{
return ((HandlerConfiguration)ConfigurationSettings.GetConfig("HandlerConfiguration"));
}
同样,这也是配置文件通过反序列化得到一个HandlerConfiguration的实例,HandlerConfiguration的配置节内容在web.config中存在,我们会得到一个defaultPageLocation属性,FullPageLocation如果在属性无法获取的时候就返回defaultPageLocation的值,也就是说,通常我们访问某个目录,不带指定的aspx 的page文件名,就会自动访问defaultPageLocation.的指示的值。
SetControls 是针对部分页面的,就是类似<HttpHandlerpattern="/archive/\d{4}/\d{1,2}\.aspx$"controls="ArchiveMonth.ascx"/>这类page 的,通常是指向一个用户控件,而大家知道用户控件实际上就是一个page。SetControls会把控件加入到当前请求的context重,以便执行期间从context中区的控件。
PageParser对象实际上是asp.net的解释对对象,它将指定的资源编译成程序集,这类似一个普通的物理存在的aspx页面执行机制。大家注意到,返回的是一个IhttpHandler对象,实际上asp.net的任何一个page都应该实现这个接口的,所以此处的逻辑就相当于执行了一个存在的页面。虽然页面可能不存在,但是通过配置指定最后得到了一个IhttpHandler对象处理了用户的http请求,这是Page类型的处理过程简要描述。
第二类HandlerType是Direct ,有以下的http资源请求定向到这类Handler:
<HttpHandlerpattern="(\.config|\.asax|\.ascx|\.config|\.cs|\.vb|\.vbproj|\.asp|\.licx|\.resx|\.resources)$" type="Dottext.Framework.UrlManager.HttpForbiddenHandler, Dottext.Framework"handlerType="Direct"/>
<HttpHandlerpattern="(\.gif|\.js|\.jpg|\.zip|\.jpeg|\.jpe|\.css|\.rar|\.xml|\.xsl)$"type="Dottext.Common.UrlManager.BlogStaticFileHandler, Dottext.Common"handlerType="Direct"/>
...... <HttpHandlerpattern="/services\/pingback\.aspx$"type="Dottext.Framework.Tracking.PingBackService, Dottext.Framework" handlerType="Direct"/> <HttpHandlerpattern="/services\/metablogapi\.aspx$"type="Dottext.Framework.XmlRpc.MetaWeblog, Dottext.Framework" handlerType="Direct"/>
可以看到,大部分我们找不到实际的文件名,但是却可以通过访问blog下的url返回内容,系统根据url判断如何返回内容。我们举一个例子来看看Direct怎么执行的。看看<HttpHandlerpattern="/rss\.aspx$"type="Dottext.Common.Syndication.RssHandler, Dottext.Common" handlerType="Direct"/>
执行:
case HandlerType.Direct: HandlerConfiguration.SetControls(context,items[i].BlogControls);
return (IHttpHandler)items[i].Instance();
时候,会实例化一个Dottext.Common.Syndication.RssHandler类的实例(RssHandler是间接实现了IhttpHandler接口的),它继承自抽象类BaseSyndicationHandler,BaseSyndicationHandler实现了总体的返回特定格式RSS文档的功能和能力,通过继承覆盖,不同的格式的实现类(RSS20和ATOM等)实现了各自格式的rss文档返回给用户。总之,在这类的handler中,最终通过Context.Response操纵到客户的输出流。
第三类的是Factory类型的,其自身就是一个工厂模式的handler,会再次将当前url转交给下一级handler,这样实现了可扩展性。如果dottext的新功能需要进一步处理URL得到其他功能就可以利用此类进行处理。譬如:
<HttpHandlerpattern="/(?:admin)"type="Dottext.Web.UI.Handlers.BlogExistingPageHandler, Dottext.Web"handlerType="Factory"/>
当用户访问应用程序下的/admin目录时候,自然处于该Dottext.Web.UI.Handlers.BlogExistingPageHandle 处理。由于是工厂模式,所以我们着重看看:
public virtual IHttpHandler GetHandler(HttpContext context, string requestType, string url, string path)
{ BlogConfig config = Config.CurrentBlog(context);
if(ConfigProvider.Instance().IsAggregateSite)
{
string app = config.Application.ToLower();
url = Regex.Replace(url,app,"/",RegexOptions.IgnoreCase);
app = "\\\\"+config.CleanApplication+"\\\\";
path = Regex.Replace(path,app,"/",RegexOptions.IgnoreCase);
if(!Regex.IsMatch(path,"\\.\\w+$"))
{
path = System.IO.Path.Combine(path,"index.aspx");
}
}
return PageParser.GetCompiledPageInstance(url, path, context);
}
处理时候,首先取得当前blog的配置,ConfigProvider.Instance()返回一个Iconfig接口的实例,看看这个Instance的代码:
static ConfigProvider() //静态构造函数
{
ConfigProviderConfiguration cpc = Config.Settings.BlogProviders.ConfigProvider;
config = (IConfig)cpc.Instance();
config.Application = cpc.Application;
config.CacheTime = cpc.CacheTime;
config.Host = cpc.Host;
config.ImageDirectory = cpc.ImageDirectory;
config.BlogID = cpc.BlogID;
}
private static IConfig config = null;
public static IConfig Instance()
{
return config;
}
执行静态构造函数,通过Config.Settings.BlogProviders.ConfigProvider反序列化得到ConfigProviderConfiguration。ConfigProviderConfiguration继承抽象类BaseProvider,通过BaseProvider的instance方法:
public object Instance()
{
return Activator.CreateInstance(System.Type.GetType(this.ProviderType));
}
此处的ProviderType是通过反序列化得到:
[XmlAttribute("type")]
public string ProviderType
{
get { return _type; }
set { _type = value; }
}
也就是
<ConfigProvidertype="Dottext.Common.Config.MultipleBlogConfig, Dottext.Common"host="localhost" cacheTime="120"/> 中指明的MultipleBlogConfig 类型。MultipleBlogConfig继承自BaseBlogConfig , BaseBlogConfig实现了IConfig,所以你才看到
config = (IConfig)cpc.Instance();
然后,得到了相应的属性IsAggregateSite。该属性的意思是当前访问的是否是聚合站点(而不是但个博客的站点,只有聚合站点才可以使用存在的aspx文件)。确认聚合站点后,就取得应用程序目录下的实际aspx文件,然后利用CLR的功能PageParser.GetCompiledPageInstance(url, path, context)返回页面执行结果。
所有blog的http请求,依据URL通过正则表达式匹配到不同的Handler类型,实现了3种类别的处理,但最终用户看到的是请求执行结果。修改web.config我们可以进行特定资源的特殊执行,这是UrlReWrite的实质。