New Code Compilation Features in ASP.NET Whidbey
G. Andrew Duthie
Graymad Enterprises, Inc.
October 2003
Summary: Examines how ASP.NET 'Whidbey'
makes working with code even easier than before. The Code directory
automatically compiles code for your site, while precompilation can make
deployment easier than ever before. (14 printed pages)
Download the source code
for this article.
Contents
Introduction
The New Code-Behind
Model
Code Directory
Interest Calculator
Precompilation Support
In-Place Precompilation
Precompilation for Deployment
IntelliSense Everywhere!
Summary
Introduction
The upcoming
release of Microsoft ASP.NET, code name ASP.NET 'Whidbey' (after the
code name for the upcoming release of Microsoft Visual Studio .NET)
introduces a basket load of new features (and changes to existing features).
Some of these take advantage of new features in the underlying Microsoft .NET
Framework version on which ASP.NET Whidbey is built. One of the most useful set
of features relates to how your code is compiled.
In this article,
we'll look at the major changes in the compilation model for ASP.NET Whidbey,
describe how these changes will affect how you write your ASP.NET applications,
and see how to use them.
The changes and
new compilation features break down into four basic areas:
1.
Changes to the code-behind model.
2.
The new code directory.
3.
Added support for pre-compiling ASP.NET applications.
4.
Microsoft IntelliSense enhancements.
The New
Code-Behind Model
By default, sites
developed using Visual Studio .NET 2002 or 2003 use a feature called code
behind to separate visual elements (HTML markup, controls, and so on) from
UI-related programming logic. When a developer created a new Web Form (such as
foo.aspx), Visual Studio would automatically create an associated Codebehind
class file with the same name as the Web Form, along with an appended .vb or
.cs, depending on the language used for the project. The class file would be
associated with the Web Form by the Codebehind and Inherits
attributes of the @ Page directive.
This class file
contained event-handling code, including code to wire up the event handlers to
the proper events, as well as separate declarations for each control added to
the .aspx file in the Visual Studio Web Form editor. When the Web application
project is compiled (built), all of the Codebehind classes in the
project are compiled into a single .NET assembly, which is placed in the
application's bin directory. The Web Forms pages themselves are compiled
dynamically at run time, and each Web Form inherits from the Codebehind
class associated with it. See the MSDN Library article, Web Forms Code Model, for more
information on the code-behind model in Visual Studio .NET 2003 and ASP.NET
1.1.
While the
original code-behind model is nice in theory (who doesn't want to separate
programmatic logic from UI elements?), it has several drawbacks:
Required rebuilds. The Codebehind class is not automatically
compiled at runtime in Visual Studio .NET, so any change to a Codebehind
class requires the entire project to be rebuilt in order to pick up the
changes. (Note that you can specify that a code-behind file is to be compiled
dynamically using the src attribute of the
@ Page directive, but this is not the default in Visual Studio .NET.)
Shared development issues. All Codebehind classes in a project
compile to a single assembly, so it is more difficult to have multiple
developers working on a project without bottlenecks.
Brittle code. Controls exist both declaratively (in the .aspx page) and
programmatically (in the Codebehind class), and it's easy for code to
break if the two sets of controls are not kept in sync properly.
Added complexity and lack of single-file support. Visual Studio .NET
requires the use of code behind for many of its productivity-enhancing
features, including IntelliSense statement completion. Unfortunately, these
features often add a large amount of relatively complex code to the Codebehind
class, adding to the problem of 'brittle' code, since changes to the
code injected by Visual Studio .NET can too easily break the page.
With these
drawbacks in mind, the team responsible for ASP.NET and Visual Studio .NET
Whidbey decided to re-think the code-behind model. The new code-behind model
takes advantage of a new feature of Microsoft Visual Basic .NET and C# called
partial classes (partial types in C#). Partial classes allow you to define
different parts of a class in more than one file. At compile time, these parts
are combined by the compiler. ASP.NET Whidbey uses the new CompileWith
and Classname attributes of the @ Page directive to identify the Codebehind
partial class to combine with the .aspx page. By taking advantage of partial
classes and making other changes, the ASP.NET team was able to do the
following:
Remove the need for control declarations and event wire-up code in Codebehind
classes (events are wired up declaratively in the control declaration).
Allow both Web Forms pages and Codebehind classes to be compiled
dynamically at run time, eliminating the need to rebuild an entire project for
a single change.
Reduce file contention in shared development.
Provide the same IDE experience for both developers choosing to work with
code-behind files, as well as those who prefer single-file development (all
code and markup in the .aspx file).
Here's a
before/after view of the changes in the code-behind model. The following code
is simply the default code created by Visual Studio when you add a new Web Form
using code behind (referred to as Web Form with code separation in Visual
Studio .NET Whidbey):
Visual Studio .NET 2002/2003
WebForm1.aspx:
<%@ Page
Language='vb' AutoEventWireup='false'
Codebehind='WebForm1.aspx.vb'
Inherits='TestWebApp_121602.WebForm1'%>
<!DOCTYPE HTML
PUBLIC '-//W3C//DTD
HTML 4.0 Transitional//EN'>
<html>
<head>
<title>WebForm1</title>
<meta name='GENERATOR'
content='Microsoft Visual Studio .NET 7.1'>
<meta name='CODE_LANGUAGE'
content='Visual Basic .NET 7.1'>
<meta name=vs_defaultClientScript
content='JavaScript'>
<meta name=vs_targetSchema
content='https://schemas.microsoft.com/intellisense/ie5'>
</head>
<body
MS_POSITIONING='GridLayout'>
<form id='Form1'
method='post' runat='server'>
</form>
</body>
</html>
WebForm1.aspx.vb:
Public Class
WebForm1
Inherits System.Web.UI.Page
#Region '
Web Form Designer Generated Code '
'This call is required by the Web Form
Designer.
<System.Diagnostics.DebuggerStepThrough()>
_
Private Sub InitializeComponent()
End Sub
'NOTE: The following placeholder
declaration is
'required by the Web Form Designer.
'Do not delete or move it.
Private designerPlaceholderDeclaration As
System.Object
Private Sub Page_Init(ByVal sender As
System.Object, _
ByVal e As System.EventArgs) Handles
MyBase.Init
'CODEGEN: This method call is required
by the Web Form Designer
'Do not modify it using the code editor.
InitializeComponent()
End Sub
#End Region
Private Sub Page_Load(ByVal sender As
System.Object, _
ByVal e As System.EventArgs) Handles
MyBase.Load
'Put user code to initialize the page
here
End Sub
End Class
Visual Studio .NET Whidbey
Default.aspx:
<%@ page
language='VB' compilewith='Default.aspx.vb'
classname='ASP.Default_aspx' %>
<html>
<head
runat='server'>
<title>Untitled Page</title>
</head>
<body>
<form runat='server'>
</form>
</body>
</html>
Default.aspx.vb:
Imports
Microsoft.VisualBasic
Namespace ASP
Expands Class
Default_aspx
End Class
End Namespace
As should be
fairly clear from the foregoing examples, the code produced by Visual Studio
.NET Whidbey is far cleaner and easier to read. And you don't have to sacrifice
drag-and-drop functionality or IntelliSense to get it.
Code
Directory
Another cool and
useful new feature in ASP.NET Whidbey is the addition of the Code directory.
The Code directory, like the bin directory, is a special directory used by
ASP.NET, but with a twist: While the bin directory is designed for storing
pre-compiled assemblies used by your application, the Code directory is
designed for storing class files to be compiled dynamically at run time. This
allows you to store classes for business logic components, data access
components, and so on in a single location in your application, and use them
from any page. Because the classes are compiled dynamically at run time and are
automatically referenced by the application containing the Code directory, you
don't need to build the project before deploying it, nor do you need to
explicitly add a reference to the class, and you can easily make changes to a
component and deploy with a simple XCOPY or with a drag-and-drop operation. In
addition to simplifying the deployment and referencing of components, the Code
directory also greatly simplifies the creation and accessing of resource files
(.resx) used in localization, as well as automatically generating and compiling
proxy classes for WSDL files (.wsdl).
To better
illustrate how this works, let's take a look at a couple of examples. In the
first example, we'll see how to create a simple business component and access
it from a Web Forms page.
Interest
Calculator
First, we open
Visual Studio .NET Whidbey, and create a new Web site called Compilation.
Once the Web site has been created, the IDE should look similar to Figure 1.
Figure 1. Visual
Studio .NET Whidbey Web site
Next, we'll add
the Code folder to the Web site by right-clicking the project, and selecting New
Folder. The folder must be named Code, but the name is not case
sensitive. Once we've added the folder, we can add a new class file by
right-clicking the Code folder and clicking Add New Item, and then
choosing the Class item in the Templates pane of the Add New
Item dialog box. We'll name the class CalculateInterest.vb. We then
add the code that calculates the interest (the code goes between the Class
and End Class statements):
Public Function
CalcBalance(ByVal Prncpl As Integer, _
ByVal Rate As Double, _
ByVal Years As Integer,
_
ByVal Period As Integer) As String
Dim BaseNum As Double = (1 + Rate / Period)
CalcBalance = _
Format(Prncpl *
System.Math.Pow(BaseNum, _
(Years * Period)),
'#,###,##0.00').ToString
End Function
Once the
component class is complete, we need to modify the Default.aspx page to provide
the necessary fields for entering data, and to call the component's CalcBalance
method. For the sake of simplicity, the full listing for Default.aspx is shown
below (note that Default.aspx uses the single-file code model).
Default.aspx:
<%@ page
language='VB' %>
<script
runat='server'>
Sub Button1_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
Dim Calc As New CalculateInterest
Label6.Text = '$' & _
Calc.CalcBalance(Convert.ToInt32(TextBox1.Text),
_
(Convert.ToInt32(TextBox2.Text)
/ 100), _
Convert.ToInt32(TextBox3.Text),
_
Convert.ToInt16(Dropdownlist1.SelectedValue))
Label6.Visible = True
End Sub
</script>
<html>
<head
runat='server'>
<title> Interest
Calculator</title>
</head>
<body>
<form runat='server'>
<asp:label id='Label1'
runat='server'>Principal ($):</asp:label>
<asp:textbox id='TextBox1'
runat='server'>
</asp:textbox>
<br />
<asp:label id='Label2'
runat='server'>Interest Rate (%):</asp:label>
<asp:textbox id='TextBox2'
runat='server'>
</asp:textbox>
<br />
<asp:label id='Label3'
runat='server'>Years:</asp:label>
<asp:textbox id='TextBox3'
runat='server'>
</asp:textbox>
<br />
<asp:label id='Label4'
runat='server'>Compounding Frequency:</asp:label>
<asp:dropdownlist id='Dropdownlist1'
runat='server'>
<asp:ListItem
Value='1'>Annually</asp:ListItem>
<asp:ListItem
Value='4'>Quarterly</asp:ListItem>
<asp:ListItem
Value='12'>Monthly</asp:ListItem>
<asp:ListItem
Value='365'>Daily</asp:ListItem>
</asp:dropdownlist>
<br />
<asp:label id='Label5'
runat='server'>Ending
Balance: </asp:label>
<asp:label id='Label6'
visible='false'
runat='server'></asp:label>
<br />
<asp:button id='Button1'
runat='server'
text='Calculate' onclick='Button1_Click' />
</form>
</body>
</html>
In design view,
the modified Default.aspx should look similar to Figure 2.
Figure 2.
Default.aspx in design view
One important
thing to note is that as you type the code in the <script> block that
calls the component class, you will get full IntelliSense statement completion,
including on the component class, as shown in Figure 3. This is a significant
improvement over Visual Studio .NET 2003, which provides no support for
IntelliSense in server-side <script> blocks.
Figure 3.
IntelliSense in Source view
Browsing
Default.aspx results in the output shown in Figure 4. Fill in some values for
principal, rate, and years and click Calculate, and the output should
look similar to Figure 5.
Figure 4. Initial
output of Default.aspx
Figure 5. Output
after calculation
Resource Files
If you've ever
worked with Web applications in Visual Studio .NET 2002 or 2003, you've no
doubt noticed that every time you created a new Web Forms page, in addition to
the .aspx page itself and the .vb or .cs code-behind file, Visual Studio also
created a matching file with the .resx extension (that is, WebForm1.aspx.resx).
Now if you're like most Web developers, you either ignored or attempted to get
rid of these files, since it wasn't very intuitive what they were, and/or how
to use them. Well, the short answer is that .resx files are resource files,
and they are used primarily for storing multiple versions of resources, such as
text strings in different languages for localization.
In Visual Studio
.NET 2002 and 2003, resource files needed to be added to the project assembly
as part of the process of building the project, and required importing two
namespaces, creating a ResourceManager object, and calling its GetString
method just to access a resource string.
Thanks to the
Code directory, the process of accessing resources is far simpler in Visual
Studio .NET Whidbey, as we'll see in the following example.
We'll get started
by creating the resource file, using the same project as in our previous
example. First, right-click the Compilation Web site created earlier, and click
Add New Item In the Add New Item dialog box, select the Assembly
Resource File template, name the file strings.resx, and click Open.
The default view of strings.resx should look similar to Figure 6.
Figure 6. Editing
a resource file in the XML editor
Add the following
items to the data table (you can leave the comment, type, and mimetype columns
blank):
Name
|
Value
|
txtColorPrompt
|
Please choose a color:
|
txtColorResponseRed
|
You chose Red!
|
txtColorResponseGreen
|
You chose Green!
|
txtColorResponseBlue
|
You chose Blue!
|
Now repeat the
process to add a new resource file named strings.en-GB.resx, and add the
following item to its data table and save the file (because we did not add
entries for txtColorResponse*, the values for these items from strings.resx
will be used for all clients):
Name
|
Value
|
txtColorPrompt
|
Please choose a color:
|
Now, in order to
take advantage of the magic of the Code directory, we'll need to drag both of
the .resx files into the Code directory from the root of the Web site. Once
that's done, the result will look like Figure 7.
Figure 7. .resx
files in the Code directory
To demonstrate
how easy it is to now use the resource files we created, we'll add a Web Form
to the project by right-clicking the Web site node, and clicking Add New
Item. In the Add New Item dialog box, we select Web Form, name
the page ColorPicker.aspx, and click Open. Modify the page so
that it matches the listing below.
ColorPicker.aspx:
<%@ page
UICulture='en-GB' language='VB' %>
<script
runat='server'>
Sub Page_Load(ByVal sender As Object, ByVal
e As System.EventArgs)
Label1.Text =
Resources.strings.txtColorPrompt
End Sub
Sub
Submit_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
Label1.ForeColor = _
System.Drawing.Color.FromName(Dropdownlist1.SelectedValue)
Select Case Dropdownlist1.SelectedValue
Case 'Red'
Label1.Text =
Resources.strings.txtColorResponseRed
Case 'Green'
Label1.Text =
Resources.strings.txtColorResponseGreen
Case 'Blue'
Label1.Text =
Resources.strings.txtColorResponseBlue
End Select
Dropdownlist1.Visible = False
Submit.Visible = False
End Sub
</script>
<html>
<head
runat='server'>
<title>Color Picker</title>
</head>
<body>
<form runat='server'>
<asp:label id='Label1'
runat='server'>Label</asp:label>
<asp:dropdownlist
id='Dropdownlist1' runat='server'>
<asp:listitem
value='Red'>Red</asp:listitem>
<asp:listitem
value='Green'>Green</asp:listitem>
<asp:listitem
value='Blue'>Blue</asp:listitem>
</asp:dropdownlist>
<asp:button id='Submit'
text='Submit'
runat='Server' onclick='Submit_Click' />
</form>
</body>
</html>
When we browse
ColorPicker.aspx from a browser, the default output will appear similar to
Figure 8. If someone browsing from a system set up for the United Kingdom
browses the page (you can simulate this by setting the UICulture property of
the page to 'en-GB' and saving the page), the output will appear as
shown in Figure 9 (note that we've added the u in 'colour').
Figure 8. Default
output of ColorPicker.aspx
Figure 9. Output
of ColorPicker.aspx for UK systems
Note that
accessing resource files in ASP.NET Whidbey requires only a single line of
code. Because the resource file is embedded and referenced automatically by placing
it in the Code directory, we do not need to reference any namespaces or
assemblies, nor do we need to create objects in order to access the resource
strings. And ASP.NET also takes care of determining which resource file should
be used, based on the settings of the user's browser-so we don't have to try to
figure that out at run time and respond appropriately. It just works.
Precompilation
Support
One of the
advantages of ASP.NET Web Forms is that because of dynamic compilation, you can
easily make changes to an .aspx page, save the page, and the page is updated,
without the need for a recompile (as long as you're not using code behind). But
dynamic compilation isn't appropriate for every application, and it carries
with it an initial performance hit when the application is accessed for the
first time. Additionally, there may be times when you'd really like to deploy
an application without the source code.
If one of those
situations applies to you, you'll be glad to hear that ASP.NET Whidbey will
ship with support for precompiling Web sites. ASP.NET Whidbey supports two
modes of pre-compilation: in-place precompilation, and precompilation for
deployment.
In-Place
Precompilation
In-place
precompilation allows you to manually cause all the pages in your Web site to
be batch compiled. This is essentially what happens the first time a user hits
a page within your application, except that in the latter case, the user gets
to sit and wait while the batch compilation takes place.
There are two
main reasons to use in-place precompilation: first, it eliminates the
performance hit of batch compiling on the first page request, and second, it
allows you to find compilation errors before your users do.
In-place
precompilation is also easy to do. Simply browse to the root of your Web site,
plus the special handler name precompile.axd (those familiar with ASP.NET's
Trace feature will note the similarity to the trace.axd handler name):
https://localhost/mywebsitename/precompile.axd
where mywebsitename
is the name of your Web site. After precompiling your site, requests for pages
within the site should be fulfilled immediately, without any compilation lag.
Precompilation
for Deployment
The second
precompilation mode allows you to create an executable version of your entire Web
site that can be deployed without any source code, including HTML and other
static files. As such, precompiling for deployment can prevent easy access to
the intellectual property represented by your code. The resulting set of
assemblies and stub files can be deployed to a production server through XCOPY,
FTP, Windows Explorer, and so on.
To precompile a
site for deployment, ASP.NET Whidbey provides a command-line utility called
aspnet_compiler.exe. To invoke the ASP.NET precompiler on a file system Web
site, you would open a command window, navigate to the location of the .NET
Framework, install (<windows>Microsoft.NETFramework<version>),
and enter the following command:
aspnet_compiler
/v /<websitename> -p <source> <destination>
where
<websitename> is the name of the Web site (as you'd enter it in a
browser), and <source> and <destination> are file system paths
pointing to the location of the site to compile and the location to which the
compiled version should be output. For our example Web site, the command would
look something like the following (note that the following is a single
command):
aspnet_compiler
/v /Compilation
-p
c:WebSitesCompilation c:WebSitesCompilation_Compiled
If you want to
view all of the available options for the ASP.NET precompiler, you can simply
enter the command:
aspnet_compiler
/?
Note that some of
the command-line options require your Web site to be a valid Microsoft
Internet Information Services (IIS) application in order to work correctly.
If you navigate
to the destination directory in Microsoft Windows Explorer, you'll see that
the result of precompiling a Web site is a site with a bin directory containing
several assemblies and descriptive files, as well as a number of stub files
with the same names as the original pages, but with the code (both HTML and
executable code) stripped out. If you browse the site, the output will be
identical to the original site. Note that you cannot use in-place
precompilation on a site that has been precompiled for deployment, for the
obvious reason that it has already been precompiled.
IntelliSense
Everywhere!
One of my biggest
gripes (and one that I'm sure is shared by many developers) about Visual Studio
.NET 2002 and 2003 is the inconsistent support for IntelliSense and other productivity-enhancing
features. Want to drag a control from the toolbox onto the page in HTML view?
Nope, can't do it. In fact, the Web Forms panel of the toolbox isn't even
available when you're in HTML view! Want to write your code inline in your
.aspx pages instead of using code behind? Well, you can do that, but you
have to give up IntelliSense, drag-and-drop functionality, and more to do it.
Finally, as I illustrated in a recent article on the MSDN ASP.NET
Developer Center, getting design-time support for custom controls in Visual
Studio .NET 2002 or 2003 requires jumping through a number of hoops, including
a somewhat inelegant (though effective) XSL hack.
The good news is
that thanks to the unification of the compilation model in ASP.NET Whidbey, all
of this goes away. In Visual Studio .NET Whidbey, you will be able to write
your code inline, or using the new code-behind model, and get the same support
for dragging controls, for IntelliSense statement completion, and for all those
productivity enhancements you wanted to use, but were prevented from doing so
because of the way you like to code. Additionally, design-time support for both
custom server controls and Web controls is much improved, including the
addition of IntelliSense statement completion for your custom controls in
Source view (the Visual Studio .NET Whidbey equivalent of HTML view).
Summary
The changes to
the compilation model within ASP.NET Whidbey, and the accompanying feature
improvements of Visual Studio .NET Whidbey, represent a great leap forward for
providing developers with the flexibility that they crave, while still allowing
them to take full advantage of the productivity features provided by the IDE.
The greatly simplified code-behind model will help to make that feature more
useful and less onerous, while the addition of full support for inline code
will definitely please those developers who prefer all of their code in a
single .aspx file.
The Code
directory promises to be a big productivity enhancer, particularly for those
working on small to medium projects that change rapidly, and anyone for whom a
complicated build process really gets in the way of getting things done. It
also provides a much more straightforward and simple means of accessing
business logic components, resource files, WSDL files, and other resources, by
automatically compiling, embedding, or creating proxies for these resources,
and automatically referencing them, allowing them to be accessed with very
little code.
The
precompilation features make it easy for developers to improve the startup
performance of their sites, and when desired, to add a measure of protection to
their valuable intellectual property by shipping fully-functional Web
applications without source code or HTML.
Finally, the
combination of all of these features means that developers can look forward to
a much richer experience in Visual Studio .NET Whidbey, with full IntelliSense
support in both inline and code-behind models, and for all views of a given
page, without having to constrain their development to a single style mandated
by the tool.
About the Author
G. Andrew Duthie
is the founder and principal of Graymad Enterprises, Inc.,
providing training and consulting in Microsoft Web development technologies.
Andrew has been developing multitier Web applications since the introduction of
Active Server Pages. He has written numerous books on ASP.NET, including Microsoft
ASP.NET Programming with Microsoft Visual Basic, Microsoft ASP.NET
Programming with Microsoft Visual C#, and ASP.NET in a Nutshell
(second edition). Andrew is a frequent speaker at events, including Software
Development, the Dev-Connections family of conferences, Microsoft Developer
Days, and VSLive! He also speaks at .NET user groups as a member of the International .NET Association (INETA) Speaker's Bureau. You
can find out more about Andrew at his company's Web site, https://www.graymad.com/