Using TemplateFields in the DetailsView Control (C#)

by Scott Mitchell

Download PDF

The same TemplateFields capabilities available with the GridView are also available with the DetailsView control. In this tutorial we'll display one product at a time using a DetailsView containing TemplateFields.

Introduction

The TemplateField offers a higher degree of flexibility in rendering data than the BoundField, CheckBoxField, HyperLinkField, and other data field controls. In the previous tutorial we looked at using the TemplateField in a GridView to:

  • Display multiple data field values in one column. Specifically, both the FirstName and LastName fields were combined into one GridView column.
  • Use an alternate Web control to express a data field value. We saw how to show the HiredDate value using a Calendar control.
  • Show status information based on the underlying data. While the Employees table does not contain a column that returns the number of days an employee has been on the job, we were able to display such information in the GridView example in the previous tutorial with the use of a TemplateField and formatting method.

The same TemplateFields capabilities available with the GridView are also available with the DetailsView control. In this tutorial we'll display one product at a time using a DetailsView that contains two TemplateFields. The first TemplateField will combine the UnitPrice, UnitsInStock, and UnitsOnOrder data fields into one DetailsView row. The second TemplateField will display the value of the Discontinued field, but will use a formatting method to display "YES" if Discontinued is true, and "NO" otherwise.

Two TemplateFields are Used to Customize the Display

Figure 1: Two TemplateFields are Used to Customize the Display (Click to view full-size image)

Let's get started!

Step 1: Binding the Data to the DetailsView

As discussed in the previous tutorial, when working with TemplateFields it's often easiest to start by creating the DetailsView control that contains just BoundFields and then add new TemplateFields or convert the existing BoundFields to TemplateFields as needed. Therefore, start this tutorial by adding a DetailsView to the page through the Designer and binding it to an ObjectDataSource that returns the list of products. These steps will create a DetailsView with BoundFields for each of the product's non-Boolean value fields and a CheckBoxField for the one Boolean value field (Discontinued).

Open the DetailsViewTemplateField.aspx page and drag a DetailsView from the Toolbox onto the Designer. From the DetailsView's smart tag choose to add a new ObjectDataSource control that invokes the ProductsBLL class's GetProducts() method.

Add a New ObjectDataSource Control that Invokes the GetProducts() Method

Figure 2: Add a New ObjectDataSource Control that Invokes the GetProducts() Method (Click to view full-size image)

For this report remove the ProductID, SupplierID, CategoryID, and ReorderLevel BoundFields. Next, reorder the BoundFields so that the CategoryName and SupplierName BoundFields appear immediately after the ProductName BoundField. Feel free to adjust the HeaderText properties and formatting properties for the BoundFields as you see fit. Like with the GridView, these BoundField-level edits can be performed through the Fields dialog box (accessible by clicking on the Edit Fields link in the DetailsView's smart tag) or through the declarative syntax. Lastly, clear out the DetailsView's Height and Width property values in order to allow the DetailsView control to expand based on the data displayed and check the Enable Paging checkbox in the smart tag.

After making these changes, your DetailsView control's declarative markup should look similar to the following:

<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False"
    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"
    EnableViewState="False">
    <Fields>
        <asp:BoundField DataField="ProductName" HeaderText="Product"
          SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
          ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier"
          ReadOnly="True" SortExpression="SupplierName" />
        <asp:BoundField DataField="QuantityPerUnit"
          HeaderText="Qty/Unit" SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice" HeaderText="Price"
          SortExpression="UnitPrice" />
        <asp:BoundField DataField="UnitsInStock"
          HeaderText="Units In Stock" SortExpression="UnitsInStock" />
        <asp:BoundField DataField="UnitsOnOrder"
          HeaderText="Units On Order" SortExpression="UnitsOnOrder" />
        <asp:CheckBoxField DataField="Discontinued"
          HeaderText="Discontinued" SortExpression="Discontinued" />
    </Fields>
</asp:DetailsView>

Take a moment to view the page through a browser. At this point you should see a single product listed (Chai) with rows showing the product's name, category, supplier, price, units in stock, units on order, and its discontinued status.

The Product's Details Are Shown Using a Series of BoundFields

Figure 3: The Product's Details Are Shown Using a Series of BoundFields (Click to view full-size image)

Step 2: Combining the Price, Units In Stock, and Units On Order Into One Row

The DetailsView has a row for the UnitPrice, UnitsInStock, and UnitsOnOrder fields. We can combine these data fields into a single row with a TemplateField either by adding a new TemplateField or by converting one of the existing UnitPrice, UnitsInStock, and UnitsOnOrder BoundFields into a TemplateField. While I personally prefer converting existing BoundFields, let's practice by adding a new TemplateField.

Start by clicking on the Edit Fields link in the DetailsView's smart tag to bring up the Fields dialog box. Next, add a new TemplateField and set its HeaderText property to "Price and Inventory" and move the new TemplateField so that it is positioned above the UnitPrice BoundField.

Add a New TemplateField to the DetailsView Control

Figure 4: Add a New TemplateField to the DetailsView Control (Click to view full-size image)

Since this new TemplateField will contain the values currently displayed in the UnitPrice, UnitsInStock, and UnitsOnOrder BoundFields, let's remove them.

The last task for this step is to define the ItemTemplate markup for the Price and Inventory TemplateField, which can be accomplished either through the DetailsView's template editing interface in the Designer or by hand through the control's declarative syntax. As with the GridView, the DetailsView's template editing interface can be accessed by clicking on the Edit Templates link in the smart tag. From here you can select the template to edit from the drop-down list and then add any Web controls from the Toolbox.

For this tutorial, start by adding a Label control to the Price and Inventory TemplateField's ItemTemplate. Next, click on the Edit DataBindings link from the Label Web control's smart tag and bind the Text property to the UnitPrice field.

Bind the Label's Text Property to the UnitPrice Data Field

Figure 5: Bind the Label's Text Property to the UnitPrice Data Field (Click to view full-size image)

Formatting the Price as a Currency

With this addition, the Label Web control Price and Inventory TemplateField will now display just the price for the selected product. Figure 6 shows a screen shot of our progress thus far when viewed through a browser.

The Price and Inventory TemplateField Shows the Price

Figure 6: The Price and Inventory TemplateField Shows the Price (Click to view full-size image)

Note that the product's price is not formatted as a currency. With a BoundField, formatting is possible by setting the HtmlEncode property to false and the DataFormatString property to {0:formatSpecifier}. For a TemplateField, however, any formatting instructions must be specified in the databinding syntax or through the use of a formatting method defined somewhere within the application's code (such as in the ASP.NET page's code-behind class).

To specify the formatting for the databinding syntax used in the Label Web control, return to the DataBindings dialog box by clicking on the Edit DataBindings link from the Label's smart tag. You can type the formatting instructions directly in the Format drop-down list or select one of the defined format strings. Like with the BoundField's DataFormatString property, the formatting is specified using {0:formatSpecifier}.

For the UnitPrice field use the currency formatting specified either by selecting the appropriate drop-down list value or by typing in {0:C} by hand.

Format the Price as a Currency

Figure 7: Format the Price as a Currency (Click to view full-size image)

Declaratively, the formatting specification is indicated as a second parameter into the Bind or Eval methods. The settings just made through the Designer result in the following databinding expression in the declarative markup:

<asp:Label ID="Label1" runat="server" Text='<%# Eval("UnitPrice", "{0:C}") %>'/>

Adding the Remaining Data Fields to the TemplateField

At this point we've displayed and formatted the UnitPrice data field in the Price and Inventory TemplateField, but still need to display the UnitsInStock and UnitsOnOrder fields. Let's display these on a line below the price and in parentheses. From the template editing interface in the Designer, such markup can be added by positioning your cursor within the template and simply typing in the text to be displayed. Alternatively, this markup can be entered directly in the declarative syntax.

Add the static markup, Label Web controls, and databinding syntax so that the Price and Inventory TemplateField displays the price and inventory information like so:

UnitPrice
(In Stock / On Order: UnitsInStock / UnitsOnOrder)

After performing this task your DetailsView's declarative markup should look similar to the following:

<asp:DetailsView ID="DetailsView1" runat="server" AutoGenerateRows="False"
    DataKeyNames="ProductID" DataSourceID="ObjectDataSource1" AllowPaging="True"
    EnableViewState="False">
    <Fields>
        <asp:BoundField DataField="ProductName"
          HeaderText="Product" SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
          ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName"
          HeaderText="Supplier" ReadOnly="True"
          SortExpression="SupplierName" />
        <asp:BoundField DataField="QuantityPerUnit"
          HeaderText="Qty/Unit" SortExpression="QuantityPerUnit" />
        <asp:TemplateField HeaderText="Price and Inventory">
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server"
                  Text='<%# Eval("UnitPrice", "{0:C}") %>'></asp:Label>
                <br />
                <strong>
                (In Stock / On Order: </strong>
                <asp:Label ID="Label2" runat="server"
                  Text='<%# Eval("UnitsInStock") %>'></asp:Label>
                <strong>/</strong>
                <asp:Label ID="Label3" runat="server"
                  Text='<%# Eval("UnitsOnOrder") %>'>
                </asp:Label><strong>)</strong>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:CheckBoxField DataField="Discontinued"
           HeaderText="Discontinued" SortExpression="Discontinued" />
    </Fields>
</asp:DetailsView>

With these changes we've consolidated the price and inventory information into a single DetailsView row.

The Price and Inventory Information is Displayed in a Single Row

Figure 8: The Price and Inventory Information is Displayed in a Single Row (Click to view full-size image)

Step 3: Customizing the Discontinued Field Information

The Products table's Discontinued column is a bit value that indicates whether the product has been discontinued. When binding a DetailsView (or GridView) to a data source control, the Boolean value fields, like Discontinued, are implemented as CheckBoxFields whereas non-Boolean value fields, like ProductID, ProductName, and so on, are implemented as BoundFields. The CheckBoxField renders as a disabled checkbox that is checked if the data field's value is True and unchecked otherwise.

Rather than display the CheckBoxField we may want to instead display text indicating whether or not the product is discontinued. To accomplish this we could remove the CheckBoxField from the DetailsView and then add a BoundField whose DataField property was set to Discontinued. Take a moment to do this. After this change the DetailsView shows the text "True" for discontinued products and "False" for products that are still active.

The Strings True and False Are Used to Display the Discontinued State

Figure 9: The Strings True and False Are Used to Display the Discontinued State (Click to view full-size image)

Imagine that we didn't want the strings "True" or "False" to be used, but "YES" and "NO", instead. Such customization can be performed with the aid of a TemplateField and a formatting method. A formatting method can take in any number of input parameters, but must return the HTML (as a string) to inject into the template.

Add a formatting method to the DetailsViewTemplateField.aspx page's code-behind class named DisplayDiscontinuedAsYESorNO that accepts a Boolean as an input parameter and returns a string. As discussed in the previous tutorial, this method must be marked as protected or public in order to be accessible from the template.

protected string DisplayDiscontinuedAsYESorNO(bool discontinued)
{
    if (discontinued)
        return "YES";
    else
        return "NO";
}

This method checks the input parameter (discontinued) and returns "YES" if it is true, "NO" otherwise.

Note

In the formatting method examined in the previous tutorial recall that we were passing in a data field that might contain NULL s and therefore needed to check if the employee's HiredDate property value had a database NULL value before accessing the EmployeesRow's HiredDate property. Such a check is not needed here since the Discontinued column can never have database NULL values assigned. Moreover, this is why the method can accept a Boolean input parameter rather than having to accept a ProductsRow instance or a parameter of type object.

With this formatting method complete, all that remains is to call it from the TemplateField's ItemTemplate. To create the TemplateField either remove the Discontinued BoundField and add a new TemplateField or convert the Discontinued BoundField into a TemplateField. Then, from the declarative markup view, edit the TemplateField so that it contains just an ItemTemplate that invokes the DisplayDiscontinuedAsYESorNO method, passing in the value of the current ProductRow instance's Discontinued property. This can be accessed via the Eval method. Specifically, the TemplateField's markup should look like:

<asp:TemplateField HeaderText="Discontinued" SortExpression="Discontinued">
    <ItemTemplate>
        <%# DisplayDiscontinuedAsYESorNO((bool)
          Eval("Discontinued")) %>
    </ItemTemplate>
</asp:TemplateField>

This will cause the DisplayDiscontinuedAsYESorNO method to be invoked when rendering the DetailsView, passing in the ProductRow instance's Discontinued value. Since the Eval method returns a value of type object, but the DisplayDiscontinuedAsYESorNO method expects an input parameter of type bool, we cast the Eval methods return value to bool. The DisplayDiscontinuedAsYESorNO method will then return "YES" or "NO" depending on the value it receives. The returned value is what is displayed in this DetailsView row (see Figure 10).

YES or NO Values are Now Shown in the Discontinued Row

Figure 10: YES or NO Values are Now Shown in the Discontinued Row (Click to view full-size image)

Summary

The TemplateField in the DetailsView control allows for a higher degree of flexibility in displaying data than is available with the other field controls and are ideal for situations where:

  • Multiple data fields need to be displayed in one GridView column
  • The data is best expressed using a Web control rather than plain text
  • The output depends on the underlying data, such as displaying metadata or in reformatting the data

While TemplateFields allow for a greater degree of flexibility in the rendering of the DetailsView's underlying data, the DetailsView output still feels a bit boxy as each field is rendered as a row in an HTML <table>.

The FormView control offers a greater degree of flexibility in configuring the rendered output. The FormView does not contain fields but rather just a series of templates (ItemTemplate, EditItemTemplate, HeaderTemplate, and so on). We'll see how to use the FormView to achieve even more control of the rendered layout in our next tutorial.

Happy Programming!

About the Author

Scott Mitchell, author of seven ASP/ASP.NET books and founder of 4GuysFromRolla.com, has been working with Microsoft Web technologies since 1998. Scott works as an independent consultant, trainer, and writer. His latest book is Sams Teach Yourself ASP.NET 2.0 in 24 Hours. He can be reached at mitchell@4GuysFromRolla.com. or via his blog, which can be found at http://ScottOnWriting.NET.

Special Thanks To

This tutorial series was reviewed by many helpful reviewers. Lead reviewer for this tutorial was Dan Jagers. Interested in reviewing my upcoming MSDN articles? If so, drop me a line at mitchell@4GuysFromRolla.com.