利用HttpModule做流量记录
简单需求:
记录用户访问网站的地址,浏览器,时间,用户信息等信息。
原来打算用免费的流量统计系统,但是考虑到分析数据最好自己保留,所以最终决定自己做。首要一步就是记录流量信息。
前面《利用HttpModule实现浏览器版本控制》就是在利用HttpModule记录流量信息时做的衍生,同时也可以实现页面编程无需任何附加代码。不需要加JS代码段也不要附加任何CS代码段。并且模块相互独立,可以重复利用,也利于不需要时分离。
FlowEntity.cs(信息实体)——————————————————————————————————————————————
using System;
using System.Collections.Generic;
using System.Text;
namespace Xingmai.WebSite.FlowStatistics
{
/// <summary>
/// 流量记录单个记录集
/// </summary>
public class FlowEntity
{
private FlowAgentEntity _AgentInfo;
private FlowUserInfoEntity _UserInfo;
private FlowRequestEntity _RequestInfo;
/// <summary>
/// 用户基本信息
/// </summary>
public FlowUserInfoEntity UserInfo
{
get
{
return _UserInfo;
}
set
{
_UserInfo = value;
}
}
/// <summary>
/// 用户代理信息
/// </summary>
public FlowAgentEntity AgentInfo
{
get
{
return _AgentInfo;
}
set
{
_AgentInfo = value;
}
}
/// <summary>
/// 用户访问信息
/// </summary>
public FlowRequestEntity RequestInfo
{
get
{
return _RequestInfo;
}
set
{
_RequestInfo = value;
}
}
}
/// <summary>
/// 用户基本信息实体
/// </summary>
public class FlowUserInfoEntity
{
private int _UserID;
private string _UserName;
/// <summary>
/// 用户ID
/// </summary>
public int UserID
{
get
{
return _UserID;
}
set
{
_UserID = value;
}
}
/// <summary>
/// 用户名
/// </summary>
public string UserName
{
get
{
return _UserName;
}
set
{
_UserName = value;
}
}
}
/// <summary>
/// 用户代理信息实体
/// </summary>
public class FlowAgentEntity
{
private int _BrowserMajorVer;
private double _BrowserMinorVer;
private string _BrowserName;
private string _HostName;
private string _IP;
private string _Language;
private string _PlatForm;
/// <summary>
/// 用户访问IP
/// </summary>
public string IP
{
get
{
return _IP;
}
set
{
_IP = value;
}
}
/// <summary>
/// 用户主机名
/// </summary>
public string HostName
{
get
{
return _HostName;
}
set
{
_HostName = value;
}
}
/// <summary>
/// 用户语种
/// </summary>
public string Language
{
get
{
return _Language;
}
set
{
_Language = value;
}
}
/// <summary>
/// 用户操作系统平台
/// </summary>
public string PlatForm
{
get
{
return _PlatForm;
}
set
{
_PlatForm = value;
}
}
/// <summary>
/// 浏览器名称
/// </summary>
public string BrowserName
{
get
{
return _BrowserName;
}
set
{
_BrowserName = value;
}
}
/// <summary>
/// 浏览器主版本号
/// </summary>
public int BrowserMajorVer
{
get
{
return _BrowserMajorVer;
}
set
{
_BrowserMajorVer = value;
}
}
/// <summary>
/// 浏览器次版本号
/// </summary>
public double BrowserMinorVer
{
get
{
return _BrowserMinorVer;
}
set
{
_BrowserMinorVer = value;
}
}
}
/// <summary>
/// 用户访问信息实体
/// </summary>
public class FlowRequestEntity
{
private DateTime _RequestDateTime;
private string _RequestUrl;
/// <summary>
/// 访问时间
/// </summary>
public DateTime RequestDateTime
{
get
{
return _RequestDateTime;
}
set
{
_RequestDateTime = value;
}
}
/// <summary>
/// 访问地址
/// </summary>
public string RequestUrl
{
get
{
return _RequestUrl;
}
set
{
_RequestUrl = value;
}
}
}
}
FlowModule.cs(Module模块)——————————————————————————————————————————————
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Xml;
using System.IO;
using System.Configuration;
namespace Xingmai.WebSite.FlowStatistics
{
public class FlowModule : IHttpModule
{
#region 内部似有变量
private string[] FlowType;
private FlowDAL dal;
#endregion
#region 内部自有方法
private bool IsFlowType(string strUrl)
{
foreach (string strFlowType in FlowType)
{
if (strUrl.ToLower().Contains(strFlowType))
{
return true;
}
}
return false;
}
#endregion
#region 实例构造函数
public FlowModule()
{
//读取记录数据的类型
try
{
FlowType = ConfigurationManager.AppSettings["FlowType"].Split((new string[] { "|" }), StringSplitOptions.RemoveEmptyEntries);
}
catch (System.Configuration.ConfigurationErrorsException ex)
{
throw new Exception(string.Format("请正确配置web.config AppSetting[BrowserChooserPage]节点,系统错误提示:{0}", ex.Message));
}
catch (System.Exception ex)
{
throw ex;
}
//实例化数据层
dal = new FlowDAL();
}
#endregion
#region IHttpModule 成员
public void Init(HttpApplication application)
{
//这里8月29日有更改,这个BeginRequest事件里面无法获取到Session,疏忽,望大家谅解!应改用AcquireRequestState事件!
//application.BeginRequest += (new EventHandler(this.Application_BeginRequest));
application.AcquireRequestState+=new EventHandler(application_AcquireRequestState);
}
private void application_AcquireRequestState(Object source, EventArgs e)
{
HttpApplication Application = (HttpApplication)source;
HttpContext ctx = Application.Context;
if(!IsFlowType(ctx.Request.Url.AbsoluteUri))
return;
FlowUserInfoEntity user = new FlowUserInfoEntity();
if (ctx.Session["UserInfo"] != null)
{
user.UserID = 1;
user.UserName = "ceshi";
}
else
{
user.UserID = 0;
user.UserName = "UnKnown";
}
FlowRequestEntity request = new FlowRequestEntity();
request.RequestDateTime = DateTime.Now;
request.RequestUrl = ctx.Request.Url.AbsoluteUri;
FlowAgentEntity agent = new FlowAgentEntity();
agent.BrowserMajorVer = ctx.Request.Browser.MajorVersion;
agent.BrowserMinorVer = ctx.Request.Browser.MinorVersion;
agent.BrowserName = ctx.Request.Browser.Browser;
agent.HostName = ctx.Request.UserHostName;
agent.IP = ctx.Request.UserHostAddress;
agent.Language = ctx.Request.UserLanguages[0];
agent.PlatForm = ctx.Request.Browser.Platform;
FlowEntity entity = new FlowEntity();
entity.AgentInfo = agent;
entity.UserInfo = user;
entity.RequestInfo = request;
dal.Record(entity);
}
public void Dispose()
{
}
#endregion
}
}
FlowDAL.cs(数据存储层,可以根据自己的需要重写此层。想存数据库也行,XML也行)—————————————————————————
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Configuration;
namespace Xingmai.WebSite.FlowStatistics
{
/// <summary>
/// 流量统计数据层
/// </summary>
public class FlowDAL
{
private SqlConnection _conn;
public FlowDAL()
{
try
{
_conn = new SqlConnection(ConfigurationManager.ConnectionStrings["FlowConn"].ConnectionString);
_conn.Open();
}
catch (ConfigurationErrorsException ex)
{
throw new Exception(string.Format("请正确配置web.config AppSetting[BrowserChooserPage]节点,系统错误提示:{0}", ex.Message));
}
catch (SqlException ex)
{
throw ex;
}
catch (Exception ex)
{
throw ex;
}
}
/// <summary>
/// 记录流量信息
/// </summary>
/// <param name="flowEntity">流量记录实体</param>
public void Record(FlowEntity flowEntity)
{
//判断连接是否为打开状态
if (_conn.State = !ConnectionState.Open)
_conn.Open();
///////记录代码略////////
}
public void Dispose()
{
_conn.Close();
}
}
}
web.config <system.web> <httpModules> 配置节点—————————————————————————————————
<add name="FlowModule" type="Xingmai.WebSite.FlowStatistics.FlowModule, Xingmai.WebSite.FlowStatistics"/>
web.config <appSettings> 配置节点 (需要记录的类型)——————————————————————————————
<add key="FlowType" value=".htm|.html|.aspx|.asmx"/>
web.config <connectionStrings>配置节点 (记录数据库的连接,如果你重写的数据层,那么这个可要可不要)——————
<add name="FlowConn" connectionString="" providerName="System.Data.SqlClient"/>
关于IHttpModule(MSDN信息)
自定义 HTTP 模块阐释了 HTTP 模块的基本功能。在响应下面两个事件时调用该模块:BeginRequest 事件和 EndRequest 事件。这使该模块可以在处理页请求之前和之后运行。在这种情况下,
该模块向请求的 ASP.NET 网页的任一 HTTP 请求开头处添加一条消息,并在处理请求后添加另一条消息。
注意
BeginRequest 和 EndRequest 事件只是在处理页期间发生的两个事件。有关在处理页期间引发的事件的更多信息,请参见 ASP.NET 网页中的服务器事件处理。
每个事件处理程序都编写为模块的私有方法。在引发已注册事件时,ASP.NET 调用该模块中适当的处理程序方法,该方法将信息写入 ASP.NET 网页中。
创建自定义 HTTP 模块类
如果网站还没有 App_Code 文件夹,请在该站点的根目录下创建这样的一个文件夹。
在 App_Code 目录中,创建一个名为 HelloWorldModule.cs的类文件。
注意
或者,可以将 HelloWorldModule 类编译到一个库中,并将得到的 .dll 文件放在 Web 应用程序的 Bin 目录中。
将以下代码添加到该类文件中:
C#
public class HelloWorldModule : IHttpModule
{
public HelloWorldModule()
{
}
public String ModuleName
{
get { return "HelloWorldModule"; }
}
// In the Init function, register for HttpApplication
// events by adding your handlers.
public void Init(HttpApplication application)
{
application.BeginRequest +=
(new EventHandler(this.Application_BeginRequest));
application.EndRequest +=
(new EventHandler(this.Application_EndRequest));
}
private void Application_BeginRequest(Object source,
EventArgs e)
{
// Create HttpApplication and HttpContext objects to access
// request and response properties.
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("<h1><font color=red>
HelloWorldModule: Beginning of Request
</font></h1><hr>");
}
private void Application_EndRequest(Object source, EventArgs e)
{
HttpApplication application = (HttpApplication)source;
HttpContext context = application.Context;
context.Response.Write("<hr><h1><font color=red>
HelloWorldModule: End of Request</font></h1>");
}
public void Dispose()
{
}
}
注册 HTTP 模块
在创建完 HelloWorldModule 类后,可以通过在 Web.config 文件中创建一项来注册该模块。
在 Web.config 文件中注册该模块
如果网站还没有 Web.config 文件,请在该站点的根目录下创建一个这样的文件。
将下面突出显示的代码添加到该 Web.config 文件中:
复制代码
<configuration>
<system.web>
<httpModules>
<add name="HelloWorldModule" type="HelloWorldModule"/>
</httpModules>
</system.web>
</configuration>
这段代码用 HelloWorldModule 的类名和模块名注册该模块。
测试自定义 HTTP 模块
创建并注册完自定义 HTTP 模块后,可以对它进行测试。
测试自定义 HTTP 模块
在应用程序中创建一个 Default.aspx 页。
在浏览器中请求该 Default.aspx 页。
HTTP 模块会将一个字符串追加到响应的开头和结尾。在请求扩展名指定为 ASP.NET 类型的文件时,该模块将会自动运行。
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
后续《利用HttpModule做流量记录 补充》对方案进行了一些补充
如果选用从Session中传入用户信息或者其他需要记录的信息,请在记录前加判断(2007年8月29日20:34:43增加)
if (ctx.Handler is Page || ctx.Handler is WebService)
根据需要选用Page还是WebService。
调试过程中发现,如果是其他的类型可能不创建Session,这时从Session读取数据发生错误,造成整个请求中断引起请求失效。这样其他的Module可能不能执行造成页面上一些需要生成的东西无法生成,例如Asp.net Ajax从WebService生成的脚本类型注册等,引发错误!
尽量捕捉错误,因为这些错误可能不会直接爆出,引发请求中断,让人有的时候摸不着头脑!