在 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

谢谢浏览!

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

原文:https://www.cnblogs.com/micro-chen/p/15266950.html

以上是在 ASP.NET WebForms/MVC 中利用 HttpModule 添加全局站点统计(CNZZ、百度统计、Google Analytics等)脚本的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>