December 2009

Volume 24 Number 12

Extreme ASP.NET - Looking Ahead to ASP.NET 4.0

By K. Scott Allen | December 2009

When Visual Studio 2010 and  .NET  4.0 arrive next year, we ASP.NET developers will have two mature frameworks for building Web applications: the ASP.NET Web Forms framework and the ASP.NET MVC framework. Both build on top of the core ASP.NET runtime, and both are getting some new features to start the next decade.

I don’t have the space to cover every addition to ASP.NET in one article, as there are numerous improvements to both frameworks and the underlying runtime. Instead, I’ll highlight what I think are the important new features for Web Forms and MVC.

New For ASP.NET Web Forms

ASP.NET Web Forms will be over eight years old by the time Microsoft releases Version 4, and the team continues to refine the framework and make improvements. In my last column, I touched on a few of these improvements, like the new classes that make it easy to use the URL routing features now included in the core services of ASP.NET, and the new MetaKeywords and MetaDescription properties on the Page base class that make it simple to control the content of metatags on a form. These changes are relatively minor, however.

The key changes in Web Forms address some of the chief criticisms  about the framework. Many developers have wanted more control over the HTML a Web form and its controls produce, including the client-side identifiers emitted inside the HTML. In 4.0, many of ASP.NET’s server-side controls were reworked to produce HTML that is easier to style with CSS and conforms to conventional Web practices.  Also, new properties have been added to base classes that will give developers more control over the client-side identifiers generated by the framework. I’ll highlight these changes in the following sections.

CSS-Friendly HTML

One example of a server control that is notoriously difficult to style with CSS is the ASP.NET menu control. When the menu renders, it emits nested table tags that include cellpadding, cellspacing and border attributes. To make matters worse, the menu control embeds style information inside the cells of the nested tables and injects an in-line style block at the top of the page. As an example, look at the following definition of a simple menu:

<asp:Menu runat="server" ID="_menu">
    <Items>
        <asp:MenuItem Text="Home" NavigateUrl="~/Default.aspx" />
        <asp:MenuItem Text="Shop" NavigateUrl="~/Shop.aspx" />
    </Items>
</asp:Menu>

In ASP.NET 3.5, the simple menu produces the following HTML (with some attributes omitted or shortened for clarity):

<table class="..." cellpadding="0" cellspacing="0" border="0">
    <tr id="_menun0">
        <td>
            <table cellpadding="0" cellspacing="0" 
                border="0" width="100%">
                <tr>
                    <td style="...">
                        <a class="..." href="Default.aspx">Home</a>
                    </td>
                </tr>
            </table>
        </td>
    </tr>
</table>

In ASP.NET 4.0, Microsoft revised the menu control to produce semantic markup. The same menu control in ASP.NET 4.0 will produce the following HTML:

<div id="_menu">
    <ul class="level1">
        <li><a class="level1" href="Default.aspx" target="">Home</a></li>
    </ul>
</div>

This type of CSS-friendly markup was achievable in previous versions of ASP.NET if you used a control adapter to provide alternate rendering logic for a control, but now the markup is CSS-friendly by default. If you already have style sheets and client script written against the HTML produced by ASP.NET 3.5, you can set the controlRenderingCompatibilityVersion attribute of the pages section in web.config to the value “3.5”, and the control will produce the nested table markup we saw earlier. The default value for this attribute is 4.0. Note that the 4.0 menu control still produces a style block at the top of the page, but you turn this off by setting the IncludeStyleBlock property of the control to false.

Many other controls in 4.0 are CSS-friendly as well. For example, validation controls like the RangeValidator and RequiredFieldValidator will no longer render inline styles, and template controls like the FormView, Login, and Wizard control will no longer render themselves inside of a table tag (but only if you set the RenderOuterTable property on these controls to false). Other controls have changed, too. As just one example, you can force the RadioButtonList and CheckBoxList controls to render their inputs inside of list elements by setting the RepeatLayout property to the value OrderedList or UnorderedList, which forces the controls to render using ol and li elements, respectively.

