ASP.NET-自定义HttpModule与HttpHandler

    在以前的ASP.NET是如何在IIS下工做的这篇文章中介绍了ASP.NET与IIS配合工做的机制,在http请求通过一系列处理后,最后到达ASP.NET管道中,这时,就是Http Modules和HttpHandler出场的时候了。css

  再来摆出管道工做时序图来一看:html

管道

   

HttpModuleweb

HttpModule是相似于过滤器的做用,能够没有,也能够有任意个,每个均可以订阅管道事件中的任意个事件,在每一个订阅的事件中可自定义功能实现。c#

HttpModule是实现IHttpModule接口的类。接口以下:api

public interface IHttpModule
    {
        // 摘要: 
        //     处置由实现 System.Web.IHttpModule 的模块使用的资源(内存除外)。
        void Dispose();
        //
        // 摘要: 
        //     初始化模块,并使其为处理请求作好准备。
        //
        // 参数: 
        //  context:
        //  一个 System.Web.HttpApplication,它提供对 ASP.NET 应用程序内全部应用程序对象的公用的方法、属性和事件的访问
        void Init(HttpApplication context);
    }

下面实现一个HttpModule,并订阅管道中的一系列事件,订阅事件就是在Init方法中绑定EventHandler的过程:浏览器

代码有点长,由于我把每个事件都订阅了,这样一来能够清楚的看出哪些事件执行了,这些事件执行的前后顺序是什么。代码以下:缓存

