ASP.NET Page那点事(5)

2012 年 10 月 21 日4730

  不用基类也能扩展

  在一个ASP.NET网站中,如果想为所有的页面添加某个功能,我们通常会想到使用基类的方式去实现。这的确是一种很有效的方法,但不并唯一的方法,还有一种方法也能容易实现这个需求,那就是使用PageAdapter的方式。

  在我写博客的过程中,我写了很多示例页面,页面中包含一些提交按钮是少不了的事情,然而,为了能让示例代码看起来比较原始(简单),我尽量不使用服务器控件,因此就要面临提交按钮的事件处理问题。在博客【细说 ASP.NET Cache 及其高级用法】的示例代码中,我开始采用PageAdapter这种方法,它可以让代码很简单,而且以后也方便以后重用(只需要复制几个文件即可)。

  或许有些人认为:扩展所有页面的功能,还是使用基类比较好。

  对于这个观点,我完全不反对。

  但是,PageAdapter的好处在于它的可插拔性(类似HttpModule的优点)。不过,我当时设计这种扩展方式只是想再换个方法尝试一下而已。

  其实微软设计PageAdapter的本意是为了处理各种浏览器的兼容问题,但是我把这个功能用到扩展Page的功能上去了。 HttpModule可以进入到ASP.NET请求管线的任何阶段,但它就是进入不了页面的生命周期中,有了这个方法,我们就可以采用HttpModule这种【外挂】式的方法进入到页面生命周期中,我认为是很有意义的。

  方法多了,我想不是件坏事。每种方法都有适合它们的应用场合,了解更多的方法,以后就能做出更优秀的设计。

  这次想到这个话题是因为前面的博客【细说ASP.NET Forms 身份认证】中的示例代码。有些人看到那些代码,发现代码的运行方式比较特别,所以,今天我就打算着重介绍这种方法。

  我们再来回顾一下以前博客中的示例代码,首先从页面代码开始:

  

普通登录

包含【用户信息】的自定义登录

  在这段页面代码中,我定义了二个表单,它们包含各自的提交按钮(其实这也只是部分代码)。

  再来看后台处理代码是如何响应提交请求的:

  public partial class _Default : System.Web.UI.Page { [SubmitMethod(AutoRedirect = true)] public void NormalLogin() { // 省略登录处理代码。 // 如果需要知道这段代码可以浏览下面的网址: // http://http://www.zjjv.com//blogs.com/fish-li/archive/2012/04/15/2450571.html } [SubmitMethod(AutoRedirect = true)] public void CustomizeLogin() { // 省略登录处理代码。 // 如果需要知道这段代码可以浏览下面的网址: // http://http://www.zjjv.com//blogs.com/fish-li/archive/2012/04/15/2450571.html }

  注意观察,这二个C#方法的名称与页面二个submit按钮的name属性相同,因此可以猜测到这二个C#方法可以处理那二个submit按钮的提交请求。那么这二段代码是如何运行起来的呢?有些人或许看到了[SubmitMethod]的使用,认为与它们有关。其实这种说法并不正确,我也可以完全不使用它们。请记住:Attribute永远只是一个标记,它不可能让代码自动运行起来。

  前面的代码能运行起来,与App_Browsers目录下的Page.browser文件有关,此文件的代码如下:

  

  这里定义了一个MyPageAdapter,它用于Page控件的请求过程。 refID="Default" 表示是对ASP.NET定义的Default.browser文件补充一些配置,它将能匹配来自所有浏览器的请求。

  我再来看一下MyPageAdapter的代码:

  [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class SubmitMethodAttribute : Attribute { public bool AutoRedirect { get; set; } } internal sealed class MethodInvokeInfo { public MethodInfo MethodInfo; public SubmitMethodAttribute MethodAttribute; } public class MyPageAdapter : System.Web.UI.Adapters.PageAdapter { private static readonly Hashtable s_table = Hashtable.Synchronized(new Hashtable()); private static MethodInvokeInfo[] GetMethodInfo(Type type) { MethodInvokeInfo[] array = s_table[type.AssemblyQualifiedName] as MethodInvokeInfo[]; if( array == null ) { array = (from m in type.GetMethods(BindingFlags.Instance | BindingFlags.Public) let a = m.GetCustomAttributes( typeof(SubmitMethodAttribute), false) as SubmitMethodAttribute[] where a.Length > 0 select new MethodInvokeInfo { MethodInfo = m, MethodAttribute = a[0] }).ToArray(); s_table[type.ToString()] = array; } return array; } protected override void OnLoad(EventArgs e) { base.OnLoad(e); if( Page.Request.Form.AllKeys.Length == 0 ) return; // 没有提交表单 MethodInvokeInfo[] array = GetMethodInfo(Page.GetType().BaseType); if( array.Length == 0 ) return; foreach( MethodInvokeInfo m in array ) { if( string.IsNullOrEmpty(Page.Request.Form[m.MethodInfo.Name]) == false ) { m.MethodInfo.Invoke(Page, null); if( m.MethodAttribute.AutoRedirect && Page.Response.IsRequestBeingRedirected == false ) Page.Response.Redirect(Page.Request.RawUrl); return; } } } }

  这段代码并不长,核心代码更是比较少。

  代码中,最重要的一块是MyPageAdapter的实现,它继承了System.Web.UI.Adapters.PageAdapter,并重写了OnLoad方法(相当是在重写Page的OnLoad方法),也正是由于这个重写,代码才有机会在页面的生命周期中被执行,这一点是HttpModule做不到的。

  在OnLoad方法中做了以下事情:

  1. 检查是不是发生了表单提交的操作。

  2. 获取当前页面类型的所有[SubmitMethod]修饰过的方法。

  3. 检查提交的表单数据中,是否存在与name对应的C#方法名。

  4. 如果找到一个匹配的方法名,则调用。

  5. 如果在[SubmitMethod]中设置了AutoRedirect=true,则引发重定向。

  注意:如果不调用base.OnLoad(e); 那么页面的Load事件根本不会发生。也就是说:PageAdapter.OnLoad的调用时间要早于Page.Onload方法。

  由于这段代码仅供我写示例代码时使用,因此并没有检查要调用的方法的参数是否满足条件,也没有优化刻意去优化它的性能。在我的设计中,被调用的方法应该是无参的,因此是容易判断的,而且可以使用一个固定签名的委托去优化它的,这些细节留着以后再去完善它吧!

0 0