Extreme ASP.NET

Codebehind and Compilation in ASP.NET 2.0

Fritz Onion

Contents

Codebehind
Compilation
Assembly Generation
Conclusion

As I write this column, the release candidates of the Microsoft® .NET Framework 2.0 and Visual Studio® 2005 have just come out, and by the time you read this, they will both already be on the shelves. It feels like it's been a long time coming.

I remember sitting in a room on the Microsoft campus in August of 2003 listening to Scott Guthrie and others (including my fellow columnist, Rob Howard) present the wide array of new features coming in ASP.NET 2.0. They astounded us with one demo after another of features that greatly simplified Web development, and did so in a pluggable and extensible fashion so that changes could be made at any level as needed during the development process.

Quite a bit has changed in the subsequent beta releases, mostly in the form of refinements, bug fixes, and control additions. However, one feature—the codebehind model—has changed rather dramatically since that first preview, primarily in response to customer feedback. Now on the cusp of the release, I thought I would take this opportunity to describe this new codebehind model, the rationale behind it, and how you as a Web developer will use it. I will also cover some of the potentially unexpected side effects of this model and how to plan for them in your designs. Note that the ASP.NET 2.0 runtime fully supports the 1.x model, so applications written for 1.x can run without modification.

Codebehind

Although the codebehind model is different in 2.0, its syntax has changed little. In fact, the change is so subtle that you may not even notice it unless you look really closely. Figure 1 shows the new codebehind syntax.

Figure 1 Syntax in ASP.NET 2.0

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" 
    CodeFile="Default.aspx.cs" Inherits="MsdnMag.Default" %>

Default.aspx.cs

namespace MsdnMag
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }
    }
}

There are two differences between this model and the previous 1.x model—the introduction of the CodeFile attribute in the @ Page directive and the declaration of the codebehind class as a partial class. As you start building the page, you will notice another difference—server-side controls no longer need to be explicitly declared in your codebehind class, but you still have complete access to them programmatically. For example, the form in Figure 2 has several server-side controls that are used programmatically in the codebehind file, but notice the absence of any explicit control declarations in the codebehind class.

Figure 2 Implicit Server-Side Control Access

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true"  
    CodeFile="Default.aspx.cs" Inherits="MsdnMag.Default" %>

<!DOCTYPE html PUBLIC "..." "...">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
     Enter your name:
     <asp:TextBox ID="_nameTextBox" runat="server" /><br />
     <asp:Button ID="_enterButton" runat="server" 
                 Text="Enter" OnClick="_enterButton_Click"/> <br />
     <asp:Label ID="_messageLabel" runat="server" />    
    </div>
    </form>
</body>
</html>

Default.aspx.cs

namespace MsdnMag
{
  public partial class Default : System.Web.UI.Page
  {
    protected void _enterButton_Click(object sender, EventArgs e)
    {
      _messageLabel.Text = "Hello there " + _nameTextBox.Text + "!";
    }
  }
}

The reason this works has to do with the partial keyword applied to your codebehind class. In addition to turning your .aspx file into a class definition with methods for rendering the page, as it has always done, ASP.NET now also generates a sibling partial class for your codebehind class that contains protected control member variable declarations. Your class is then compiled together with this generated class definition and used as the base class for the class generated for the .aspx file. The end result is that you essentially write codebehind classes the way you always have, but you no longer have to declare (or let the designer declare for you) member variable declarations of server-side controls. This was always a somewhat fragile relationship in 1.x, since if you ever accidentally modified one of the control declarations so that it no longer matched the ID of the control declared on the form, things suddenly stopped working. Now the member variables are declared implicitly and will always be correct. Figure 3 shows an example set of classes involved.

Figure 3 Class Generation with Codebehind

Class for ASPX file generated by ASP.NET

namespace ASP 
{
    public class default_aspx : MsdnMag.Default
    {
       ...
    }
}

Sibling partial class generated by ASP.NET

