sup { vertical-align:text-top; }

Cutting Edge

ASP.NET AJAX and Client-Side Templates

DINO ESPOSITO

Contents

A Typical AJAX Function
Limits of Partial Rendering
Reconsidering the Live Quote Page
Client-Side Templating in ASP.NET AJAX
The Browser-Side Templating Pattern
Further Considerations
The HTML Message Pattern
Send your questions and comments for Dino to cutting@microsoft.com.
Code download available at msdn2.microsoft.com/magazine/cc135911.

What makes partial rendering particularly appealing for the development of ASP.NET AJAX applications is its inherent simplicity—lower impact on existing pages and fewer demands on developers' skills. But partial rendering is merely a trick to bypass the browser in the execution of the postback request. Pages that use partial rendering are not truly AJAX pages because they still rely on the old postback method. But what does it mean to be a real AJAX application?

Essentially, a true AJAX application uses the XMLHttpRequest object to bypass the browser and establish direct communication with the Web server and any hosted HTTP endpoints. The application is capable of asynchronously retrieving necessary data and independently refreshing blocks of the user interface. Based on this key behavior, a number of AJAX frameworks have been designed that have different doses of syntactic sugar, larger or smaller feature sets, and richer or simpler families of user interface widgets. ASP.NET AJAX is one of these frameworks.

Designing and building true AJAX applications is not an easy task, and it is even more difficult if you lack the proper programming tools. Compared to other AJAX frameworks, ASP.NET AJAX excels in the support it provides for accessing server-side code. With ASP.NET AJAX, connecting to a scriptable Web or Windows® Communication Foundation (WCF) service is child's play from a developer's perspective. You reference the URL, and the framework generates a JavaScript proxy class for you. It's free and effective. The proxy class hides all the details of XMLHttpRequest plumbing, serialization and deserialization, data formats, and HTTP packaging. You just call a method asynchronously and get a plain data-transfer object (DTO) back with its own set of properties.

ASP.NET AJAX, however, doesn't provide the same efficient mechanism for consuming downloaded data in the browser. No doubt this will improve soon, but for now ASP.NET AJAX doesn't offer a client-side UI model as rich as the server-side service model.

What can you do today to improve the UI model of ASP.NET AJAX pure Web applications? Last month, I introduced the concept of Single-Page Interface (SPI) and presented a couple of simple patterns that can be useful for building SPI pages. This month, I'll discuss client-side templates and data binding.

A Typical AJAX Function

A canonical example to illustrate the power of the AJAX approach is an application that retrieves stock quotes in real time. The non-AJAX Web sites that offer this feature today are using either meta-refresh tags or a plugin-based approach such as Flash, Silverlight™, or ActiveX® controls. Typically, the list of stock values to display is bound to a server-side grid control, and the grid is then refreshed with the rest of the page with the next postback or page request. A page like this can be quickly enhanced with a sprinkle of partial rendering. Let's see how. The markup in Figure 1 wraps a grid control in an UpdatePanel control that is periodically refreshed by a Timer control.

Figure 1 UI for a Real-Time Stock Quote Page

<asp:UpdatePanel runat="server" ID="UpdatePanel1" UpdateMode="Conditional">
  <ContentTemplate>
    <div style="margin:10px;">
      <asp:Label runat="server" ID="Label2" />
      <asp:GridView ID="GridView1" runat="server" SkinID="ClassicGrid"
        AutoGenerateColumns="false" 
        OnRowDataBound="GridView1_RowDataBound">
        <Columns>
          <asp:BoundField DataField="Symbol" HeaderText="SYMBOL" />
          <asp:BoundField DataField="Quote" HeaderText="LAST" >
            <ItemStyle HorizontalAlign="Right" />
          </asp:BoundField>
          <asp:BoundField DataField="Change" HeaderText="CHANGE" >
            <ItemStyle HorizontalAlign="Right" />
          </asp:BoundField>
        </Columns>
      </asp:GridView>
      <small><asp:Label runat="server" ID="Label1" /></small>
    </div> 
  </ContentTemplate>
  <Triggers>
    <asp:AsyncPostBackTrigger ControlID="Timer1" EventName="Tick" /> 
  </Triggers> 
</asp:UpdatePanel>

<asp:Timer runat="server" id="Timer1" Enabled="true" 
  Interval="600000" OnTick="Timer1_Tick" />