public class MyModule : IHttpModule
    {
        #region IHttpModule Members

        public void Dispose()
        {
            //此处放置清除代码。
        }

        public void Init(HttpApplication context)
        {
            // 下面是如何处理 LogRequest 事件并为其 
            // 提供自定义日志记录实现的示例
            context.LogRequest += new EventHandler(OnLogRequest);
            context.BeginRequest += new EventHandler(context_BeginRequest);
            context.AuthenticateRequest += new EventHandler(context_AuthenticateRequest);
            context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
            context.AuthorizeRequest += new EventHandler(context_AuthorizeRequest);
            context.Disposed += new EventHandler(context_Disposed);
            context.Error += new EventHandler(context_Error);
            context.EndRequest += new EventHandler(context_EndRequest);
            context.MapRequestHandler += new EventHandler(context_MapRequestHandler);
            context.PostAcquireRequestState += new EventHandler(context_PostAcquireRequestState);
            context.PostAuthenticateRequest += new EventHandler(context_PostAuthenticateRequest);
            context.PostAuthorizeRequest += new EventHandler(context_PostAuthorizeRequest);
            context.PostLogRequest += new EventHandler(context_PostLogRequest);
            context.PostReleaseRequestState += new EventHandler(context_PostReleaseRequestState);
            context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute);
            context.PostResolveRequestCache += new EventHandler(context_PostResolveRequestCache);
            context.PostUpdateRequestCache += new EventHandler(context_PostUpdateRequestCache);
            context.ReleaseRequestState += new EventHandler(context_ReleaseRequestState);
            context.RequestCompleted += new EventHandler(context_RequestCompleted);
            context.ResolveRequestCache += new EventHandler(context_ResolveRequestCache);
            context.UpdateRequestCache += new EventHandler(context_UpdateRequestCache);
            context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
            context.PreSendRequestContent += new EventHandler(context_PreSendRequestContent);
            context.PreSendRequestHeaders += new EventHandler(context_PreSendRequestHeaders);
            context.PostMapRequestHandler += new EventHandler(context_PostMapRequestHandler);


        }

        void context_Error(object sender, EventArgs e)
        {
            WriteLog("Error");
            //HttpContext.Current.Response.Write("Error<br />");
        }

        void context_UpdateRequestCache(object sender, EventArgs e)
        {
            WriteLog("UpdateRequestCache");
            //HttpContext.Current.Response.Write("UpdateRequestCache<br />");
        }

        void context_ResolveRequestCache(object sender, EventArgs e)
        {
            WriteLog("ResolveRequestCache");
            // HttpContext.Current.Response.Write("ResolveRequestCache<br />");
        }

        void context_RequestCompleted(object sender, EventArgs e)
        {
            WriteLog("RequestCompleted");
            // HttpContext.Current.Response.Write("RequestCompleted<br />");
        }

        void context_ReleaseRequestState(object sender, EventArgs e)
        {
            WriteLog("ReleaseRequestState");
            //HttpContext.Current.Response.Write("ReleaseRequestState<br />");
        }

        void context_PostUpdateRequestCache(object sender, EventArgs e)
        {
            WriteLog("PostUpdateRequestCache");
            //HttpContext.Current.Response.Write("PostUpdateRequestCache<br />");
        }

        void context_PostResolveRequestCache(object sender, EventArgs e)
        {
            WriteLog("PostResolveRequestCache");
            //HttpContext.Current.Response.Write("PostResolveRequestCache<br />");
        }

        void context_PostRequestHandlerExecute(object sender, EventArgs e)
        {
            WriteLog("PostRequestHandlerExecute");
            //HttpContext.Current.Response.Write("PostRequestHandlerExecute<br />");
        }

        void context_PostReleaseRequestState(object sender, EventArgs e)
        {
            WriteLog("PostReleaseRequestState");
            //HttpContext.Current.Response.Write("PostReleaseRequestState<br />");
        }

        void context_PostLogRequest(object sender, EventArgs e)
        {
            WriteLog("PostLogRequest");
            //HttpContext.Current.Response.Write("PostLogRequest<br />");
        }

        void context_PostAuthorizeRequest(object sender, EventArgs e)
        {
            WriteLog("PostAuthorizeRequest");
            //HttpContext.Current.Response.Write("PostAuthorizeRequest<br />");
        }

        void context_PostAuthenticateRequest(object sender, EventArgs e)
        {
            WriteLog("PostAuthenticateRequest");
            //HttpContext.Current.Response.Write("PostAuthenticateRequest<br />");
        }

        void context_PostAcquireRequestState(object sender, EventArgs e)
        {
            WriteLog("PostAcquireRequestState");
            //HttpContext.Current.Response.Write("PostAcquireRequestState<br />");
        }

        void context_MapRequestHandler(object sender, EventArgs e)
        {
            WriteLog("MapRequestHandler");
            //HttpContext.Current.Response.Write("MapRequestHandler<br />");
        }

        void context_Disposed(object sender, EventArgs e)
        {
            WriteLog("Disposed");
            //HttpContext.Current.Response.Write("Disposed<br />");
        }

        void context_AuthorizeRequest(object sender, EventArgs e)
        {
            WriteLog("AuthorizeRequest");
            //HttpContext.Current.Response.Write("AuthorizeRequest<br />");
        }

        void context_AcquireRequestState(object sender, EventArgs e)
        {
            WriteLog("AcquireRequestState");
            //HttpContext.Current.Response.Write("AcquireRequestState<br />");
        }


        void context_PreSendRequestHeaders(object sender, EventArgs e)
        {
            WriteLog("PreSendRequestHeaders");
            //HttpContext.Current.Response.Write("PreSendRequestHeaders<br />");
        }

        void context_PreSendRequestContent(object sender, EventArgs e)
        {
            WriteLog("PreSendRequestContent");
            //HttpContext.Current.Response.Write("PreSendRequestContent<br />");
        }

        void context_PreRequestHandlerExecute(object sender, EventArgs e)
        {
            WriteLog("PreRequestHandlerExecute");
            //HttpContext.Current.Response.Write("PreRequestHandlerExecute<br />");
        }

        void context_EndRequest(object sender, EventArgs e)
        {
            WriteLog("EndRequest");
            //HttpContext.Current.Response.Write("EndRequest<br />");
        }

        void context_BeginRequest(object sender, EventArgs e)
        {
            WriteLog("*******************************************************************************");
            HttpApplication app = sender as HttpApplication;
            WriteLog(app.Context.Request.Path);
            WriteLog("BeginRequest");
            //HttpContext.Current.Response.Write("BeginRequest<br />");
        }

        void context_AuthenticateRequest(object sender, EventArgs e)
        {
            WriteLog("AuthenticateRequest");
            //HttpContext.Current.Response.Write("AuthenticateRequest<br />");
        }
        #endregion

        public void OnLogRequest(Object source, EventArgs e)
        {
            //能够在此处放置自定义日志记录逻辑
            WriteLog("OnLogRequest");
            //HttpContext.Current.Response.Write("OnLogRequest<br />");
        }

        public void context_PostMapRequestHandler(object sender, EventArgs e)
        {
            WriteLog("PostMapRequestHandler");
            //HttpContext.Current.Response.Write("PostMapRequestHandler<br />");
        }

        public void WriteLog(string message)
        {
            string path = @"d:\aspnet\httpmodule.txt";
            StreamWriter writer = null;
            if (!File.Exists(path))
            {
                writer = File.CreateText(path);
            }
            else
            {
                FileInfo info = new FileInfo(path);
                writer = info.AppendText();

            }
            writer.WriteLine(message);

            writer.Flush();
            writer.Close();
        }
    }