namespace MsdnMag 
{
  public partial class Default : IRequiresSessionState
  {
    protected TextBox  _nameTextBox;
    protected Button   _enterButton;
    protected Label    _messageLabel;
    private   HtmlForm form1;
    ...
  }
}

Codebehind partial class that you write

namespace MsdnMag 
{
  public partial class Default : Page
  {
    void _enterButton_Click(object sender, EventArgs e)
    {
       _messageLabel.Text = "Hello there " + _nameTextBox.Text + "!";
    }
  }
}

Note that the partial class model is only used if you use the CodeFile keyword in your @ Page directive. If you use the Inherits keyword without CodeFile (or with the src attribute instead), ASP.NET resorts to the 1.x codebehind style and simply places your class as the sole base class for the .aspx file. Also, if you have no codebehind at all, the class generation acts very much the same as it does in 1.x. Since ASP.NET 2.0 is backwards compatible with 1.x, there is now a range of codebehind options at your disposal.

Visual Studio 2005 will use the new partial class codebehind model for any Web Forms, and it will also happily convert Visual Studio .NET 2003 projects to use the new model as well if you use the conversion wizard. It is best, if possible, to convert all files to the new codebehind model, since some of the new features of ASP.NET 2.0 depend on it (if you're using Visual Studio, converting is pretty much the only option, since Visual Studio 2005 won't open unconverted 1.x projects). For example, strongly typed access to the Profile property bag is added to the sibling partial class for codebehind classes in 2.0, but if you use the 1.x codebehind model, that strongly typed accessor is added directly to the .aspx generated class definition, and will be unavailable to your codebehind class. This is also true for strongly typed Master Page and previous page access.

Compilation

At this point, you may be wondering why the ASP.NET team bothered to use inheritance at all with this new codebehind model. ASP.NET could easily generate all of the control variable declarations in addition to the rendering methods from the .aspx file as a partial class which could then be merged with your simplified codebehind class. This is exactly how Windows Forms works in the .NET Framework 2.0. All of the designer-generated code is placed into a sibling partial class which is then merged with your application logic and event handlers into a single Form-derived class, creating a clean separation between machine-generated code and developer code without resorting to inheritance.

Well, it turns out that the original implementation of codebehind in ASP.NET 2.0 did exactly this—the codebehind class was just a partial class that was merged with the parsed .aspx file class definition. It was simple and effective, but unfortunately, not flexible enough. The problem with this model was that it was no longer possible to deploy the codebehind files in precompiled binary assemblies along with intact .aspx files since they now had to be compiled at the same time (a restriction when using partial classes is that all partial pieces of a class must be merged during a single compilation, and class definitions cannot span assemblies). This restriction was unacceptable to many developers as they were already used to being able to deploy binary codebehind assemblies along with intact .aspx files which could then be updated in place without having to recompile. This is, in fact, the exact model used by default in Visual Studio .NET 2003, and is thus very prevalent in practice.

As a result of reintroducing the inheritance model and shifting the partial class into the base class, .aspx files can now be deployed and compiled independently from the codebehind class. To complete the picture, you need some way to generate the sibling partial classes containing control variable declarations during compilation or deployment since this was always done in the past on demand in response to requests. Enter the ASP.NET compiler.