Generating Client IDs

If you have ever written client-side script to manipulate the DOM, then you are probably aware of ASP.NET’s affinity for changing client-side ID attributes. In an effort to ensure that all ID attributes are unique on a page, ASP.NET will generate a client ID by concatenating a control’s ID property with additional information. On the server, you can access the generated value using the ClientID property of a control.

As an example, if a control is inside a naming container (a control that implements the INamingContainer interface, as user controls and master pages do), then ASP.NET produces the ClientID value by prefixing the naming container’s ID to the control’s ID. For data-bound controls that render repeating blocks of HTML, ASP.NET will add a prefix that includes sequential numbers. If you view the source of any ASP.NET page, you’ll probably encounter id values like “ctl00_content_ctl20_ctl00_loginlink”. These generated values add an extra level of difficulty when writing client script for a Web Forms page.

In Web Forms 4.0, a new ClientIDMode property is on every control. You can use this property to influence the algorithm ASP.NET will use for generating the control’s ClientID value. Setting the value to Static tells ASP.NET to use the control’s ID as its ClientID, with no concatenation or prefixing. For example, the CheckBoxList in the following code will generate an <ol> tag with a client id of “checklist”, regardless of where the control exists on the page:

<asp:CheckBoxList runat="server" RepeatLayout="OrderedList" 
                  ID="checklist" ClientIDMode="Static">
    <asp:ListItem>Candy</asp:ListItem>
    <asp:ListItem>Flowers</asp:ListItem>
</asp:CheckBoxList>

When using a ClientIDMode of Static, you’ll need to ensure the client identifiers are unique. If duplicated id values exist on a page, you’ll effectively break any scripts that are searching for DOM elements by their ID value.

There are three additional values available for the ClientIDMode property. The value Predictable is useful for controls implementing IDataBoundListControl, like the GridView and ListView. Use the Predictable value in conjunction with the ClientIDRowSuffix property of these controls to generate client IDs with specific values suffixed to the end of the ID. For example, the following ListView will bind to a list of Employee objects. Each object has EmployeeID and IsSalaried properties. The combination of the ClientIDMode and ClientIDRowSuffix properties tell the CheckBox to generate a client ID like employeeList_IsSalaried_10, where 10 represents the associated employee’s ID.

<asp:ListView runat="server" ID="employeeList" 
                      ClientIDMode="Predictable"
                      ClientIDRowSuffix="EmployeeID">
            <ItemTemplate>
                <asp:CheckBox runat="server" ID="IsSalaried" 
                              Checked=<%# Eval("IsSalaried") %> />
            </ItemTemplate>
        </asp:ListView>

Another possible value for ClientIDMode is Inherit. All controls on a page use a ClientIDMode of Inherit by default. Inherit means the control will use the same ClientIDMode as its parent. In the previous code sample, the CheckBox inherits its ClientIDMode value from the ListView, which holds the value Predictable. The final possible value for ClientIDMode is AutoID. AutoID tells ASP.NET to use the same algorithm for generating the ClientID property as it does in Version 3.5. The default value for a page’s ClientIDMode property is AutoID. Since all controls on a page default to using a ClientIDMode of Inherit, moving an existing ASP.NET application to 4.0 will not change the algorithm the runtime uses to generate client ID values until you make a change to a ClientIDMode property. This property can also be set in the pages section of web.config to provide a different default for all pages in an application.

New Project Template

The Web application and Web site project templates in Visual Studio 2008 provide a Default.aspx page, a web.config file and an App_Data folder. These starting templates are simple and require some additional work before you can get started on a real application. The same templates in Visual Studio 2010 provide more of the infrastructure you need to build an application using contemporary practices. A screen capture of a brand new application produced by these templates is shown in Figure 1.

New Web Application in Visual Studio 2010

Figure 1 New Web Application in Visual Studio 2010