订阅的事件实现中,将事件名称保存到我本地D盘的一个文本文件中。服务器

代码实现完毕了,下一步就是要代码起做用了,很简单,只须要在web.config中简单配置就能够了。配置中注意IIS7集成模式和IIS7经典模式(包括IIS6)的区别,配置以下:app

<!--IIS6或者IIS7经典模式-->
<system.web>
    <httpModules>
      <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
    </httpModules>
  </system.web>
<!--IIS7集成模式-->
<system.webServer>
    <modules>
      <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
    </modules>
</system.webServer>

如此一来,一个HttpModule及其配置工做就完成了,接下来,发布网站到IIS或者直接在VS中运行,随便访问项目中的一个文件(任何文件类型均可以),个人项目中有一个WebForm2.aspx的页面,我在浏览器中访问这个页面,发现页面是空白的,由于页面中我什么都没写,上面的Module实现中,我把输出所有放到本地D盘的一个文本文件中了,ok,打开那个文本文件。如图:模块化

image

咱们看到输出内容,第2行是访问的页面地址,下面依次为订阅的事件输出,咱们清楚的看到了事件的执行顺序。

BeginRequest          		#发出信号表示建立任何给定的新请求。 此事件始终被引起,而且始终是请求处理期间发生的第一个事件
AuthenticateRequest         #发出信号表示配置的身份验证机制已对当前请求进行了身份验证。 订阅 AuthenticateRequest 事件可确保在处理附加模块或事件处理程序以前对请求进行身份验证
PostAuthenticateRequest     #预订 PostAuthenticateRequest 事件的功能能够访问由 PostAuthenticateRequest 处理的任何数据
AuthorizeRequest			#发出信号表示 ASP.NET 已对当前请求进行了受权。 订阅 AuthorizeRequest 事件可确保在处理附加的模块或事件处理程序以前对请求进行身份验证和受权
PostAuthorizeRequest		#发出信号表示 ASP.NET 已对当前请求进行了受权。 订阅 PostAuthorizeRequest 事件可确保在处理附加的模块或处理程序以前对请求进行身份验证和受权
ResolveRequestCache			#引起这个事件来决定是否可使用从输出缓冲返回的内容来结束请求。这依赖于Web应用程序的输出缓冲时怎样设置的
PostResolveRequestCache		#在 ASP.NET 跳过当前事件处理程序的执行并容许缓存模块知足来自缓存的请求时发生
MapRequestHandler			#ASP.NET 基础结构使用 MapRequestHandler 事件来肯定用于当前请求的请求处理程序
PostMapRequestHandler		#在 ASP.NET 已将当前请求映射到相应的事件处理程序时发生
AcquireRequestState			#当 ASP.NET 获取与当前请求关联的当前状态(如会话状态)时发生
PostAcquireRequestState		#预订 AcquireRequestState 事件的功能能够访问由 PostAcquireRequestState 处理的任何数据
PreRequestHandlerExecute	#在ASP.NET开始执行HTTP请求的处理程序以前引起这个事件。在这个事件以后,ASP.NET 把该请求转发给适当的HTTP处理程序
PostRequestHandlerExecute   #在 ASP.NET 事件处理程序(例如,某页或某个 XML Web service)执行完毕时发生
ReleaseRequestState			#在 ASP.NET 执行完全部请求事件处理程序后发生。 该事件将使状态模块保存当前状态数据
PostReleaseRequestState		#在 ASP.NET 已完成全部请求事件处理程序的执行而且请求状态数据已存储时发生
UpdateRequestCache			#当 ASP.NET 执行完事件处理程序以使缓存模块存储将用于从缓存为后续请求提供服务的响应时发生
PostUpdateRequestCache		#在 ASP.NET 完成缓存模块的更新并存储了用于从缓存中为后续请求提供服务的响应后,发生此事件
OnLogRequest				#刚好在 ASP.NET 为当前请求执行任何记录以前发生,即便发生错误,也会引起 LogRequest 事件
PostLogRequest				#在 ASP.NET 处理完 LogRequest 事件的全部事件处理程序后发生
EndRequest					#在 ASP.NET 响应请求时做为 HTTP 执行管线链中的最后一个事件发生
PreSendRequestContent		#刚好在 ASP.NET 向客户端发送内容以前发生,可能发生屡次
PreSendRequestHeaders		#刚好在 ASP.NET 向客户端发送 HTTP 标头以前发生
RequestCompleted			#在任何托管模块和处理程序执行后,它使模块清理资源

 