The grid control is bound to a collection of objects that feature at least three properties: Symbol, Quote, and Change. The content of the grid is refreshed every 10 minutes. The ASP.NET AJAX Timer control activates a client timer and fires a postback when the interval expires. Because it is wrapped by an UpdatePanel, the grid with stock quotes updates separately from the page. On the server, the timer's tick runs the following code:

protected void Timer1_Tick(object sender, EventArgs e)
{
    Label2.Text = "<b>Last update: </b>" + DateTime.Now.ToLocalTime();
    GridView1.DataSource = picker.Update();
    GridView1.DataBind();
}

The picker object is an instance of a custom helper class named StockPicker. This class incorporates an instance of the service class in charge of retrieving quotes. Here's the constructor of the StockPicker class:

public StockPicker(string stocks)
{
   string provider = ConfigurationManager.AppSettings["StockProvider"];
   _service = GetFactory(serviceProvider, stocks);

    if (_service == null)
      return;

    _desc = _service.GetServiceDescription();
}

The picker reads the name of the service provider class from the configuration file and instantiates it. The picker communicates with the service class through the contract represented by the IQuoteService interface:

interface IQuoteService
{
    StockInfoCollection GetQuotes();
    string GetServiceDescription();
}

The sample code accompanying this column uses a fake quote service that just returns random numbers in lieu of stock quotes. However, the page code can easily be configured to use a real-world quote service. All that you have to do is create a class that implements the IQuoteService interface and connect to a real stock data service. In the sample code, the bindable data is represented as a collection of StockInfo objects:

public class StockInfo
{
    public string Symbol { get; set; }
    public string Quote { get; set; } 
    public string Change { get; set; }
    public string Day { get; set; }
    public string Time { get; set; } 
}

Figure 2 shows a sample ASP.NET AJAX page that is based on this API in action.

fig02.gif

Figure 2 Live Quote Page Using Partial Rendering

Limits of Partial Rendering

Functionally speaking, the page in Figure 2 works as expected. If you're OK with its performance and overall behavior, then you're just fine. However, such a page has a few drawbacks worth noting, even if they won't keep you up at night.

First, the page moves quite a bit of data around each time it's updated. The amount of data may not be problematic for a relatively scanty page like this that refreshes every 10 minutes. If you use Fiddler or Web Development Helper to monitor, you'll see that the size of the payload is around 3KB in download and somewhat less in upload. Again, if you're OK with these numbers, then by all means go for partial rendering.

These numbers, though, lose much of their appeal if you look at the internal mechanics of the page. The view state of the page is sent back and forth for every update. The response contains both data and layout information. Moreover, data is merged with the surrounding markup and can't be separated from it. Finally, if you have other similarly functioning updatable panels on the page, the timer may stop for a while if another partial rendering operation is started in the meantime. Or it may stop another pending operation when the timer triggers.

In the end, while it is quick to learn and apply, partial rendering is not the ideal mechanism to employ when you need to support simultaneous asynchronous calls. Let's see what it takes to rewrite the page in Figure 2 using a pure AJAX approach.

Reconsidering the Live Quote Page

How would a pure AJAX approach affect the design of a live quote page? The page sets up the timer and uses XMLHttpRequest to invoke a remote service. The service is part of the application back end and will use the standard Microsoft® .NET Framework API to call the finance Web service and get data. The data will then be returned to the browser as a JavaScript object. Finally, it's up to you to render it to the user.

What's the difference? Firstly, the invoked URL is not the page itself. The page invokes an HTTP endpoint backed by a scriptable Web or WCF service. There's no page lifecycle, no postback events, no view state restoration. As a result, network traffic is significantly reduced. In the case of this sample page, the payload is 10 times smaller than it is with partial rendering. From an architectural standpoint, you see two neatly separated blocks of code at work—the client-side front end and the server-side back end. The former is powered by JavaScript; the latter is based on managed code.

ASP.NET AJAX shines in this context as it manages to unify the programming interface and data types so that the JavaScript client developer sees exactly the same programming interface and contracts that have been defined on the server. The JavaScript Object Notation (JSON) layer ensures that DTOs that arrive to the client mirror the data sent out from the server.

Let's experiment with some code. The new page has a client button that the user clicks to download fresh data. I'm using a button here instead of a timer only for convenience:

<input type="button" id="btnRefresh" 
       value="Live Quotes" 
       onclick="_getLiveQuotes()" />

The JavaScript handler then invokes a method on a wrapper live quote Web service and gets a collection of StockInfo objects:

<%@ WebService Class="Samples.WebServices.LiveQuoteService" %>

The full source code for the service is shown in Figure 3.

Figure 3 Live Quote Web Service

namespace Samples.WebServices
{
    [WebService(Namespace = "https://samples.ajax/")]
    [ScriptService]
    public class LiveQuoteService : System.Web.Services.WebService
    {
        private static string stocks = 
            ConfigurationManager.AppSettings["StockSymbols"];
        private static StockPicker picker = new StockPicker(stocks);

        public LiveQuoteService()
        {
        }

        [WebMethod]
        public StockInfoCollection Update()
        {
            return picker.Update();
        }

        [WebMethod]
        public string GetServiceDescription()
        {
            return picker.GetServiceDescription();
        }
    }
}

In partial rendering, there's no natural separation between the user interface elements (the view) and the core application logic (the model). Everything is assembled on the server and served ready-to-use to the client. With a pure AJAX architecture, the presentation layer is smarter, richer, and physically separated from the business layer. It goes without saying that such a highly decoupled architecture is inherently more flexible and easy to test. In addition, you have a few more places where you can cache data, as shown in Figure 4.

fig04.gif

Figure 4 Possible Levels of Caching in an AJAX Architecture

Data can be cached on the server, care of the (Web) service front end and implementation. In addition, data can be cached along the way by an HTTP proxy or even by the JavaScript code running in the browser. More importantly, you're not caching markup; you'll be caching usable data (objects or JSON strings) that can be checked at any time against runtime conditions.

Moving rendering logic to the client is beneficial also because it makes life easier for a busy server. The server saves CPU cycles and memory if it only has to return a small chunk of data and does not have to execute a full page lifecycle with markup generation and view state processing.

It is equally straightforward to use ASP.NET AJAX from the client to interact with remote services and bring down live data to the browser. For example, the following is the code to invoke a remote method:

function _getLiveQuotes()
{
    Samples.WebServices.LiveQuoteService.Update(onDataAvailable);
}
function onDataAvailable(results)
{
    // Update the UI
}

ASP.NET AJAX gives you a JavaScript proxy object with the same name as the server-side service and a bunch of static methods. In the code snippet, the Update method ultimately fetches an array of JavaScript Document Types Definitions (DTDs) and passes it to the callback function ultimately responsible for updating the user interface. The variable named results in the code contains the response from the service—just the data you need to bind to the grid.

Unfortunately, there's no client-side GridView control as there is in the server-based ASP.NET approach. You need a form of client-side data binding, preferably with some templating support. Currently, ASP.NET AJAX is not of much help here. For further explanation on this topic, straight from the Microsoft ASP.NET team, see the sidebar, "Insights: ASP.NET AJAX."

Some major ASP.NET control vendors recently began offering server controls with an equivalent client-side object model. If you're using any of these libraries, you may end up having a JavaScript object that mimics the server-side control, armed with a client Data source property and a DataBind method. ASP.NET AJAX provides you with the scaffolding required to invoke a Web service from the client, but it doesn't provide an entire UI framework designed with AJAX in mind, nor does it include client data binding and templating. An interesting resource on various AJAX approaches from the UI perspective is the post by Miljan Braticevic at go.microsoft.com/fwlink/?LinkID=116054.

Client-Side Templating in ASP.NET AJAX

Do you remember "Atlas"? In early builds of what later become ASP.NET AJAX Extensions, there was an attempt to provide client-side data binding and templates. And it was a pretty good one indeed. For some reason, however, it didn't make its way into the final release of ASP.NET and was then pushed to the ASP.NET Futures library.

Basically, the old Atlas binding model was based on HTML templates and a declarative syntax for expressing bindings between markup elements and data fields. Templates and binding are then tied together in a client control—the List view control. Configured through XML, the Atlas List view control does a lot of parsing work with all of the information it receives and attaches behaviors, controls, and bindings to various parts of the markup.

Let's start from this last point to explore the ways in which you can build your own small but effective data binding and templating framework. Note that the final output has to be a string of HTML markup. The initial input is a collection of objects with a few public properties. Your job is to build a piece of HTML that fulfills the user's expectations. Generally, you start by looping over the bound collection and concatenating the template's text with the value of the object's properties.