Notice how the new application includes a master page by default (Site.master). All of the .aspx files you find inside the new project will be content pages using ContentPlaceholder controls to plug content into the structure defined by the master page. Notice the new project also includes a style sheet in the Content directory (Site.css). The master page includes this style sheet using a link tag, and inside the style sheet you’ll find a number of styles defined to control the appearance of the page body, headings, primary layout and more. The new project also includes a Scripts directory with the latest version of the jQuery library, an open source JavaScript framework officially supported by Microsoft and included with Visual Studio 2010 as part of the install.

The new project template, with its use of master pages and style sheets, will help developers get started in the right direction when using Web Forms. A running version of the new application is shown in Figure 2. Visual Studio 2010 will also include “Empty” templates for both Web sites and Web applications. These empty templates will not include files or directories when you use them, so you’ll be starting your application from scratch.

Running the new ASP.NET Application

Figure 2 Running the new ASP.NET Application

Another bit of good news about new projects in ASP.NET 4.0 is that the web.config file starts off nearly empty. Most of the configuration we became accustomed to seeing in ASP.NET 3.5 web.config files is now in the machine.config file that lives underneath the 4.0 framework’s installation directory. This includes the configuration of controls from the System.Web.Extensions directory, the HTTP handlers and modules configured to support 
JavaScript proxies for Web services, and the system.webserver section for Web sites running under IIS 7.

New for ASP.NET MVC

Visual Studio 2010 should bring us the second release of the ASP.NET MVC framework. While still young, the framework has attracted many Web developers who wanted a framework designed for testability. The second release of ASP.NET MVC is going to focus on better developer productivity and adding the infrastructure to handle large, enterprise-scale projects.

Areas

One approach to building an extremely large ASP.NET Web Forms application is to break apart the application into multiple sub-projects (an approach promoted by the P&P Web Client Composite Library). This approach is difficult to undertake with ASP.NET MVC 1.0 because it works against a number of the MVC conventions. MVC 2.0 will officially support this scenario using the concept of an area. An area allows you to partition an MVC application across Web application projects, or across directories inside of a single project. Areas help to separate logically different pieces of the same application for better maintainability.

The parent area of an MVC Web application is an MVC project that will include a global.asax and root level web.config file for the application. The parent area can also include common pieces of content, like application-wide style sheets, JavaScript libraries, and master pages. Child areas are also MVC Web application projects, but since these projects physically exist underneath the parent area project  at runtime, the parent and its children will appear as a single application.

As an example, imagine a large inventory application. In addition to the parent area, the inventory application might be broken into ordering, distributing, reporting and administrative areas. Each area can live in a separate MVC web project, and each project will need to register its routes by including a class that derives from the abstract base class AreaRegistration. In the code that follows, we override the AreaName property to return the friendly name of the reporting area, and override the RegisterArea method to define the routes available in the reporting area:

public class ReportingAreaRegistration : AreaRegistration
{
    public override string AreaName
    {
        get { return "Reporting"; }
    }

    public override void RegisterArea(AreaRegistrationContext context)
    {
        context.MapRoute(
            // route name
            "ReportingDefault",
            // url pattern
            "reporting/{controller}/{action}",
            // route defaults
            new { controller = "Home", action = "Index" },
            // namespaces
            new string[] { "Reporting.Controllers" });            
    }
}

Notice that we include a string array of namespaces to search when locating the controller for the reporting area. Constraining the namespaces to search allows different areas to have controllers with the same name (multiple HomeController classes can exist in the application, for example).

DataAnnotations for Easy Validation

The DefaultModelBinder in ASP.NET MVC is responsible for moving data from the request environment into model properties. For example, when the model binder sees a model object with a property named Title, it will look through the form, query string and server variables to find a variable with a matching name (Title). However, the model binder doesn’t perform any validation checks beyond simple type conversions. If you want the Title property of your model object to contain only strings with 50 characters or less, you have to perform this validation check during the execution of your controller action, implement a custom model binder or implement the IDataErrorInfo interface on your model.

In ASP.NET MVC 2.0, the DefaultModelBinder will look at DataAnnotation attributes on model objects. These DataAnnotation attributes allow you to provide validation constraints on your model. As an example, consider the following Movie class:

