好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

在 ASP.NET WebForms/MVC 中利用 HttpModule 添加全局站点统计(CNZ

在 ASP.NET WebForms/MVC 中利用 HttpModule 添加全局站点统计(CNZZ、百度统计、Google Analytics等)脚本

在 ASP.NET WebForms/MVC 中利用 HttpModule 添加全局站点统计(CNZZ、百度统计、Google Analytics等)脚本

2012-09-21 13:38 by 自由的生活, 218 阅读,  0  评论,  收藏 ,  编辑

在面向大众类型的网站应用中,我们常常需要知道网站的访问情况,特别是站长。就目前来说,有很多网站可以为你提供统计服务,比如:CNZZ、百度统计、Google Analytics等等,而你只需要在你的网站的每个页面的底部添加一些 Javascript 脚本就可以了,比如:

 <!--   百度统计   --> 
 <  script   type  ="text/javascript"  > 
     var   _bdhmProtocol   =   ((  "  https:  "   ==   document.location.protocol)   ?   "   https://  "   :   "   http://  "  );
    document.write(unescape(  "  %3Cscript src='  "   +   _bdhmProtocol   +   "  hm.baidu.com/h.js%3F5ba98b01aa179c8992f681e4e11680ab' type='text/javascript'%3E%3C/script%3E  "  ));
  </  script  > 
 <!--   Google 统计   --> 
 <  script   type  ="text/javascript"  > 
     var   _gaq   =   _gaq   ||   [];
    _gaq.push([  '  _setAccount  '  ,   '  UA-18157857-1  '  ]);
    _gaq.push([  '  _trackPageview  '  ]);

    (  function   ()
    {
          var   ga   =   document.createElement(  '  script  '  ); ga.type   =   '  text/javascript  '  ; ga.async   =   true  ;
        ga.src   =   (  '  https:  '   ==   document.location.protocol   ?   '  https://ssl  '   :   '  http://www  '  )   +   '  .google-analytics.com/ga.js  '  ;
          var   s   =   document.getElementsByTagName(  '  script  '  )[  0  ]; s.parentNode.insertBefore(ga, s);
    })();
  </  script  > 

添加这些脚本的方式有多种,第一种就是在每个页面都手动添加,这种方式适合与一些小网站,只有几个静态的 html 页面。第二种方式在“模板(或母板)”页中添加,这种也是比较好的方法。第三种就是在服务器响应的时候,动态添加,这种方法适合与一些网站前期开发时没有添加统计脚本,又没有模板(或母板)页,又可能包含静态的 html 页面的网站,为了不改变原有的代码,又节省时间,又利用维护,这也是我今天写这篇博客的目的。

新建自己的 HttpModule 类

新建自己的 HttpModule 类,比如我这里叫 SiteStatModule,实现 IHttpModule 接口,在 Init 方法给 HttpApplication 注册 ReleaseRequestState 事件,这个事件的解释如下:

在 ASP.NET 执行完所有请求事件处理程序后发生。该事件将使状态模块保存当前状态数据。

在这个事件中,我们需要做的就是判断 HttpResponse.StatusCode 是否等于 200,并且响应的内容的类型是否为 "text/html",如果是,我们就对它进行处理。

 public   class   SiteStatModule : IHttpModule
{
      private   const   string  Html_CONTENT_TYPE =  "  text/html  "  ;

      #region  IHttpModule Members

     public   void   Dispose()
    {
    }

      public   void   Init(HttpApplication app)
    {
        app.ReleaseRequestState  +=  OnReleaseRequestState;
    }

      #endregion 

     public   void  OnReleaseRequestState( object   sender, EventArgs e)
    {
        HttpApplication app  =  (HttpApplication)sender;
        HttpResponse response  =  app.Response;
          string  contentType =  response.ContentType.ToLowerInvariant();
          if  (response.StatusCode ==  200  && ! string .IsNullOrEmpty(contentType) &&  contentType.Contains(Html_CONTENT_TYPE))
        {
            response.Filter  =  new   SiteStatResponseFilter(response.Filter);
        }
    }
} 

这里的 response.Filter 需要一个 Stream 类的实例,于是我们自己建一个 SiteStatResponseFilter 类。

新建自己的 Response.Filter 类

新建自己的 Response.Filter 类,比如我这里叫 SiteStatResponseFilter 。我们需要重写 Stream 相关的成员(Property + Method),其中主要还是 Write 方法里。为了便于重复利用,我自己抽象出一个公用的 AbstractHttpResponseFilter,代码如下:

 public   abstract   class   AbstractHttpResponseFilter : Stream
{
      protected   readonly   Stream _responseStream;
        
      protected   long   _position;

      protected   AbstractHttpResponseFilter(Stream responseStream)
    {
        _responseStream  =  responseStream;
    }

      public   override   bool  CanRead {  get  {  return   true  ; } }

      public   override   bool  CanSeek {  get  {  return   true  ; } }

      public   override   bool  CanWrite {  get  {  return   true  ; } }

      public   override   long  Length {  get  {  return   0  ; } }

      public   override   long  Position {  get  {  return  _position; }  set  { _position =  value; } }

      public   override   void  Write( byte [] buffer,  int  offset,  int   count)
    {
        WriteCore(buffer, offset, count);
    }

      protected   abstract   void  WriteCore( byte [] buffer,  int  offset,  int   count);

      public   override   void   Close()
    {
        _responseStream.Close();
    }

      public   override   void   Flush()
    {
        _responseStream.Flush();
    }

      public   override   long  Seek( long   offset, SeekOrigin origin)
    {
          return   _responseStream.Seek(offset, origin);
    }

      public   override   void  SetLength( long   length)
    {
        _responseStream.SetLength(length);
    }

      public   override   int  Read( byte [] buffer,  int  offset,  int   count)
    {
          return   _responseStream.Read(buffer, offset, count);
    }
} 

然后让我们前面新建的 SiteStatResponseFilter 类继承自 AbstractHttpResponseFilter。在 WriteCore 方法中判断当前缓冲的字节流是否存在 "</body>",因为我们的统计脚本需要插入到 "</body>" 前。如果当前缓冲的字节流中存在 "</body>",我们就动态地往 HttpResponse 中写统计脚本。PS:由于 HttpResponse 在响应时是一点一点地输出,所以需要在 WriteCore 中判断。完整代码如下:

 public   class   SiteStatResponseFilter : AbstractHttpResponseFilter
{
      private   static   readonly   string  END_HTML_TAG_NAME =  "  </body>  "  ;

      private   static   readonly   string  SCRIPT_PATH =  "  DearBruce.ModifyResponseSteamInHttpModule.CoreLib.site-tongji.htm  "  ;

      private   static   readonly   string  SITE_STAT_SCRIPT_CONTENT =  ""  ;

      static   SiteStatResponseFilter()
    {
        Stream stream  =  System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(SCRIPT_PATH);
          if  (stream ==  null  )
        {
              throw   new  FileNotFoundException( string .Format( "  The file \"{0}\" not found in assembly  "  , SCRIPT_PATH));
        }
          using  (StreamReader reader =  new   StreamReader(stream))
        {
            SITE_STAT_SCRIPT_CONTENT  =  reader.ReadToEnd();
            reader.Close();
        }
    }

      public   SiteStatResponseFilter(Stream responseStream)
        :   base  (responseStream)
    {
            
    }
        

      protected   override   void  WriteCore( byte [] buffer,  int  offset,  int   count)
    {
          string  strBuffer =  Encoding.UTF8.GetString(buffer, offset, count);
        strBuffer  =  AppendSiteStatScript(strBuffer);
          byte [] data =  Encoding.UTF8.GetBytes(strBuffer);
        _responseStream.Write(data,   0  , data.Length);
    }

      ///   <summary> 
     ///   附加站点统计脚本
      ///   </summary> 
     ///   <param name="strBuffer"></param> 
     ///   <returns></returns> 
     protected   virtual   string  AppendSiteStatScript( string   strBuffer)
    {
          if  ( string  .IsNullOrEmpty(strBuffer))
        {
              return   strBuffer;
        }
          int  endHtmlTagIndex =  strBuffer.IndexOf(END_HTML_TAG_NAME, StringComparison.InvariantCultureIgnoreCase);
          if (endHtmlTagIndex <=  0  )
        {
              return   strBuffer;
        }
          return   strBuffer.Insert(endHtmlTagIndex, SITE_STAT_SCRIPT_CONTENT);
    }
} 

对了,为了不把这些统计脚本(本文最上面的那段脚本)硬编码到代码中,我把它放到了 site-tongji.htm 中,作为内嵌资源打包到 DLL 中,你也可以把它放到你网站下的某个目录。我的解决方法如下,请暂时忽略 JsonpModule.cs、JsonResponseFilter.cs

我把这些类放到了一个单独的程序集中,是为了让以前的 ASP.NET WebForms 程序和现在使用的 ASP.NET MVC 程序共用。

在 Web.Config 中自己的 HttpModule 类

最后一步就很简单了,在项目中添加对这个程序集的引用,我这里是添加 DearBruce.ModifyResponseSteamInHttpModule.CoreLib.dll,然后在 Web.Config 中注册一下就可以了。

 <  httpModules  > 
   <  add   name  ="SiteStatModule"   type  ="DearBruce.ModifyResponseSteamInHttpModule.CoreLib.SiteStatModule,DearBruce.ModifyResponseSteamInHttpModule.CoreLib"  /> 
 </  httpModules  > 

运行查看网页源代码,就可以看到统计脚本了。

如果部署在 IIS 上,需要添加一个映射,让 IIS 把 .htm 或 .html 的后缀的请求交给 ASPNET_ISAPI.dll。

附录

上面提到的 JsonpModule.cs 和 JsonResponseFilter.cs 是为了把程序中返回的 JSON 数据,转换为支持跨域的 JSONP 格式即 jsoncallback([?]),有兴趣的话你可以下载看看。

JsonpModule.cs

 public   class   JsonpModule : IHttpModule
{
      private   const   string  JSON_CONTENT_TYPE =  "  application/json  "  ;
      private   const   string  JS_CONTENT_TYPE =  "  text/javascript  "  ;

      #region  IHttpModule Members

     public   void   Dispose()
    {
    }

      public   void   Init(HttpApplication app)
    {
        app.ReleaseRequestState  +=  OnReleaseRequestState;
    }

      #endregion 

     public   void  OnReleaseRequestState( object   sender, EventArgs e)
    {
        HttpApplication app  =  (HttpApplication)sender;
        HttpResponse response  =  app.Response;
          if   (response.ContentType.ToLowerInvariant().Contains(JSON_CONTENT_TYPE)
             && ! string .IsNullOrEmpty(app.Request.Params[ "  jsoncallback  "  ]))
        {
            response.ContentType  =  JS_CONTENT_TYPE;
            response.Filter  =  new   JsonResponseFilter(response.Filter);
        }
    }
} 

JsonResponseFilter.cs

 public   class   JsonResponseFilter : AbstractHttpResponseFilter
{
      private   bool   _isContinueBuffer;

      public   JsonResponseFilter(Stream responseStream)
        :   base  (responseStream)
    {

    }

      protected   override   void  WriteCore( byte [] buffer,  int  offset,  int   count)
    {
          string  strBuffer =  Encoding.UTF8.GetString(buffer, offset, count);
        strBuffer  =  AppendJsonpCallback(strBuffer, HttpContext.Current.Request);
          byte [] data =  Encoding.UTF8.GetBytes(strBuffer);
        _responseStream.Write(data,   0  , data.Length);
    }

      private   string  AppendJsonpCallback( string   strBuffer, HttpRequest request)
    {
          string  prefix =  string  .Empty;
          string  suffix =  string  .Empty;

          if  (! _isContinueBuffer)
        {
            strBuffer  =  RemovePrefixComments(strBuffer);

              if  (strBuffer.StartsWith( "  {  "  ))
                prefix  = request.Params[ "  jsoncallback  " ] +  "  (  "  ;
        }
          if  (strBuffer.EndsWith( "  }  "  ))
        {
            suffix  =  "  );  "  ;
        }
        _isContinueBuffer  =  true  ;
          return  prefix + strBuffer +  suffix;
    }

      private   string  RemovePrefixComments( string   strBuffer)
    {
          var  str =  strBuffer.TrimStart();
          while  (str.StartsWith( "  /*  "  ))
        {
              var  pos = str.IndexOf( "  */  " ,  2  );
              if  (pos <=  0  )
                  break  ;
            str  = str.Substring(pos +  2  );
            str  =  str.TrimStart();
        }
          return   str;
    }
} 

谢谢浏览!

作者: 音乐让我说 ( 自由的生活 - 博客园 )
微博: http://weibo.com/liuzuliang  
出处: http://music.cnblogs.com/
文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

分类:  ASP.NET

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于在 ASP.NET WebForms/MVC 中利用 HttpModule 添加全局站点统计(CNZ的详细内容...

  阅读:46次