What's wrong with this approach? Actually, nothing is wrong with this approach. In fact, there's no radically different approach that I can think of that doesn't do the exact same thing at some point. This means that you can certainly create a framework that abstracts this basic routine. In the catalog of AJAX patterns (see www.ajaxpatterns.org), the key steps for defining a client-side templating model go under the name Browser-Side Templating pattern (BST).

The Browser-Side Templating Pattern

The goal of the BST pattern is to separate the code that produces a view of data from the data itself. A longstanding problem in software systems, separation between view and data has its canonical solution in any variation of the Model View Controller (MVC) pattern.

While display patterns like MVC and BST are not at all mutually exclusive, you can consider BST by itself to be like an MVC, where you have no inherent notion of a controller and keep view and model separated by the actual separation between the browser and server. In Figure 5, you see a diagram of the steps involved in a BST. The user triggers a remote call that downloads some data to the client. The data is managed by a JavaScript callback that instantiates a new breed of component—the markup builder.

fig05.gif

Figure 5 Browser-Side Templating in Action

The markup builder returns an HTML string based on a reference to one or more HTML templates in the page document object model (DOM) and the downloaded data. Finally, the callback injects the string in the page's DOM.

Now let's take a look at some code. In this implementation, the heart of BST is in the JavaScript MarkupBuilder class, which accepts up to three HTML templates—header, footer, and item. You can reference these templates directly from the DOM or you can specify them as plain string literals:

function pageLoad()
{
    if (builder === null)
    {
        builder = new Samples.MarkupBuilder();
        builder.loadHeader($get("header"));
        builder.loadFooter($get("footer"));
        builder.loadItemTemplate($get("item"));
   }
}

You can embed HTML templates directly in the page using invisible DIVs. However, this only works if the markup in the block is well formed. A better option would be to embed templates as XML data islands, as shown here:

<xml id="item">
    <tr style="background-color:#F0FAFF;">
        <td align="left">#Symbol</td>
        <td align="right">#Quote</td>
        <td align="right">#Change</td>
    </tr> 
</xml>

As you can see, the template is a chunk of HTML that refers to binding fields using a custom notation. In this case, I'm using the #PropertyName expression to indicate the placeholder for a bound value. When data is available, you just invoke the bind method on the markup builder:

function _getLiveQuotes()
{
    Samples.WebServices.LiveQuoteService.Update(onDataAvailable);
}
function onDataAvailable(results)
{
    var temp = builder.bind(results);
    $get("grid").innerHTML = temp;
}

Needless to say, the element named grid is the placeholder for the final output in this example. The MarkupBuilder class is based on the Microsoft AJAX Client Library. Its full source code is found in Figure 6.

Figure 6 MarkupBuilder Class

Type.registerNamespace('Samples');

// Class ctor
Samples.MarkupBuilder = function Samples$MarkupBuilder() 
{
    Samples.MarkupBuilder.initializeBase(this);

    // Initializes private members
    this._header = "";
    this._footer = "";
    this._itemTemplate = "";
}
Samples.MarkupBuilder = function Samples$MarkupBuilder(header, footer) 
{
   Samples.MarkupBuilder.initializeBase(this);

    // Initializes the private members
    this._header = header;
    this._footer = footer;
    this._itemTemplate = "";
}

// PROPERTY:: header (String)
function Samples$MarkupBuilder$get_header() { 
    if (arguments.length !== 0) throw Error.parameterCount();
    return this._header;
}
function Samples$MarkupBuilder$set_header(value) {
    var e = Function._validateParams(arguments, [{name: 'value', 
        type: String}]);
    if (e) throw e;

    this._header = value;
}

// PROPERTY:: footer (String)
function Samples$MarkupBuilder$get_footer() { 
    if (arguments.length !== 0) throw Error.parameterCount();
    return this._footer;
}
function Samples$MarkupBuilder$set_footer(value) {
    var e = Function._validateParams(arguments, [{name: 'value', 
        type: String}]);
    if (e) throw e;

    this._footer = value;
}

// PROPERTY:: itemTemplate (String)
function Samples$MarkupBuilder$get_itemTemplate() { 
    if (arguments.length !== 0) throw Error.parameterCount();
    return this._itemTemplate;
}
function Samples$MarkupBuilder$set_itemTemplate(value) {
    var e = Function._validateParams(arguments, [{name: 'value', 
        type: String}]);
    if (e) throw e;

    this._itemTemplate = value;
}

// METHOD:: bind()
function Samples$MarkupBuilder$bind(data) {
   var temp = this._generate(data);
   return temp;
}
// METHOD:: loadHeader()
function Samples$MarkupBuilder$loadHeader(domElement) {
   var temp = domElement.innerHTML;
   this._header = temp;
}