访问一个页面的过程当中,依次触发了23个事件,而HttpModule可订阅的事件个数为25个,观察发现,Error和Disposed这两个事件没有触发。Error事件在发生错误的状况下执行,而Disposed事件,当咱们关闭刚才打开的页面,再到文本文件里查看,发现Disposed事件出现了,因此Disposed在会话结束后触发。

因为HttpModule的个数能够有多个,咱们能够按照上面的方式定义HttpModule实现类,而后再web.config中增长配置项,就能够实现多个HttpModule同时订阅管道事件了。

 

介绍完HttpModule,那么HttpHandler又是什么呢,它又在什么何时执行呢?接下来看一下HttpHandler。

HttpHandler

HttpHandler是HTTP请求的处理中心,真正地对客户端请求的服务器页面作出编译和执行,并将处理事后的信息附加在HTTP请求信息流中再次返回到HttpModule中。 
HttpHandler与HttpModule不一样,一旦定义了本身的HttpHandler类,那么它对系统的HttpHandler的关系将是“覆盖”关系。

HttpHandler是实IHttpHandler接口的类,IHttpHandler接口定义以下:

    public interface IHttpHandler
    {
        // 摘要: 
        //     获取一个值,该值指示其余请求是否可使用 System.Web.IHttpHandler 实例。
        //
        // 返回结果: 
        //     若是 System.Web.IHttpHandler 实例可再次使用,则为 true;不然为 false。
        bool IsReusable { get; }

        // 摘要: 
        //     经过实现 System.Web.IHttpHandler 接口的自定义 HttpHandler 启用 HTTP Web 请求的处理。
        //
        // 参数: 
        //   context:
        //     System.Web.HttpContext 对象,它提供对用于为 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session
        //     和 Server)的引用。
        void ProcessRequest(HttpContext context);
    }

接口中只有一个属性和一个方法,因此实现一个HttpHandler也很简单,下面实现一个简单的HttpHandler,代码以下:

