Thursday, November 20, 2008 11:43 PM lasseeskildsen

Demystifying the Page_Load Method

During a debugging session today Søren and I got to talk about how the Page_Load method on a page is called.

The method is called after the Load event and is declared like this:

   1: protected void Page_Load(object sender, EventArgs e)
   2: {
   3:     
   4: }

 

Taking a look at the Page class in Reflector shows the it inherits from the TemplateControl class. All the following code is extracted using Reflector.

page_load1

If we take a look at the TemplateControl class we can see that following constants are declared:

   1: private const string _onTransactionAbortEventName = "OnTransactionAbort";
   2: private const string _onTransactionCommitEventName = "OnTransactionCommit";
   3: private const string _pageAbortTransactionEventName = "Page_AbortTransaction";
   4: private const string _pageCommitTransactionEventName = "Page_CommitTransaction";
   5: private const string _pageDataBindEventName = "Page_DataBind";
   6: private const string _pageErrorEventName = "Page_Error";
   7: private const string _pageInitCompleteEventName = "Page_InitComplete";
   8: private const string _pageInitEventName = "Page_Init";
   9: private const string _pageLoadCompleteEventName = "Page_LoadComplete";
  10: private const string _pageLoadEventName = "Page_Load";
  11: private const string _pagePreInitEventName = "Page_PreInit";
  12: private const string _pagePreLoadEventName = "Page_PreLoad";
  13: private const string _pagePreRenderCompleteEventName = "Page_PreRenderComplete";
  14: private const string _pagePreRenderEventName = "Page_PreRender";
  15: private const string _pageSaveStateCompleteEventName = "Page_SaveStateComplete";
  16: private const string _pageUnloadEventName = "Page_Unload";

Right there on line 10 you can the name of the Page_Load method declared. If you declare a method with the same signature as the default generated Page_Load method, but name them after the constants declared above they will automatically get called appropriately.

The reason why, is found in the HookUpAutomaticHandlers() method on the TemplateControl class which eventually calls the GetDelegateInformationFromMethod(string, IDictionary) method. The source looks something like this (please excuse the variable naming). Methods are listed in the order which they are called:

   1: internal void HookUpAutomaticHandlers()
   2: {
   3:     if (this.SupportAutoEvents)
   4:     {
   5:         object obj2 = _eventListCache[base.GetType()];
   6:         IDictionary dictionary = null;
   7:         if (obj2 == null)
   8:         {
   9:             lock (_lockObject)
  10:             {
  11:                 obj2 = _eventListCache[base.GetType()];
  12:                 if (obj2 == null)
  13:                 {
  14:                     dictionary = new ListDictionary();
  15:                     this.GetDelegateInformation(dictionary);
  16:                     if (dictionary.Count == 0)
  17:                     {
  18:                         obj2 = _emptyEventSingleton;
  19:                     }
  20:                     else
  21:                     {
  22:                         obj2 = dictionary;
  23:                     }
  24:                     _eventListCache[base.GetType()] = obj2;
  25:                 }
  26:             }
  27:         }
  28:         if (obj2 != _emptyEventSingleton)
  29:         {
  30:             dictionary = (IDictionary) obj2;
  31:             foreach (string str in dictionary.Keys)
  32:             {
  33:                 EventMethodInfo info = (EventMethodInfo) dictionary[str];
  34:                 bool flag = false;
  35:                 MethodInfo methodInfo = info.MethodInfo;
  36:                 Delegate delegate2 = base.Events[_eventObjects[str]];
  37:                 if (delegate2 != null)
  38:                 {
  39:                     foreach (Delegate delegate3 in delegate2.GetInvocationList())
  40:                     {
  41:                         if (delegate3.Method.Equals(methodInfo))
  42:                         {
  43:                             flag = true;
  44:                             break;
  45:                         }
  46:                     }
  47:                 }
  48:                 if (!flag)
  49:                 {
  50:                     IntPtr functionPointer = methodInfo.MethodHandle.GetFunctionPointer();
  51:                     EventHandler handler = new CalliEventHandlerDelegateProxy(this, functionPointer, info.IsArgless).Handler;
  52:                     base.Events.AddHandler(_eventObjects[str], handler);
  53:                 }
  54:             }
  55:         }
  56:     }
  57: }
  58:  
  59: private void GetDelegateInformation(IDictionary dictionary)
  60: {
  61:     if (HttpRuntime.IsFullTrust)
  62:     {
  63:         this.GetDelegateInformationWithNoAssert(dictionary);
  64:     }
  65:     else
  66:     {
  67:         this.GetDelegateInformationWithAssert(dictionary);
  68:     }
  69: }
  70:  
  71: private void GetDelegateInformationWithNoAssert(IDictionary dictionary)
  72: {
  73:     if (this is Page)
  74:     {
  75:         this.GetDelegateInformationFromMethod("Page_PreInit", dictionary);
  76:         this.GetDelegateInformationFromMethod("Page_PreLoad", dictionary);
  77:         this.GetDelegateInformationFromMethod("Page_LoadComplete", dictionary);
  78:         this.GetDelegateInformationFromMethod("Page_PreRenderComplete", dictionary);
  79:         this.GetDelegateInformationFromMethod("Page_InitComplete", dictionary);
  80:         this.GetDelegateInformationFromMethod("Page_SaveStateComplete", dictionary);
  81:     }
  82:     this.GetDelegateInformationFromMethod("Page_Init", dictionary);
  83:     this.GetDelegateInformationFromMethod("Page_Load", dictionary);
  84:     this.GetDelegateInformationFromMethod("Page_DataBind", dictionary);
  85:     this.GetDelegateInformationFromMethod("Page_PreRender", dictionary);
  86:     this.GetDelegateInformationFromMethod("Page_Unload", dictionary);
  87:     this.GetDelegateInformationFromMethod("Page_Error", dictionary);
  88:     if (!this.GetDelegateInformationFromMethod("Page_AbortTransaction", dictionary))
  89:     {
  90:         this.GetDelegateInformationFromMethod("OnTransactionAbort", dictionary);
  91:     }
  92:     if (!this.GetDelegateInformationFromMethod("Page_CommitTransaction", dictionary))
  93:     {
  94:         this.GetDelegateInformationFromMethod("OnTransactionCommit", dictionary);
  95:     }
  96: }
  97:  
  98: private bool GetDelegateInformationFromMethod(string methodName, IDictionary dictionary)
  99: {
 100:     EventHandler handler = (EventHandler) Delegate.CreateDelegate(typeof(EventHandler), this, methodName, true, false);
 101:     if (handler != null)
 102:     {
 103:         dictionary[methodName] = new EventMethodInfo(handler.Method, false);
 104:         return true;
 105:     }
 106:     VoidMethod method = (VoidMethod) Delegate.CreateDelegate(typeof(VoidMethod), this, methodName, true, false);
 107:     if (method != null)
 108:     {
 109:         dictionary[methodName] = new EventMethodInfo(method.Method, true);
 110:         return true;
 111:     }
 112:     return false;
 113: }

