1.HttpModule概述
HttpModule是ASP.net开发中的瑞士军刀,如果适当地应用HttpModule可以提高系统的可维护性,HttpModule以极小的侵入性来解决系统中的具有通用性的问题。
HttpModule的典型应用场景有:
a. 用户验证
b. 权限控制
c. 用户访问控制,比如基于IP来控制用户对系统的访问
d. 脚本(CSS,JS等)的合并
e. URL Rewriting
f. 本地化(Localization)
g. 异常处理
h. 其他方面。
实际上,.net Framework中就大量采用了HttpModule.比如在.net 4.0的web.config文件中有如下的HttpModule.
<add name="OutputCache" type="System.Web.Caching.OutputCacheModule" />
<add name="Session" type="System.Web.SessionState.SessionStateModule" />
<add name="WindowsAuthentication" type="System.Web.Security.WindowsAuthenticationModule" />
<add name="FormsAuthentication" type="System.Web.Security.FormsAuthenticationModule" />
<add name="PassportAuthentication" type="System.Web.Security.PassportAuthenticationModule" />
<add name="RoleManager" type="System.Web.Security.RoleManagerModule" />
<add name="UrlAuthorization" type="System.Web.Security.UrlAuthorizationModule" />
<add name="FileAuthorization" type="System.Web.Security.FileAuthorizationModule" />
<add name="AnonymousIdentification" type="System.Web.Security.AnonymousIdentificationModule" />
<add name="Profile" type="System.Web.Profile.ProfileModule" />
<add name="ErrorHandlerModule" type="System.Web.Mobile.ErrorHandlerModule, System.Web.Mobile, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
<add name="ServiceModel" type="System.ServiceModel.Activation.HttpModule, System.ServiceModel.Activation, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="UrlRoutingModule-4.0" type="System.Web.Routing.UrlRoutingModule" />
<add name="ScriptModule-4.0" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
这里仅仅展示用户验证,本地化和防止用户重复提交。
2.用户验证
通过判断Session中是否保存了UserName来判断用户是否已经登录系统。
用户验证的事件必须是在HttpApplication的AcquireRequestState及其以后的事件中处理,因为Session只有在AcquireRequestState及其以后的事件中才是可用的。
app.Context.Handler is IRequiresSessionState || app.Context.Handler is IReadOnlySessionState
对Css,js文件不需要进行验证,比如当Login.aspx引用了js文件的时候,那么即使用户没有登录,那么Login.aspx和js文件都是可以被访问到。
HttpMOdule的代码如下:
public class LoginHttpModule : IHttpModule
{
#region IHttpModule 成员
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.AcquireRequestState += new EventHandler(loginCheck_PostAcquireRequestState);
}
#endregion
private void loginCheck_PostAcquireRequestState(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
if (app.Context.Handler is IRequiresSessionState || app.Context.Handler is IReadOnlySessionState)
{
if (app.Context.Session["UserName"] == null &&
!app.Context.Request.RawUrl.EndsWith("Login.aspx"))
app.Context.Response.Redirect("~/Login.aspx");
}
}
}
3.本地化,比如根据不同的国家设置不同的日期格式
/// <summary>
/// 当然可可以在asp.net的页面中override InitializeCulture.
/// </summary>
public class LocalizationHttpModule : IHttpModule
{
#region IHttpModule 成员
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.PreRequestHandlerExecute += new EventHandler(loc_PreRequestHandlerExecute);
}
#endregion
private void loc_PreRequestHandlerExecute(object sender, EventArgs e)
{
//被注释代码仅仅处理Page
//HttpApplication app = (HttpApplication)sender;
//if (app.Context.Handler is Page)
//{
// (app.Context.Handler as Page).PreRender += new EventHandler(PreRender);
//}
//可以处理Page,实现IHttpHandler,WebService中的国际化问题。
//法国的日期日期部分的格式 日/月/年
System.Threading.Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR");
System.Threading.Thread.CurrentThread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture;
}
}
当然本地化也可以在asp.net的页面中override InitializeCulture中来处理.但是对于asmx类型的请求在InitializeCulture中是无法处理的。
4.防止用户重复提交
用户重复提交流程:
解决用户重复提交的流程如下:
HttpModule的代码主要是在页面上输出token.
代码如下:
public class TokenHttpModule : IHttpModule
{
#region IHttpModule 成员
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.PostAcquireRequestState += new EventHandler(token_PostAcquireRequestState);
}
#endregion
private void token_PostAcquireRequestState(object sender, EventArgs e)
{
HttpApplication app = (HttpApplication)sender;
//因为需要访问Session
if (app.Context.Handler is Page)
{
(app.Context.Handler as Page).PreRenderComplete += new EventHandler(PreRenderComplete);
}
}
private void PreRenderComplete(object sender, EventArgs arg)
{
Page page= (sender as Page);
if (page == null) return;
if (page.Form == null) return;
HttpSessionState session=HttpContext.Current.Session;
if (session[TokenUtil.TokenKey_InSessions] != null)
{
HiddenField ctrl = new HiddenField();
ctrl.ID = TokenUtil.TokenKey_InPages;
ctrl.Value = session[TokenUtil.TokenKey_InSessions].ToString();
ctrl.ClientIDMode = ClientIDMode.Static;
page.Form.Controls.Add(ctrl);
}
}
}
TokenUtil的代码如下:
public static class TokenUtil
{
public const string TokenKey_InSessions="tokenkey";
public const string TokenKey_InPages = "Param_TokenKey";
public static bool IsValidToken()
{
HttpContext context = HttpContext.Current;
HttpSessionState session = context.Session;
if (session == null) return false;
if (session[TokenKey_InSessions] == null) return false;
if (String.IsNullOrWhiteSpace(context.Request[TokenKey_InPages])) return false;
return session[TokenKey_InSessions].ToString().Equals(context.Request[TokenKey_InPages]);
}
public static void SaveToken()
{
HttpContext context = HttpContext.Current;
HttpSessionState session = context.Session;
if (session == null) return;
session[TokenKey_InSessions] = Guid.NewGuid().ToString();
}
public static void ResetToken()
{
HttpContext context = HttpContext.Current;
HttpSessionState session = context.Session;
if (session == null) return;
session.Remove(TokenKey_InSessions);
}
}
编辑界面的代码如下:
public partial class PersonEdit : BasePage
{
protected void Page_Load(object sender, EventArgs e)
{
if (!this.IsPostBack)
{
base.SaveToken();
}
}
protected void Button1_Click(object sender, EventArgs e)
{
if (!base.IsValidToken())
{
//Response.Redirect("InValidToken.aspx");
Label1.Text = "重复提交!!";
return;
}
int i = 0;
PersonService ps = new PersonService();
ps.SavePerson(this.TextBox1.Text);
base.ResetToken();
Response.Redirect("PersonList.aspx");
}
}
代码文件下载地址.