public class MyIISHandler : IHttpHandler
    {
        /// <summary>
        /// 您将须要在网站的 Web.config 文件中配置此处理程序 
        /// 并向 IIS 注册它,而后才能使用它。有关详细信息,
        /// 请参见下面的连接: http://go.microsoft.com/?linkid=8101007
        /// </summary>
        #region IHttpHandler Members

        public bool IsReusable
        {
            // 若是没法为其余请求重用托管处理程序,则返回 false。
            // 若是按请求保留某些状态信息,则一般这将为 false。
            get { return true; }
        }

        public void ProcessRequest(HttpContext context)
        {
            //在此处写入您的处理程序实现。
            WriteLog("请求一个asox页面");
        }

        #endregion
    }

上面我实现了一个很简单的HttpHandler类,在ProcessRequest方法中,调用上面的HttpModule类中写文本文件的方法,在文本文件中写入“请求一个asox页面”,没错,是一个asox页面,我本身定义的文件格式,下面我会在web.config中添加配置项:

<!--IIS6或者IIS7经典模式-->
  <system.web>
    <httpHandlers>
      <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
    </httpHandlers>
  </system.web>
 
<!--IIS7集成模式-->
  <system.webServer>
    <handlers>
       <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
    </handlers>
  </system.webServer>

配置项属性的解释:

path:指定了须要调用处理程序的路径和文件名(能够包含通配符)。“*”、“*.aspx”、“booklist.aspx”、“test1.aspx,test2.aspx”、“*.asox”、“*.txt”。

verb:指定了处理程序支持的HTTP动做。*-支持全部的HTTP动做;“GET”-支持Get操做;“POST”-支持Post操做;“GET, POST”-支持两种操做。

type:用名字空间、类名称和程序集名称的组合形式指定处理程序或处理程序工厂的实际类型。ASP.NET运行时首先搜索bin目录中的DLL,接着在GAC中搜索。

 

接着,发布站点到IIS。打开IIS,找到当前站点的“处理程序映射”,会发现多了刚刚配置的HttpHandler,如图:

2014-04-15_181203

没错,关于对*.asox这种类型的文件,就能够映射到上面建立的HttpHandler来进行处理,观察其它条目发现,像*.aspx、*.ashx的处理程序是System.Web.UI.PageHandlerFactory和System.Web.UI.SimpleHandlerFactory这样的工厂类型。没错,能够指定处理程序为一个HttpHandler,也能够指定为一个抽象工厂类型。先不说工厂类型的事儿,访问一下网站中的asox页面,看一下文本文件的记录状况。

image

起做用了,在HttpModule输出的一堆信息中,夹杂着HttpHandler的输出,固然这仅限于访问asox类型的页面,由于我只对路径为*.asox的文件格式作了设置,修改下配置文件,例如将path=”*.asox”改成path=”*.aspx”,那么ASP.NET对*.aspx页面原有的解析机制将被咱们设置的处理程序所覆盖。

 

回到上面的输出内容,咱们观察HttpHandler输出内容所在的位置,位于PreRequestHandlerExecute和PostRequestHandlerExecute这两个事件之间,这与HttpApplication类中的管道事件的建立过程有关。

 

前面说到了,处理处理程序能够指定为一个工厂类型,下面,我就建立一个工厂类型的处理程序。

这里所说的工厂类型的处理程序,就是实现了IHttpHandlerFactory接口的类,IHttpHandlerFactory接口定义以下:

public interface IHttpHandlerFactory
    {
        // 摘要: 
        //     返回实现 System.Web.IHttpHandler 接口的类的实例。
        //
        // 参数: 
        //   context:
        //     System.Web.HttpContext 类的实例,它提供对用于为 HTTP 请求提供服务的内部服务器对象(如 Request、Response、Session
        //     和 Server)的引用。
        //
        //   requestType:
        //     客户端使用的 HTTP 数据传输方法(GET 或 POST)。
        //
        //   url:
        //     所请求资源的 System.Web.HttpRequest.RawUrl。
        //
        //   pathTranslated:
        //     所请求资源的 System.Web.HttpRequest.PhysicalApplicationPath。
        //
        // 返回结果: 
        //     处理请求的新的 System.Web.IHttpHandler 对象。
        IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated);
        //
        // 摘要: 
        //     使工厂能够重用现有的处理程序实例。
        //
        // 参数: 
        //   handler:
        //     要重用的 System.Web.IHttpHandler 对象。
        void ReleaseHandler(IHttpHandler handler);
    }

