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


    在面向大众类型的网站应用中,我们常常需要知道网站的访问情况,特别是站长。就目前来说,有很多网站可以为你提供统计服务,比如: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 = "";
        private const string FLAG_IsHasAppend_CrossDomainScript = "CrossDomainScriptAppend";
    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 (null != HttpContext.Current && null != HttpContext.Current.Response)
    {
      object flagAppend = HttpContext.Current.Items[FLAG_IsHasAppend_CrossDomainScript];
       if (null!=flagAppend&&(bool)flagAppend==true)
       {
          return 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;
        }
    }
    复制代码

    Demo 下载:http://files.cnblogs.com/Music/ModifyResponseSteamInHttpModuleDemo.rar

    谢谢浏览!

  • 相关阅读:
    程序自动更新版本
    [.NET] Rough Dependency Injection
    Python标准库存储对象(pickle包,cPickle包)
    发送邮件,支持群发
    css3传送带示例
    “计算机之子”的MVVM框架源码学习笔记
    Windows 8 应用商店正式面向全部开发者开放
    MVVM框架 v1发布
    Python学习索引
    注册 windows 8 开发者账号
  • 原文地址:https://www.cnblogs.com/micro-chen/p/15266950.html
Copyright © 2020-2023  润新知