The ASP.NET compiler (aspnet_compiler.exe) was originally introduced in ASP.NET 2.0 as a way of completely precompiling an entire site, making it possible to deploy nothing but binary assemblies (even .aspx and .ascx files are precompiled). This is compelling because it eliminates any on-demand compilation when requests are made, eliminating the first postdeployment hit seen in some sites today. It also makes it more difficult for modifications to be made to the deployed site (since you can't just open .aspx files and change things), which can be appealing when deploying applications that you want to be changed only through a standard deployment process. The compiler that ships with the release version of ASP.NET 2.0 supports this binary-only deployment model, but it has also been enhanced to support an updatable deployment model, where all source code in a site is precompiled into binary assemblies, but all .aspx and .ascx files are left basically intact so that changes can be made on the server (the only changes to the .aspx and .ascx files involve the CodeFile attribute being removed and the Inherits attribute being modified to include the assembly name). This model is possible because of the reintroduction of inheritance in the codebehind model, so that the sibling partial classes containing control declarations can be generated and compiled independently of the actual .aspx file class definitions.

Figure 4 Binary Deployment with aspnet_compiler.exe

Figure 4** Binary Deployment with aspnet_compiler.exe **

Figure 4 shows an invocation of the aspnet_compiler.exe utility using the binary deployment option, and the resulting output to a deployment directory. Note that the .aspx files in the deployment directory are just marker files with no content. They have been left there to ensure that a file with the endpoint name is present in case the "Check that file exists" option for the .aspx extension in an IIS app is set. The PrecompiledApp.config file is used to keep track of how the app was deployed and whether ASP.NET needs to compile any files at request time. To generate the "updatable" site, you would add a -u to the command line, and the resulting .aspx files would contain their original content (and not be empty marker files). Note that this functionality can also be accessed graphically through the Build | Publish Web Site menu item of Visual Studio 2005, as you can see in Figure 5. Both the command-line tool and Visual Studio rely on the ClientBuildManager class of the System.Web.Compilation namespace to provide this functionality.

Figure 5 Build | Publish Web Site Tool in Visual Studio 2005

Figure 5** Build | Publish Web Site Tool in Visual Studio 2005 **

With the aspnet_compiler utility in hand, you can work on your application without worrying about how it will be deployed for the most part, since any site can now be deployed in any of three ways—all source, all binary, or updatable (source code in binary and .aspx files in source)—without any modification to page attributes or code files used in development. This was not possible in previous releases of ASP.NET since you had to decide at development time whether to use the src attribute to reference codebehind files or to precompile them and deploy the assemblies to the /bin directory. Complete binary deployment was not even an option.

Assembly Generation

Now that compilation into assemblies can happen in one of three places (either explicitly by the developer, using aspnet_compiler.exe, or during request processing), understanding the mapping of files into assemblies becomes even more important. In fact, depending on how you write your pages, you can actually end up with an application that works fine when deployed as all source or all binary, but which fails to compile when deployed using the updatable switch.

The model ASP.NET generally uses creates separate assemblies for the contents of the App_Code directory as well as the global.asax file (if present), and then compiles all of the .aspx pages in each directory into a separate assembly. (If pages in the same directory are authored in different languages or if they have dependencies on each other through an @ Reference directive, they could also end up in separate assemblies.) User controls and Master Pages are also typically compiled independently from .aspx pages. It is also possible to configure the App_Code directory to create multiple assemblies if, for example, you wanted to include both Visual Basic® and C# source code in a project. There are some subtleties in the details of assembly creation, depending on which mode of deployment you have chosen. Figure 6 describes the components of your Web site that compile into separate assemblies based on the deployment mode you are using. (Note that I am ignoring the resource, theme, and browser directories since they don't contain code, although they are compiled into separate assemblies as well. The target assembly can also differ based on language variance and reference dependencies, as mentioned previously.)

Figure 6 Assembly Generation

  Deployment Mode
  All Source All Binary Updatable (mixed)
What compiles into a unique assembly App_Code directory
global.asax
.ascx and associated codebehind file (separate assembly for each user control)
.master and associated codebehind file (separate assembly for each master page)
All .aspx files and their code­behind files in a given directory (separate assembly per directory)
App_Code directory
global.asax
.ascx and .master files and their associated codebehind files
All .aspx files and their code­behind files in a given directory (separate assembly per directory)
App_Code directory (D)
global.asax (R)
.ascx and .master files (R)
codebehind files for .ascx and .master files (D)
All .aspx files in a given directory (separate assembly per directory) (R)
All codebehind files associated with .aspx files in a given directory (separate assembly per directory) (D)
When it's compiled Request time Deployment time (R) = Compiled at request time
(D) = Compiled at deployment time

The only other twist in the assembly generation picture is that you can use the -fixednames option of aspnet_compiler to request that each .aspx file be compiled into a separate assembly whose name remains the same across different invocations of the compiler. This can be useful if you want to be able to update individual pages without modifying other assemblies on the deployment site. It can also generate a large number of assemblies for sites of any significant size, so be sure to test your deployment before depending on this option.

If this sounds complicated, the good news is that most of the time you shouldn't have to think about which files map to separate assemblies. Your .aspx files are always compiled last, and always include references to all other generated assemblies, so typically things will just work no matter what deployment model you choose.

One of the key differences in deployment that may actually affect the way you author code in your pages is the split in compilation when using updatable deployments. When you deploy an updatable site, the codebehind files are compiled into separate assemblies prior to deployment. The classes generated from the .aspx files are not compiled until a request is actually made for a file in a directory. This is in contrast to binary deployment, in which all files are compiled prior to deployment, and to source deployment, in which all files are compiled at request time. As a simple example of how this can cause problems, consider the user control (.ascx file) in Figure 7 with an embedded property, and an associated page that uses the control and sets the property from its codebehind class.

Figure 7 User Control

MyUserControl.ascx

<%@ Control Language="C#"%>

<script runat="server">
    public string Color
    {
        get { return (string)ViewState["color"] ?? "white"; }
        set { ViewState["color"] = value; }
    }

    protected override void OnLoad(EventArgs e)
    {
        _mainPanel.BackColor = System.Drawing.Color.FromName(Color);
        base.OnLoad(e);
    }
</script>

<asp:Panel runat="server" ID="_mainPanel">
<h2>Some text</h2>
Other text
</asp:Panel>

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<%@ Register Src="MyUserControl.ascx" TagName="MyUserControl" TagPrefix="uc1" %>

<!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 runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <uc1:MyUserControl id="MyUserControl1" runat="server">
        </uc1:MyUserControl></div>
    </form>
</body>
</html>

Default.aspx.cs

using System;
using System.Web.UI;

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        MyUserControl1.Color = "purple";
    }
}