一样很简单,也是只有两个接口方法,下面是实现这个接口的工厂,代码以下:

public class MyHttpHandlerFactory:IHttpHandlerFactory
{
        public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
        {
			//写日志
            WriteLog(string.Format("requestType:{0}|url:{1}|pathTranslated:{2}", requestType, url, pathTranslated));
            
			//生成一个IHttpHandler
			Type t = typeof(MyIISHandler);
            IHttpHandler handler =  Activator.CreateInstance(t) as IHttpHandler;
            return handler;
        }

        public void ReleaseHandler(IHttpHandler handler)
        {
        }
}

方法中,返回了前面建立的那个HttpHander类,依然调用记录文本文件的方法输出内容,方便观察执行的实际和具体内容。配置文件改改成这个工厂类。

<add name="mycustomFactoryHandler" path="*.asox" verb="*" type="fengzheng.MyHttpHandlerFactory,handler_modules"/>

配置完毕后,访问网站中的asox页面,打开文本文件,内容以下:

image

咱们发现,工厂类中构造IHttpHandler接口的方法发生在HttpModule的MapRequestHandler以后,这一样与HttpApplication类中构造管道事件有关。

 

说了这么多,那么,HttpModule和HttpHandler究竟能干什么呢?

HttpModule很经常使用的一个做用就是Url重写,URLRewriter就是基于HttpModule实现的。

另外,有经过HttpHandler对图片加水印,防止盗链的。

具体的能够参考这篇文章

 

部署网站注意事项:

网站采用.net 4.0集成模式部署,集成模式是一种统一的请求处理管道,它将ASP.NET请求管道与IIS核心管道组合在一块儿,这种模式可以提供更好的性能,可以实现配置和治理的模块化,并且增长了使用托管代码模块扩展IIS时的灵活性。IIS经典模式与集成模式的区别

集成模式和经典模式的配置文件稍有不一样,部署时须要注意针对不一样的部署模式,修改配置文件。在vs2013中新建的web应用程序,默认的web.config内容以下:

<?xml version="1.0" encoding="utf-8"?>
<!--
  有关如何配置 ASP.NET 应用程序的详细信息,请访问
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
</configuration>
  • 按照经典模式部署,配置文件应该以下:
<?xml version="1.0" encoding="utf-8"?>
<!--
  有关如何配置 ASP.NET 应用程序的详细信息,请访问
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <httpModules>
      <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
    </httpModules>
    <httpHandlers>
      <add name="mycustomhandler" path="*.asox" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
    </httpHandlers>
  </system.web>
</configuration>

经典模式经测试老是出现以下错误,500.21 - 模块没法识别:

HTTP 错误 500.21 - Internal Server Error
处理程序“PageHandlerFactory-ISAPI-4.0_64bit”在其模块列表中有一个错误模块“IsapiModule”

至于错误缘由:目前还不是很清楚。

 

  • 按照集成模式部署,配置文件应该以下,将配置信息放到system.webServer节点之下
<?xml version="1.0" encoding="utf-8"?>
<!--
  有关如何配置 ASP.NET 应用程序的详细信息,请访问
  http://go.microsoft.com/fwlink/?LinkId=169433
  -->
<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <modules>
      <add name="mycustommodule" type="fengzheng.MyModule,handler_modules"/>
    </modules>
    <handlers>
      <add name="mycustomhandler" path="*" verb="*" type="fengzheng.MyIISHandler,handler_modules"/>
    </handlers>
  </system.webServer>
</configuration>
相关文章
相关标签/搜索