Tuesday 4 September 2007

AutoEventWireup is evil!

Summary



AutoEvenWireup is evil, especially if you need performance and scalability.
Setting it to true will add hundreds of function calls at runtime to the code which is running you Asp.Net page (probably thousands if there have many controls), function calls that could be safely avoided manually plugging the event handlers you need to deal with.


Investigating AutoEventWireup



A few years ago, I read in a Microsoft document that the AutoEventWireup attribute of the Asp.Net @ Page directive, although handy and probably even cherished by some, especially those from a VB background, is in truth only suitable for prototyping and only whenever performance and scalability are not an issue. Well, if you wonder when performance and scalability are not an issue, you should probably read on!



Let's imagine to have an ASP.Net project called Riolo.Investigation.Web, and to put inside a folder called AutoEventWireup an ASP.Net page called True.aspx, with the corresponding True.aspx.cs code behind file.



This is the ASP.NET page source code:


<%@ Page
Language = "C#"
AutoEventWireup = "true"
Inherits = "Riolo.Investigation.Web.AutoEventWireup.True"
ValidateRequest = "false"
EnableSessionState = "false"
CodeFile = "True.aspx.cs"
%>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>True</title>

<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="CACHE-CONTROL" content="NO-CACHE" />
<meta http-equiv="PRAGMA" content="NO-CACHE" />

<link href="Riolo.Investigation.Web.AutoEventWireup.css" type="text/css" rel="stylesheet" />
</head>
<body>
<% = _message %>
</body>
</html>



This is the c# code behind file source code:


using System.Web.UI;

namespace Riolo.Investigation.Web.AutoEventWireup
{
public partial class True : Page
{
protected string _message;

private void Page_Load(object sender, System.EventArgs e)
{
if(!IsPostBack)
{
_message = string.Format
("SupportAutoEvents is {0}", base.SupportAutoEvents);
}
}
}
}



After compiling the ASP.Net project, after I get the True.aspx page from an ASP.Net enabled web server, I can see the following text on my browser:


SupportAutoEvents is True



When I take a look at the underlying HTML, I can see the following resulting source code:


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>True</title>

<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<meta http-equiv="CACHE-CONTROL" content="NO-CACHE" />
<meta http-equiv="PRAGMA" content="NO-CACHE" />

<link href="Riolo.Investigation.Web.AutoEventWireup.css" type="text/css" rel="stylesheet" />
</head>
<body>
SupportAutoEvents is True
</body>
</html>


What is happening?



What is happening is that setting the AutoEventWireup to true is telling the ASP.Net runtime engine to generate some delegates that enables us to use the methods with the following signatures to intercept the corresponding events of the ASP.Net page lifecycle:


  • Page_PreInit

  • Page_PreLoad

  • Page_LoadComplete

  • Page_PreRenderComplete

  • Page_InitComplete

  • Page_SaveStateComplete

  • Page_Init

  • Page_Load

  • Page_DataBind

  • Page_PreRender

  • Page_Unload

  • Page_Error

  • Page_AbortTransaction

  • OnTransactionAbort

  • Page_CommitTransaction

  • OnTransactionCommit



In the example before I used the Page_Load method to get the value of the SupportAutoEvent property and to put that in a nicely formatted string, which will be then printed inside the body of the resulting HTML.
Relying on the ASP.Net runtime to generate these delegates for us has a price, anyway, and this price is going to get quite expensive if our ASP.NET pages will become popular.


The price to pay



First of all, let's precise that this is an ASP.Net implementation issue, that is you get it on the platform targeted by my investigation (.Net 2.0), but some smarter future implementation could easily make it cheaper, pretty cheaper indeed. For the time being, anyway, the price is there to pay.
How high is this price, anyway? The key is an internal method of the abstract class System.Web.UI.TemplateControl called HookUpAutomaticHandlers().



There are various ways that are leading our pages and controls to HookUpAutomaticHandlers, the following is probably one of the more common:


System.Web.UI.TemplateControl.GetDelegateInformationWithNoAssert(IDictionary) : Void
System.Web.UI.TemplateControl.GetDelegateInformation(IDictionary) : Void
System.Web.UI.TemplateControl.HookUpAutomaticHandlers() : Void
System.Web.UI.Page.SetIntrinsics(HttpContext, Boolean) : Void
System.Web.UI.Page.SetIntrinsics(HttpContext) : Void
System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext) : Void
System.Web.UI.Page.ProcessRequest(HttpContext) : Void
ASP.autoeventwireup_true_aspx.ProcessRequest(HttpContext) : Void