On line 15 the delegates are all collected into a dictionary using the GetDelegateInformationFromMethod method. On lines 31 to 53 the MethodInfos are collected, and handlers is added to the events collection. When raising the associated events (e.g. Load for the Page_Load method), the MethodInfo is invoked from the Events-collection.

The HookUpAutomaticHandlers method is called from the SetIntrinsics method on the Page class.

Summary

So to sum up, the Page_Load method and all it's colleagues (Page_Init, Page_DataBind etc.) are not invoked using slow reflection, but is simply grabbed as a named delegate. I'm not used to use these delegates, but from now on I don't see any reason why not to use them, instead of overriding the OnInit, OnPreRender etc. methods.

Well, nothing new in this one, just stuff thats makes me wonder :o)

Filed under: ,

Comments

# re: Demystifying the Page_Load Method

Thursday, November 20, 2008 6:31 PM by Peter

" from now on I don't see any reason why not to use them"

Here's a reason, how many words do you need to explain how it works as opposed to explaining the override method. Using the override makes far more sense, it's part of the language and OOP. It doesn't give you this impression that you're living on a cloud. The code you write behind the aspx is actually a class that inherits from a different class. The Page_Load method is an abomination of object oriented design. It introduced a lot of confusion. What if you override OnLoad and use Page_Load at the same time, which one gets called first and why? If you have to fire up VS to test it out you've already proven my point.

# re: Demystifying the Page_Load Method

Friday, November 21, 2008 2:25 AM by lasseeskildsen

Yes, you're right about it takes a lot more explaining. In addition, Page_Load is called after base.OnLoad, so if you want to do some stuff before that, overriding the OnLoad can be the way to go.

# re: Demystifying the Page_Load Method

Friday, November 21, 2008 5:38 PM by Dennis Riis

I am with Peter on this one. The problem with using a Page_Load method, is the fact that it is somehow "magic". It is not apparent why it gets called, in fact you just spent an entire blog post entire blog post explaining why. I'm in favour of overriding the appropiate methods (and you still get to decide when base.OnXxx is called, mind you) at all times.

I think overriding a method promotes OO design and thinking, since the principle will work in whatever environment you might find yourself in, while these "magic" method names will only work in ASP .NET when writing code that inherits TemplateControl.

# re: Demystifying the Page_Load Method

Sunday, November 23, 2008 5:25 AM by Soren

Great post.nice  to get an expanstion on why it worked.

# re: Demystifying the Page_Load Method

Sunday, November 23, 2008 6:38 PM by lasseeskildsen

First off, I really appreciate you guy's opinions.

I usually use the override approach, but I have two concerns on this one:

- The purpose of e.g. OnLoad should be to raise the Load-event.

- Overriding the method implies you want to change the method's behavior.

Ideally, I would not add any business logic to the OnLoad method. Say for instance I inherit page into MyPage, overrides OnLoad, inherits MyPage and overrides OnLoad. This way I cannot raise the Load event without triggering the logic added to the OnLoad method. This can be perfectly ok, but if I want to avoid this, the Page_Load approach can be used.

I'm not saying that either one is right or wrong, just that is has different usages, e.g. to avoid those nasty state variables.

Thoughts?

Leave a Comment

(required) 
(required) 
(optional)
(required)