// METHOD:: loadFooter()
function Samples$MarkupBuilder$loadFooter(domElement) {
   var temp = domElement.innerHTML;
   this._footer = temp;
}

// METHOD:: loadItemTemplate()
function Samples$MarkupBuilder$loadItemTemplate(domElement) {
   var temp = domElement.innerHTML;
   this._itemTemplate = temp;
}

///////                     ///////
///////  PRIVATE members    ///////
///////                     ///////

function Samples$MarkupBuilder$_generate(data) {
    var _builder = new Sys.StringBuilder(this._header);

    for(i=0; i<data.length; i++)
    {
        var dataItem = data[i];
        var template = this._itemTemplate;


        var pattern = /#\w+/g;  // Finds all #word occurrences
        var matches = template.match(pattern); 
        for (j=0; j<matches.length; j++)
        {
            var name = matches[j];
            name = name.slice(1);
            template = template.replace(matches[j], dataItem[name]);
        }

        _builder.append(template);
    }

    _builder.append(this._footer);
    return _builder.toString();
}

// PROTOTYPE
Samples.MarkupBuilder.prototype = 
{
    get_header:         Samples$MarkupBuilder$get_header,
    set_header:         Samples$MarkupBuilder$set_header,
    get_footer:         Samples$MarkupBuilder$get_footer,
    set_footer:         Samples$MarkupBuilder$set_footer,
    get_itemTemplate:   Samples$MarkupBuilder$get_itemTemplate,
    set_itemTemplate:   Samples$MarkupBuilder$set_itemTemplate,
    bind:               Samples$MarkupBuilder$bind,
    _generate:          Samples$MarkupBuilder$_generate,
    loadHeader:         Samples$MarkupBuilder$loadHeader,
    loadFooter:         Samples$MarkupBuilder$loadFooter,
    loadItemTemplate:   Samples$MarkupBuilder$loadItemTemplate
}

Samples.MarkupBuilder.registerClass('Samples.MarkupBuilder');

Internally, the markup builder uses a Sys.StringBuilder object to compose the HTML string. It first adds the header template (if any) and then proceeds to loop over the bound data. Finally, it adds the footer template. In this implementation, header and footer templates are not data-bound. (Figure 7 shows a page using the markup builder.)

fig07.gif

Figure 7 New Page Using the Markup Builder

Further Considerations

Is there any difference between specifying the template as a plain string or as an embedded DOM subtree or XML data island? From the markup builder's perspective, it doesn't really matter—all that it needs is a string to append to the string builder buffer. From the developer's perspective, using DOM templates may be a better option because you can more easily validate your template and might also be able to download the template from a URL.

Designed to work only with collections of data, the solution presented here follows the guidelines of other free frameworks for client-side templating. One of these frameworks that I would like to specifically mention is PrototypeJS. For more information, samples, and source code, refer to prototypejs.org/api/template.

Is there anything that you can improve? Absolutely! Note that the code presented in this column implements the minimum set of features one would expect from a browser-side template and binding engine.

As you can see in Figure 7, the current implementation does not have colored type to distinguish the prices of rising and falling stocks, as Figure 2 had. In the code of Figure 2, I handle the RowDataBound event of the GridView control and directly modify the style of involved cells:

void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.Cells[2].Text.StartsWith("+"))
        e.Row.Cells[2].ForeColor = Color.Green;
    if (e.Row.Cells[2].Text.StartsWith("-"))
        e.Row.Cells[2].ForeColor = Color.Red;
}

Within a partial rendering solution, this styling happens on the server and therefore has the ability to leverage the rich programming model of server data-bound controls. In a client-side solution, you need a more sophisticated markup builder that accepts a function delegate that styles any piece of bound data before it is appended to the buffer.

Another more important difference exists between the code that produced Figures 2 and 7 that doesn't show up through the printed figures. To discover this difference, try running both pages and click on the other buttons you see (Weather Forecasts and Start Some Task). The page depicted in Figure 7 does not stop other tasks while it retrieves stock quotes and the service call is not aborted if the user clicks to start some other task.

What's the point? Remember that with partial rendering, you can only have one request pending at a time per session—just like in a normal non-AJAX page. Of course, you can have any number of simultaneous calls if you control them directly through XMLHttpRequest. Furthermore, if you make direct service calls, you have no view state around and no worries about keeping it consistent across multiple partial rendering calls (which is the real reason why the partial rendering implementation prevents you from executing concurrent operations).