public class Movie
{
    [Required(ErrorMessage="The movie must have a title.")]
    [StringLength(50, ErrorMessage="The movie title is too long.")]
    public string Title { get; set; }
}

The attributes on the Title property tell the model binder that the Title is a required field, and the maximum length of the string is 50 characters. The MVC framework can automatically display the ErrorMessage text in the browser when validation fails. Additional built-in validation attributes include an attribute to check a range and an attribute to match a regular expression.

At the time of this writing, the MVC runtime uses only the validation attributes for server-side validation checks. The MVC team expects to generate client-side validation logic from the validation attributes by the time it releases MVC 2.0. Driving both the server- and client-side validation using these attributes will be a boon for the maintainability of an application.

Templated Helpers

Templated helpers in ASP.NET MVC 2.0 also consume DataAnnotation attributes. But instead of using the attributes to drive validation logic, template helpers use the attributes to drive the UI display of a model. The template helpers begin with the new HTML helper methods DisplayFor and EditorFor. These helper methods will locate the templates for a given model based on the model’s type. For example, let’s use the Movie class we looked at before, but with an additional property:

public class Movie
{        
    // ...

    [DataType(DataType.Date)]
    public DateTime ReleaseDate { get; set; }
}

In this scenario, every movie carries its release date, but no one ever cares what time of day a movie is released. We only want to display the date information when displaying this property, and not the time information. Notice the property is decorated with a DataType attribute that advertises our intention.

To properly display the release date, we need a display template. A display template is just a partial view with an .ascx extension that lives inside a DisplayTemplates folder. The DisplayTemplates folder itself can live underneath a controller’s view folder (in which case the template applies only to the views for that one controller), or in the shared views folder (in which case the template is available everywhere). In this case, the template needs the name Date.ascx and looks like the following:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %>
<%= Html.Encode(String.Format("{0:d}", Model)) %>

In order for the MVC framework to use this template, we need to use the DisplayFor helper method when rendering the ReleaseDate property. The code shown in Figure 3 is from another template, the Movie.ascx display template.

Figure 3 Movie.ascx Display Template

<%@ Control Language="C#" 
    Inherits="System.Web.Mvc.ViewUserControl<Movie>" %>

    <fieldset>
        <legend>Fields</legend>
        <p>
            Title:
            <%= Html.LabelFor(m => m.Title) %>
            <%= Html.DisplayFor(m => m.Title) %>
        </p>
        <p>
            <%= Html.LabelFor(m => m.ReleaseDate) %>
            <%= Html.DisplayFor(m => m.ReleaseDate) %>
        </p>
    </fieldset>

Notice how the LabelFor and DisplayFor helper methods are strongly typed, which can help you propagate changes if a model is refactored. To use the Movie.ascx template to display a movie anywhere in an application, we just need to use the DisplayFor helper again. The following code is from a view that is strongly typed against the Movie class:

<asp:Content ID="detailContent" 
             ContentPlaceHolderID="MainContent" 
             runat="server">                    
        Movie:
        <%= Html.DisplayFor(m => m) %>
        
    </p>
</asp:Content>

The DisplayFor method is strongly typed to use the same model as the view page, so the m parameter in the DisplayFor lambda expression is of type Movie. DisplayFor will automatically use the Movie.ascx template when displaying the movie (which in turn uses a DisplayFor to find the Date.ascx template). If we did not use the DataType attribute on the ReleaseDate property of a movie, DisplayFor would not use the Date.ascx template and would display the date and the time portions of the ReleaseDate, but the DataType attribute helps guide the framework to the correct template. This concept of strongly typed, nested templates and data type annotations is powerful and will prove to be a productivity boost.


K. Scott Allen is a member of the Pluralsight technical staff and founder of OdeToCode. You can reach Scott at scott@OdeToCode.com or read his blog at odetocode.com/blogs/scott.

Thanks to the following technical experts for reviewing this article: Phil Haack and Matthew Osborn

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