In ASP.NET 1.x the primary code model is based on CodeBehind, which uses inheritance and code generation to manage the code that you as the developer work with. In V1 you create a base class into which the VS.NET Editor generates control definitions from the HTML markup – each control gets an instance field on the form and any explicit event hookups are stuck into the InitalizeComponent section of the page. The ASP.NET engine then generates a class from the HTML markup and inherit from your CodeBehind form, in effect providing the functionality you implemented in that class in addition to the generated code.
Some problems with the 1.1 model
The concept’s pretty straight forward, but in VS.NET 2003 there are lots of problems with this approach that had to do with the code generation into the CodeBehind class. In many cases control declarations would not make it, or event declarations would get lost. Who’s ever lost their datagrid events after saving a page or copying a couple of controls around on it? Worse, if you removed a control from a page VS.NET wouldn’t clean up after itself and leave the declaration and event hook up code in the CodeBehind class. It’s even worse if you were using a plain text editor to manage this on your own, as you’d have to remember to match control definitions in the markup to control definitions and event hookups in the class, so you always have to maintain code in two places.
How ASP.NET 2.0 addresses these issues
In ASP.NET 2.0, the CodeBeside model addresses this issue with Partial Classes. The core model is still the same and based on inheritance, but the rather than using a single class definition ASP.NET 2.0 breaks up the class into two partial classes that get combined at compile time. There’s the class that you write your code into – Page_Load() and any event implementations as well as any field definitions. And then there’s a generated class that contains all the Control definitions and event hookups. When the page is compiled both partial classes are combined and that class is then used as the base that is inherited by the Page parser generated class.
The advantage of this approach is that there’s only one place that code generation occurs and it’s at page compile time. So even if you use a plain text editor to create your CodeBeside page it will automatically pick up your control definitions. No longer does VS.NET have to parse the document at design time (well it still does to provide Intellisense support) to create control definitions to inject into your source document. This is of course much more reliable and consistent and makes for much cleaner code. You only see the code you actually wrote yourself.
Another advantage of the new page model is the fact that individual pages can (but don’t have to) compile into individual classes. ASP.NET 2.0 can compile pages individually or at the directory level. When you run ASP.NET in debug mode it uses single page compilation. Single page compilation is an awesome feature for debugging pages inside of VS.NET 2005. Because pages can individually compile when they are run you can now execute a page, make a change and immediately re-run the page without explicitly recompiling your project. This is much faster than the 1.1 change and compile steps you had to go through especially on larger projects.
This works even if you are running in Debug mode; so you can step through code, find a problem, fix it in the editor and simply re-run the page. This is similar to the way single source debugging worked in ASP.NET 1.x but it now can also work with the more sophisticated and more realistic CodeBeside approach. As you might expect this is a huge timesaver, especially for large projects as you’re saving a long compile step, although the page recompilation is not super fast at least in Beta 2.
How it works
ASP.NET 2.0 makes use of partial classes to provide this improved stability in the page model. The core model hasn’t change all that much from ASP.NET 1.1 as the combined classes are still very similar to the single codebehind page used in 1.1. You still have page inheritance where the ASPX page inherits from your partial class and the generated control definitions.
At the CodeBeside level there are two partial classes: There’s the class that you write your code into – Page_Load() and any event methods as well as any field and property definitions and events that you want to define. This page is the ‘master’ partial class as it provides the inheritance structure where you can tell it what to inherit from. Typically it looks like this:
namespace Westwind.WebStore
{
public partial class UploadItemPicture : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
… implementation here
}
protected void btnGo_Click(object sender, EventArgs e)
{
… implementation here
}
}
}
Note that a partial class does not contain control definitions or InitializeComponent that usually hooks up the events. ASP.NET generates the surrogate partial class at compile time that represents the ASPX page code ASP.NET generates another partial class that contains all the control definitions, the event delegate hookups and setting of Page settings (like EnableSessionState, Trace etc.). Both of these partial classes combine to make up the base class that the ASPX page inherits.
This base page is inherited by the class that the ASP.NET page parser creates. The Page parser runs through all of the ASPX script code and creates a class from it that is made up of literal HTML content, expressions that are written straight out into the HtmlTextWriter object, and the Control tree that is parsed and then rendered.
You can see this to a large degree if you throw the generated, compiled assembly into .NET Reflector. In case you don’t know about Reflector, run and don’t walk to get it – it’s an awesome tool to figure out how code works, be it your own, Microsoft’s or cough, cough any third parties that isn’t obfuscating their code.
ASP.NET generates assemblies from your classes using the ASP.NET compiler (ASPNET_COMPILER.EXE). You can pre-compile your project and look at the generated assemblies. For more info click here. What you’ll find is something that looks like the Figure below which shows a composite assembly created from the precompiler with the following command line:
"<frameworkPath>\aspnet_compiler.exe" -f -v "/wwstore" "d:\temp\deploy\wwstore"
I used a virtual IIS path (-v /wwstore) and compiled it into a fixed directory that is to overwritten (-f). This option create one assembly per directory of the Web application which combines all of the pages of the directory into one assembly all starting APP_WEB as the name prefix. The assemblies are randomly named, so finding the right one is not easy if you have more than one directory – usually you can guess by looking at the file sizes.
Once you find the right assembly in Reflector it might look something like the figure below. In my case I’ll look at the UploadItemPicture page in the Web. Notice the two classes related to this in the ASP namespace and the Westwind.WebStore namespace:
The class on the bottom (UploadItemPicture) is my user class, which is the merged version of my partial class, plus the generated page control code and event hookup code. If you look at the properties and methods of this class you will find all the control definitions, which although they weren’t defined in your source code are now there. All of that comes from the generated partial class. The combined class looks pretty similar to an ASP.NET 1.x single CodeBehind class…
If you look closely at the figure above you can see that admin_uploaditempicture_aspx inherits from my UploadItemPicture class. The inherited class contains the actual page construction logic for instantiating each of the controls (__Build<ControlName>) and then renders the page using __BuildControlTree(). BuildControlTree is the top level routine that fires of the page rendering process. It renders the form literals, and then renders each of the controls inside of the page. It does this using the containership hierarchy so in this case there’s RenderForm1 which in turn renders each of the child controls. Depending on where controls appear in your page this may look differently. In my example above all other controls are inside of the Form tag, so the other controls are rendered as part of internal code of the Form object. Each control (except the form tag which requires special handling) is rendered using RenderControl() for each of the control in the Controls Collection and each control manages its own rendering process.
BTW, if you quickly want to see the generated page source for the Partial class that has the above definitions in it – you can put an error into your script page in an expression. For example:
<b>itemimages/<%= this.ImageFileName_INVALID %><br>
On the resulting error page, click on the Show Complete Compilation Source and you should see the code displayed. You’ll want to trigger an error in the ASPX page to make this work, not in the CodeBeside code, because that shows you just the compilation code of your code behind partial class. It’s too bad we can’t see the combined class or at least both classes that make up the composite.
What to watch out for
So far this all sounds really nice – ASP.NET 2.0 provides a solution to the nasty control mismatch problems that so often would creep up in V1 without really giving up any features. Well, mostly, but there are a few issues you need to be careful of.
Most importantly you have to realize that part of your class is dynamically generated at compile time. What this means is that some things are not necessarily available to your code.
Let’s presume for a second I wanted to subclass an existing page and use it for a custom class like this:
public partial class UploadItemPictureEx : Westwind.WebStore.UploadItemPicture
{
protected void Page_Load(object sender, EventArgs e)
{
}
}
First off you’ll notice that VS.NET doesn’t show UploadItemPicture as an identified class (it’s black – it should be Teal colored text for a type name). If you try to run this page it fails too. ASP.NET complains that it can’t find Westwind.WebStore.UploadItemPicture. This even though it exists in the same assembly! You can’t reference other Page classes in your code like this. This is true whether the code is in the same assembly or not. If the UploadItemPicture.aspx page were in a different directory it would end up in a completely different assembly and not visible at all to the current assembly because it doesn’t receive a reference to any of the other directories.
This is a pretty major change in the way ASP.NET works. There’s a workaround for this, but it’s not pretty:
- Create a base class for your page and make it inherit from Page
- Store this Page either in APP_CODE or in a separate assembly so it’s visible to your pages
- Implement your Public interface on this base Page
- Then create your Page and make it inherit from this base Page
- Now when you need to cast, cast to the base type
Yes, this is pretty ugly, but it works and it’s not too bad of a workaround once you know what to do. I doesn’t change the way you work much but it requires one additional class and remembering to expose the base methods and properties you want to access at the base class level. You still implement all your code at the UserControl level – the base class is merely there to give you the strongly typed base interface that you can use for Intellisense and getting the proper typecasting to access the interface without Reflection.
User Controls
You have similar issues with controls, but the rules are a little more lenient because apparently Microsoft realized that it’s pretty vital that you can reference user controls from your code. If the control lives in the same directory (ie. it gets compiled into the same assembly) as your current page the user control class will always be visible and you can create and simply cast to it. So the following works just fine:
Control ctl = this.LoadControl("MyUserControll.ascx");
this.AddParsedSubObject(ctl);
MyUserControll CastCtl = (MyUserControll) ctl;
CastCtl.CustomProperty = "Hell on wheels is no big deal...";
If the control lives in a different directory (ie. it gets compiled into a separate assembly) you need to make sure that the page that you are loadign the control onto has a <%@ Register %> tag in place on the page, which provide the needed assembly reference. If you DON’T have a the Register tag on the page the following will fail with an error that MyUserControll cannot be found:
Control ctl = this.LoadControl("../MyUserControll.ascx");
this.AddParsedSubObject(ctl);
MyUserControll CastCtl = (MyUserControll) ctl;
CastCtl.CustomProperty = "Helllo Dolly...";
Adding the Register tag fixes this problem. The Register tag on the page acts as an assembly import for the user control’s assembly that makes this work. In the same directory this isn’t necessary because the control exists in the current assembly.
For normal application usage this is not a big issue as in most applications developers will drop controls onto a Page visually. However, for dynamically loaded user controls things are going to be tricky – I don’t see how you can truly dynamically load a user control any more assuming you can’t add the <@ Register > directive to the page explictly. Maybe there’s some way to do the equivalent of <@ Register > in code? If there is, I couldn’t find it…
All of this worked in 1.1 because you had a single assembly and all pages and controls living in that assembly were visible with the namespace you defined. Now ASP.NET takes some liberties with our namespaces and to some degree there’s a loss of control over where our code generates because there may be multiple assemblies involved.
If you think this is confusing as heck – I agree with ‘ya. This is going to throw a lot of people for a loop. Luckily this isn’t the most common scenario but I don’t envy the folks who have designed entire architectures around dynamically loaded UserControls…
Overall Goodness
This last issue is a bit of a bummer and it smells of a design flaw that wasn't addressed early enough in the cycle. There's a lot of discussion around the page compilation and the way the ASP.NET compiler works and this has become a very hot topic primarily because nobody seems to be able to explain exactly how it's supposed to work. <g> While it sounds major, it really is a marginal issue to most developers. Most will never run into this issue.
In return you get a lot of new functionality in the page model that makes a lot more productive. The ability to keep on running and debugging your application without a full application restart alone is a huge timesaver. Add to that the cleaner environment that Partial classes bring and it’s hard to think about going back to VS.NET 2003…
Other Posts you might also like