概述
从前面的章节我们知道HTTP Handler提供了类似于ISAPI Server Extention的功能,而HttpModule实现了类似于ISAPI Filter的功能。使用自定义的Handler会覆盖系统默认的Handler,而Module是可以多个同时存在的。
HttpHandler与HttpModule简单来说其实都是对一个请求上下文的处理,但它们两个所处的功能角色是完全不一样的。我暂时还想不出一个既简单又能体现HttpHandler优点的场景来,其实Page类就是一个实现了IHttpHandler的类,在Ajax中也有相关的实现,以及Asp.Net Forums V2中有两个比较简单的实现(AvatarHttpHandler和VCardHttpHandler),大家可以去参考一下。
下面是一个检查用户是否登录及模块授权的HttpModule示例。
场景
一个网站,有一个首页(Default.aspx),一个登录页面(Login.aspx),两个模块(模块1和模块2)。
一、 当用户在未登录的情况下访问网站的任一个页面都会跳转到登录页面要求用户登录,登录完成后跳转到网站首页并在每个页面上显示欢迎词。
二、 假设有两个用户,一个“文野”,一个“stwyhm”,文野可以访问模块1,stwyhm可以访问模块2,当他们访问各自有权访问的模块时,显示模块给出的欢迎词,如果访问的模块没有访问权限,给出错误提示。其它用户只能访问指定模块以后的页面。
示例
using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
namespace AuthorizationModule
{
/// <summary>
/// 说明:检查用户登录的Module
/// 作者:文野
/// 联系:stwyhm.cnblogs.com
/// </summary>
public class UserAuthorizationModule : IHttpModule
{
#region IHttpModule 成员
public void Dispose()
{ }
public void Init(HttpApplication context)
{
context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
}
void context_AcquireRequestState(object sender, EventArgs e)
{
// 获取应用程序
HttpApplication application = (HttpApplication)sender;
// 检查用户是否已经登录
if (application.Context.Session["UserName"] == null || application.Context.Session["UserName"].ToString().Trim() == "")
{
// 获取Url
string requestUrl = application.Request.Url.ToString();
string requestPage = requestUrl.Substring(requestUrl.LastIndexOf('/') + 1);
// 如果请求的页面不是登录页面,刚重定向到登录页面。
if (requestPage != "Login.aspx")
application.Server.Transfer("Login.aspx");
}
else
{
// 已经登录,向每个请求的页面打印欢迎词。
application.Response.Write(string.Format("欢迎您!{0}!", application.Context.Session["UserName"]));
}
}
#endregion
}
/// <summary>
/// 说明:检查用户是否有权使用模块的Module
/// 作者:文野
/// 联系:stwyhm.cnblogs.com
/// </summary>
public class SystemModuleAuthorizationModule : IHttpModule
{
#region IHttpModule 成员
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.AcquireRequestState += new EventHandler(context_AcquireRequestState);
}
void context_AcquireRequestState(object sender, EventArgs e)
{
HttpApplication application = (HttpApplication)sender;
// 如果用户未登录,则无需检查模块授权,因为请求会被用户登录Module重定向到登录页面。
if (application.Session["UserName"] == null)
return;
// 获取用户名和Url
string userName = application.Session["UserName"].ToString();
string url = application.Request.Url.ToString();
// 如果用户没有被授权,请求被终止,并打印提示信息。
if (!Validator.CanUseModule(userName, url))
{
application.CompleteRequest();
application.Response.Write(string.Format("对不起!{0},您无权访问此模块!", userName));
}
}
#endregion
}
public class Validator
{
/// <summary>
/// 检查用户是否被授权使用模块。
/// 文野可以使用模块,stwyhm可以使用模块,所有用户都可以请求限定模块以外的页面
/// </summary>
/// <param name="userName"></param>
/// <param name="url"></param>
/// <returns></returns>
public static bool CanUseModule(string userName, string url)
{
if (!url.Contains("模块"))
return true;
else if (userName == "文野" && url.Contains("模块1"))
return true;
else if (userName == "stwyhm" && url.Contains("模块2"))
return true;
else
return false;
}
}
}
页面中除了简单的登录保存用户名到Session的代码及一些Html呈现代码外无任何代码。
执行结果
第一步:打开网站首页,因示登录被跳转到登录页面
第二步:登录成功后跳转到首面。
第三步:访问有权进入的模块1
第四步:访问无权进入的模块2
开发注意点
无论是通过Url发出请求,还是一个按钮引起的页面回发,对IIS来说是一样的,都是一次请求。而HttpModule中的事件一般都在页面事件前,特别是控件事件,所以如果在HttpModule的事件中对请求进行过滤处理后就不会执行到页面事件或控件事件,这就是上面示例的UserAuthorizationModule代码中为什么要对请求页面是否是Login.aspx进行判断的原因了。
总结
这里两个自定义的HttpModule实现了它们各自所要达到的过滤请求的功能,一个限制用户登录,一个限制模块访问,当然实际应用中比这要复杂许多。这样的验证方式是简单的、安全的,代码在修改时只要修改相应的HttpModule就可以了,无需在每个页面都写相同的验证代码,也不会发生在Url地址栏里输入一段Url就可以跳过登录及其它验证的情况了。在这两个HttpModule中,因为都要涉及到对Session的访问,所有都使用了AcquireRequestState事件,大家可以根据实际情况使用不同的事件,可以参考我前面文章中的那副HttpModule生命周期图。