在 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的详细内容...
声明:本文来自网络,不代表【好得很程序员自学网】立场,转载请注明出处:http://www.haodehen.cn/did47979