The HTML Message Pattern

To top off this column, let me just make a quick comment on another pattern that really deserves more attention in a future column—it's called the HTML Message pattern. The goal of the HTML Message pattern is to have the server generate blocks of HTML markup to be displayed in the browser. As you can see, it falls somewhere between partial rendering and other models for building true AJAX applications.

How would you use this pattern? One possible implementation would be to make a call to a remote URL (either a service or an ad hoc HTTP handler) and receive an HTML snippet ready for display. Note that it generates more traffic than plain service calls but also less traffic than partial rendering.

On the server, you can use fresh instances of server controls (no view state) to assemble any output you need, styled as you like. I'll return to this pattern in a future column.

In the meantime, remember that when you're working with ASP.NET AJAX, you need programming tools to define an AJAX service layer and call it from the client browser. In addition, you need powerful tools to manipulate data on the client effectively, such as JavaScript-based data binding and templates.

Hopefully when you take a look at the sample implementation of the BST AJAX pattern I presented in this column and compare it to solutions based on partial rendering, you will inevitably come to the same conclusions that I did.

Insights: ASP.NET AJAX

Template-driven rendering has long been one of the major strengths of ASP.NET server controls. Templates offer a number of advantages over older techniques of looping around markup using <% %> and <%= %> blocks. One could cite two-way binding and general cleanliness of the model. On the client, without a framework, even simple <% %> blocks are not available, so a good template engine represents a major improvement. It also enables lots of optimizations that are easy to achieve on the client but a lot more difficult on the server, such as only re-rendering the changed items in a list instead of re-rendering the whole list. On the other hand, rendering performance on the client leaves a lot to be desired, so a template engine needs to be especially well optimized for performance.

Dino describes here a simple approach to client-side template rendering that is both easy to set up and works today. The ASP.NET team of course recognizes the power of templates. In fact, when we shipped the first previews of ASP.NET AJAX, we introduced a very elaborate declarative syntax, called xml-script, which among other things enabled template-driven rendering. Unfortunately, it was perceived as too complex and verbose; also, the performance wasn't as good as we and our users would have liked. Moreover, it lacked tooling support.

It's important to note that there is a lot more to templates than just inserting data into placeholders in an HTML string. First, a template engine must have some sort of expression language to go beyond the simple field insertion. Second, it should enable scenarios of conditional rendering. And most importantly, it should be simple to add rich behavior to the rendered markup. In other words, the engine must make it simple to instantiate components that attach to the markup it created.

In the next version of ASP.NET AJAX, we're going to revisit templates and reintroduce them into the AJAX framework, along with a simple declarative component syntax. Here's what a simple list view could look like:

<body xmlns:sys="javascript:Sys" xmlns:dv="javascript:Sys.UI.DataView">
...
<div id="dataview1" sys:type="dv" dv:data="{{someArray}}">
  <div class="sys-template">
    <h2><a href="{{ 'products/' + id }}">{{name}}</a></h2>
    <!--% if (description) { %-->
      <p>{{description}}</p>
    <!--% } %-->
  </div>
</div>

There are a few things to notice here. Expressions are embedded into the markup using {{ }} delimiters. These expressions can be direct references to current data item fields (name, description, and so on) or can be complex JavaScript expressions ('products/' + id). On the body tag, we define a "dv" namespace that maps to a Data­View JavaScript component. Later, we set sys:type on the "dataview1" div to "dv", indicating that we want to instantiate a DataView control and attach it to that element.

On the same tag, dv:data="{{someArray}}" sets the data property of the DataView control to the someArray global variable. The same declarative syntax can be used inside of the template to instantiate and attach arbitrary controls and behaviors. The template itself here is the markup inside the dataview1 div, but it could be built from external contents. Finally, the engine supports arbitrary code to be embedded in the template markup using <!--% %--> blocks, which we use here to conditionally render a paragraph containing the description data column.

—Bertrand Le Roy
Program Manager, ASP.NET

Dino Esposito is the author of Programming ASP.NET 3.5 Core References. Based in Italy, Dino is a frequent speaker at industry events worldwide. You can join his blog at weblogs.asp.net/despos. Send your questions and comments for Dino to cutting@microsoft.com. Code download available at msdn2.microsoft.com/magazine/cc135911.