If we take a look to HookUpAutomaticHandlers (thanks to Lutz Roeder's Reflector), we will notice that its code is executed only if the SupportAutoEvents is set to true:


internal void HookUpAutomaticHandlers()
{
if (this.SupportAutoEvents)
{
object obj2 = _eventListCache[base.GetType()];
IDictionary dictionary = null;
if (obj2 == null)
{
lock (_lockObject)
{
obj2 = _eventListCache[base.GetType()];
if (obj2 == null)
{
dictionary = new ListDictionary();
this.GetDelegateInformation(dictionary);
if (dictionary.Count == 0)
{
obj2 = _emptyEventSingleton;
}
else
{
obj2 = dictionary;
}
_eventListCache[base.GetType()] = obj2;
}
}
}
if (obj2 != _emptyEventSingleton)
{
dictionary = (IDictionary) obj2;
foreach (string text in dictionary.Keys)
{
EventMethodInfo info = (EventMethodInfo) dictionary[text];
bool flag = false;
MethodInfo methodInfo = info.MethodInfo;
Delegate delegate2 = base.Events[_eventObjects[text]];
if (delegate2 != null)
{
foreach (Delegate delegate3 in delegate2.GetInvocationList())
{
if (delegate3.Method.Equals(methodInfo))
{
flag = true;
break;
}
}
}
if (!flag)
{
IntPtr functionPointer = methodInfo.MethodHandle.GetFunctionPointer();
EventHandler handler = new CalliEventHandlerDelegateProxy(this, functionPointer, info.IsArgless).Handler;
base.Events.AddHandler(_eventObjects[text], handler);
}
}
}
}
}



That's it, setting AutoEventWireup to true will set SupportAutoEvents to true, and once HookUpAutomaticHandlers will be called, and it will be called for any page and all the controls in the page, will cause all of that code in red, using foreach, reflection, locks and especially calling GetDelegateInformation(IDictionary), which in turn will call GetDelegateInformationWithNoAssert(IDictionary dictionary). Using Reflector, let's take a look at that:


private void GetDelegateInformationWithNoAssert(IDictionary dictionary)
{
if (this is Page)
{
this.GetDelegateInformationFromMethod("Page_PreInit", dictionary);
this.GetDelegateInformationFromMethod("Page_PreLoad", dictionary);
this.GetDelegateInformationFromMethod("Page_LoadComplete", dictionary);
this.GetDelegateInformationFromMethod("Page_PreRenderComplete", dictionary);
this.GetDelegateInformationFromMethod("Page_InitComplete", dictionary);
this.GetDelegateInformationFromMethod("Page_SaveStateComplete", dictionary);
}
this.GetDelegateInformationFromMethod("Page_Init", dictionary);
this.GetDelegateInformationFromMethod("Page_Load", dictionary);
this.GetDelegateInformationFromMethod("Page_DataBind", dictionary);
this.GetDelegateInformationFromMethod("Page_PreRender", dictionary);
this.GetDelegateInformationFromMethod("Page_Unload", dictionary);
this.GetDelegateInformationFromMethod("Page_Error", dictionary);
if (!this.GetDelegateInformationFromMethod("Page_AbortTransaction", dictionary))
{
this.GetDelegateInformationFromMethod("OnTransactionAbort", dictionary);
}
if (!this.GetDelegateInformationFromMethod("Page_CommitTransaction", dictionary))
{
this.GetDelegateInformationFromMethod("OnTransactionCommit", dictionary);
}
}



As you can see, GetDelegateInformationFromMethod(..) is called a minimum 14 times from each page and a minimum of 8 time from each control in the page! GetDelegateInformationFromMethod(..) will then call System.Delegate.CreateDelegate(..) and the .Net trail will end when that will call the extern method BindToMethodInfo(..).
It is quite impressive that all of this is basically for nothing! If you think about it a few seconds, you will realize that you may save your web pages hundreds if not probably thousands of function calls just setting AutoEventWireup to false. We may then follow others strategies to handle events happening during the lifecycle of the Asp.Net pages and controls, but before introducing those, let's take a look at what happens when we set AutoEventWireup to false.


Setting AutoEventWireup to false



I created a new Asp.Net page in the AutoEventWireup folder, and I called that False.aspx, and then I created a new code behind c# file, and I called that False.aspx.cs, and then I copied inside them the code from True.aspx and from True.aspx.cs respectively. Togheter with AutoEventWireup I had to change also the values of the CodeFile and Inherits attributes of the @ Page directive.
If we compile the web site, and we ask False.aspx from an Asp.Net enabled web server, we will quickly notice that the code inside the Page_Load(..) method is not running anymore.
This is happening because at compile time the Asp.Net compiler is overriding the SupportAutoEvents property, which will now returns false, so the code inside HookUpAutomaticHandlers() is not running anymore, so no precooked delegates will be created for us at runtime.
You may confirm that going to the Temporary ASP.NET Files folder (C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\ in my machine) and drilling down to the folder containing false.aspx.[x].compiled ([x] was ef11d7f0 in my case) file. If you open that file with a text editor, you will notice it is an xml file, and if you check the assembly attribute of the preserve element, you will read the name of the assembly where the Asp.Net page has been compiled in (in my case, it was App_Web_rzqguydd.dll).
You may choose to investigate that assembly with Reflector, but you may notice that there are a set of *.cs file starting with the same of the assembly. If you open these, if you will find one of them is containing a public class named autoeventwireup_false_aspx, which is where SupportAutoEvents get overridden and set to false.


Alternative Strategies



The alternative strategy is basically that of manually create the delegate and plug the event handler, as in the following version of False.aspx.cs:


using System.Web.UI;

namespace Riolo.Investigation.Web.AutoEventWireup
{
public partial class False : Page
{
protected string _message;

protected override void OnInit(System.EventArgs e)
{
base.OnInit(e);
this.Load += new System.EventHandler(Page_Load);
}

private void Page_Load(object sender, System.EventArgs e)
{
if(!IsPostBack)
{
_message = string.Format
("SupportAutoEvents is {0}", base.SupportAutoEvents);
}
}
}
}



Instead of plugging the event handler inside the OnInit(..) method, you may plug it inside a constructor, but another alternative is that of getting rid of Page_Load(..) and using OnInit(..) directly.