The page in Figure 7 will compile and run in either source or binary deployment mode, but will fail to compile when deployed as an updatable site since the definition of the Color property of the user control is unavailable at deployment time (this limitation also existed in the 1.x model). You can typically avoid issues like this by keeping all code in codebehind files or, at the other extreme, not using codebehind files at all and leaving code directly in .aspx and .ascx files.

Another thing to keep in mind when considering the file-to-assembly mapping is that the use of the internal keyword to prevent external assemblies from accessing methods in your classes may work in some deployment scenarios and not others, because of the different assembly mapping options. Unless you plan ahead of time which deployment option you will be using, it is probably best to avoid internal methods in your pages and stick to the type-scoped protection keywords: public, protected, and private.

Conclusion

The new codebehind model in ASP.NET 2.0 seems both familiar and foreign to ASP.NET developers. It's familiar because it still uses inheritance to relate codebehind classes with their .aspx generated class definitions, and yet foreign elements like partial classes and the implicit generation of control member variable declarations are fundamental shifts. In practice, you will probably not notice much difference in usage, but it will be important to understand the class relationships and assembly mappings outlined here whenever you are doing something out of the ordinary, like creating a common base Page class or mixing codebehind and inline code models.

Send your questions and comments for Fritz to  xtrmasp@microsoft.com.

Fritz Onion is a cofounder of Pluralsight, a premier Microsoft .NET training provider, where he heads the Web development curriculum. Fritz is the author of Essential ASP.NET (Addison Wesley, 2003) and the upcoming Essential ASP.NET 2.0 (Addison Wesley, 2006). Reach him at pluralsight.com/fritz.