Programming Microsoft ASP.NET 2.0 Core Reference
Data-Binding Expressions
What we have examined so far is the most common form of data binding that involves list and iterative controls and collections of data. Note that any ASP.NET controls support some minimal form of data binding, including text boxes and labels, through the DataBind method. In its simplest form, a binding is a connection between one piece of data and a server control property. This simple form of binding is established through a special expression that gets evaluated when the code in the page calls the DataBind method on the control.
Simple Data Binding
A data-binding expression is any executable code wrapped by <% %> and prefixed by the symbol #. Typically, you use data-binding expressions to set the value of an attribute in the opening tag of a server control. A data-binding expression is programmatically managed via an instance of the DataBoundLiteralControl class.
Note | The binding expression is really any executable code that can be evaluated at run time. Its purpose is to generate data that the control can use to bind for display or editing. Typically, the code retrieves data from the data source, but there is no requirement that this be the case. Any executable code is acceptable as long as it returns data for binding. |
The following code snippet shows how to set the text of a label with the current time:
<asp:label runat="server" Text='<%# DateTime.Now %>' />
Within the delimiters, you can invoke user-defined page methods, static methods, and properties and methods of any other page components. The following code demonstrates a label bound to the name of the currently selected element in a drop-down list control:
<asp:label runat="server" Text='<%# dropdown.SelectedItem.Text %>' />
Note that if you're going to use quotes within the expression, you should wrap the expression itself with single quotes. The data-binding expression can accept a minimal set of operators, mostly for concatenating subexpressions. If you need more advanced processing and use external arguments, resort to a user-defined method. The only requirement is that the method is declared public or protected.
Important | Any data-bound expression you define in the page is evaluated only after DataBind is called. You can either call DataBind on the page object or on the specific control. If you call DataBind on the page object, it will recursively call DataBind on all controls defined in the page. If DataBind is not called, no <%# %> expressions will ever be evaluated. |
Binding in Action
Data-binding expressions are particularly useful to update, in a pure declarative manner, properties of controls that depend on other controls in the same page. For example, suppose you have a drop-down list of colors and a label, and you want the text of the label to reflect the selected color:
<asp:DropDownList runat="server" AutoPostBack="True"> <asp:ListItem>Orange</asp:ListItem> <asp:ListItem>Green</asp:ListItem> <asp:ListItem>Red</asp:ListItem> <asp:ListItem>Blue</asp:ListItem> </asp:DropDownList> <asp:Label runat="server" Text='<%# "<b>You selected: </b>" + SelColors.SelectedValue %>' />
Note that in the <%# %> expression you can use any combination of methods, constants, and properties as long as the final result matches the type of the bound property. Also note that the evaluation of the expression requires a postback and a call to DataBind. We set the AutoPostBack property to true just to force a postback when the selection changes in the drop-down list. At the same time, a call to the page's or label's DataBind method is required for the refresh to occur:
protected void Page_Load(object sender, EventArgs e) { DataBind(); }
You can bind to expressions virtually any control properties regardless of the type. Let's see how to bind the ForeColor property of the Label control to the color string picked from the drop-down list:
ForeColor='<%# Color.FromName(SelColors.SelectedValue) %>'
Note that you can't just set ForeColor to an expression that evaluates to a color string, such as "orange":
ForeColor='<%# SelColors.SelectedValue %>'
The preceding code won't compile because of the impossible automatic conversion between a string (your expression) and a color (the type of the ForeColor property). Interestingly enough, of the two following statements only the second will work fine:
ForeColor='<%# "orange" %>' ForeColor="orange"
The upshot is that a data-binding expression requires that the return type match the type of the property represented via an attribute. Using a plain constant string is fine, on the other hand, because the page parser recognizes the expression and seamlessly inserts proper conversion code, if such a conversion is possible. Figure 9-8 shows the sample page in action.
Implementation of Data-Binding Expressions
What really happens when a data-binding expression is found in a Web page? How does the ASP.NET runtime process it? Let's consider the following code:
<asp:label runat="server" text='<%# DateTime.Now %>' />
When the page parser takes care of the .aspx source code, it generates a class where each server control has a factory method. The factory method simply maps the tag name to a server-side control class and transforms attributes on the tag into property assignments. In addition, if a data-binding expression is found, the parser adds a handler for the DataBinding event of the control a Label in this case. Here's some pseudocode to illustrate the point:
private Control __BuildControlToday() { Label __ctrl = new Label(); this.today = __ctrl; __ctrl.ID = "today"; __ctrl.DataBinding += new EventHandler(this.__DataBindToday); return __ctrl; }
The handler assigns the data-binding expression verbatim to the property:
public void __DataBindToday(object sender, EventArgs e) { Label target; target = (Label) sender; target.Text = Convert.ToString(DateTime.Now); }
If the value returned by the data-binding expression doesn't match the expected type, you generally get a compile error. However, if the expected type is string, the parser attempts a standard conversion through the Convert.ToString method. (All .NET Framework types are convertible to a string because they inherit the ToString method from the root object type.)
The DataBinder Class
Earlier in this chapter, we met <%# %> expressions in the context of templates along with the Eval method. The Eval method is a kind of tailor-made operator you use in data-binding expressions to access a public property on the bound data item. The Eval method as used earlier is an ASP.NET 2.0-only feature and will generate a compile error if used in ASP.NET 1.x applications. For all versions of ASP.NET, you can use a functionally equivalent method, also named Eval, but from another class DataBinder.
Important | Through the Eval method even if it comes from DataBinder or Page you can access public properties on the bound data item. Let me clarify what public properties are in this context and why I insist on calling them properties. Any class that implements IEnumerable can be bound to a control. The list of actual classes certainly includes DataTable (where a data item logically corresponds to table record), but it also includes custom collections (where a data item corresponds to an instance of a given class). The Eval method ends up querying the data item object for its set of properties. The object that represents a table record will return descriptors for its columns; other objects will return their set of public properties. |
The DataBinder class supports generating and parsing data-binding expressions. Of particular importance is its overloaded static method Eval. The method uses reflection to parse and evaluate an expression against a run-time object. Clients of the Eval method include RAD tools such as Microsoft Visual Studio .NET designers and Web controls that declaratively place calls to the method to feed properties dynamically changing values.
The Eval Method
The syntax of DataBinder.Eval typically looks like this:
<%# DataBinder.Eval(Container.DataItem, expression) %>
A third, optional parameter is omitted in the preceding snippet. This parameter is a string that contains formatting options for the bound value. The Container.DataItem expression references the object on which the expression is evaluated. The expression is typically a string with the name of the field to access on the data item object. It can be an expression that includes indexes and property names. The DataItem property represents the object within the current container context. Typically, a container is the current instance of the item object for example, a DataGridItem object that is about to be rendered.
The code shown earlier is commonly repeated, always in the same form. Only the expression and the format string change from page to page.
A More Compact Eval
The original syntax of DataBinder.Eval can be simplified in ASP.NET 2.0, as we already saw earlier in the Repeater example. In ASP.NET 2.0, you can use
<%# Eval(expression) %>
wherever the following expression is accepted in ASP.NET 1.x:
<%# DataBinder.Eval(Container.DataItem, expression) %>
It goes without saying that the DataBinder object is also fully supported in ASP.NET 2.0.
Any piece of code that appears within the <%# %> delimiters enjoys special treatment from the ASP.NET runtime. Let's briefly look at what happens with this code. When the page is compiled for use, the Eval call is inserted in the source code of the page as a standalone call. The following code gives an idea of what happens:
object o = Eval("lastname"); string result = Convert.ToString(o);
The result of the call is converted to a string and is assigned to a data-bound literal control an instance of the DataBoundLiteralControl class. Then the data-bound literal is inserted in the page's control tree.
In ASP.NET 2.0, the TemplateControl class the parent of Page is actually enriched with a new, protected (but not virtual) method named Eval. The following pseudocode illustrates how the method works:
protected object Eval(string expression) { if (Page == null) throw new InvalidOperationException( ); return DataBinder.Eval(Page.GetDataItem(), expression); }
As you can see, Eval is a simple wrapper built around the DataBinder.Eval method. The DataBinder.Eval method is invoked using the current container's data item. Quite obviously, the current container's data is null outside a data-binding operation that is, in the stack of calls following a call to DataBind. This fact brings up a key difference between Eval and DataBinder.Eval.
Important | The TemplateControl's Eval is a data-binding method and can be used only in the context of a data-bound control during a data-binding operation. On the other hand, DataBinder.Eval is a fully fledged method that can be used anywhere in the code. Typically, you use it in the implementation of custom data-bound controls. I'll show this in the companion volume, Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics. |
Getting the Default Data Item
The pseudocode that illustrates the behavior of the page's Eval method shows a GetDataItem method off the Page class. What is it? As mentioned, the simplified syntax assumes a default Container.DataItem context object. GetDataItem is simply the function that returns that object.
More precisely, GetDataItem is the endpoint of a stack-based mechanism that traces the current binding context for the page. Each control in the control tree is pushed onto this stack at the time the respective DataBind method is called. When the DataBind method returns, the control is popped from the stack. If the stack is empty, and you attempt to call Eval programmatically, GetDataItem throws an invalid operation exception. In summary, you can use the Eval shortcut only in templates; if you need to access properties of a data item anywhere else in the code, resort to DataBinder.Eval and indicate the data item object explicitly.
Tip | As mentioned, you generally need to call DataBinder.Eval directly only in the code of custom data-bound controls. (I cover custom controls in Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics.) When this happens, though, you might want to save a few internal calls and CPU cycles by calling DataBinder.GetPropertyValue instead. This is exactly what DataBinder.Eval does in the end. |
Other Data-Binding Methods
In ASP.NET 2.0, data-binding expressions go far beyond read-only evaluation of enumerable and tabular data. In addition to DataBinder, ASP.NET 2.0 provides a class that can bind to the result of XPath expressions that are executed against an object that implements the IXPathNavigable interface. This class is named XPathBinder; it plays the same role as DataBinder, except it works on XML data. The XPathBinder class backs up a new data-binding method named XPath.
ASP.NET 2.0 also supports declarative two-way data binding, meaning that you can read and write data item properties through a new data-binding method named Bind.
Finally, ASP.NET 2.0 supports user-defined expressions that operate outside the boundaries of data-binding operations. It might seem weird that I discuss non-data-binding expressions in a section explicitly dedicated to data-binding expressions. The reason I mention this option here is to avoid confusion, as the syntax for custom expressions is nearly identical.
The XPath Method
In ASP.NET 2.0, data-bound controls can be associated with raw XML data. You can bind XML data in version 1.x, but you have to first fit XML data into a relational structure such as a DataSet. When a templated control such as DataList or Repeater is bound to an XML data source (such as the new XmlDataSource control, which we'll cover in the next section), individual XML fragments can be bound inside the template using the XPathBinder object.
The XPathBinder.Eval method accepts an XmlNode object along with an XPath expression, and it evaluates and returns the result. The output string can be formatted if a proper format string is specified. XPathBinder.Eval casts the container object to IXPathNavigable. This is a prerequisite to applying the XPath expression. If the object doesn't implement the interface, an exception is thrown. The IXPathNavigable interface is necessary because in the .NET Framework the whole XPath API is built for, and works only with, objects that provide a navigator class. The goal of the interface is creating an XPath navigator object for the query to run.
Like DataBinder, the XPathBinder class supports a simplified syntax for its evaluator method. The syntax assumes a default container context that is the same object that is tracked for the data binder. The following example demonstrates using the simplified XPath data-binding syntax:
<%# XPath("Orders/Order/Customer/LastName") %>
The output value is the object returned by XPathBinder.Eval converted to a string. Internally, XPathBinder.Eval gets a navigator object from the data source and evaluates the expression. The managed XPath API is used.
Note | In this book, we don't cover XML classes in the .NET Framework. A good reference is my book Applied XML with the .NET Framework (Microsoft Press, 2003). The book covers .NET Framework 1.x, but as far as XPath is concerned, what you can learn from that source is exactly what you need to know. |
The XPathSelect Method
The XPathBinder class also features a Select method. The method executes an XPath query and retrieves a nodeset an enumerable collection of XML nodes. This collection can be assigned as a late-bound value to data-bound controls (such as the Repeater control). An equivalent simplified syntax exists for this scenario, too:
<asp:Repeater runat="server" DataSource='<%# XPathSelect("orders/order/summary") %>'> ... </asp:Repeater>
XPathSelect is the keyword you use in data-binding expressions to indicate the results of an XPath query run on the container object. If the container object does not implement IXPathNavigable, an exception is thrown. Like Eval and XPath, XPathSelect assumes a default data item context object.
The Bind Method
As we'll see in Chapter 11, ASP.NET 2.0 supports two-way data binding that is, the capability to bind data to controls and submit changes back to the database. The Eval method is representative of a one-way data binding that automates data reading but not data writing. The new Bind method can be used whenever Eval is accepted and through a similar syntax:
<asp:TextBox Runat="server" Text='<%# Bind("notes") %>' />
The big difference is that Bind works in both directions reading and writing. For example, when the Text property is being set, Bind behaves exactly like Eval. In addition, when the Text property is being read, Bind stores the value into a collection. Enabled ASP.NET 2.0 databound controls (for example, the new FormView control and other templated controls) automatically retrieve these values and use them to compose the parameter list of the insert or edit command to run against the data source. The argument passed to Bind must match the name of a parameter in the command. For example, the text box shown earlier provides the value for the @notes parameter.
User-Defined Dynamic Expressions
Data-binding expressions are not really dynamic expressions because they are evaluated only within the context of a data-binding call. ASP.NET 2.0 provides a made-to-measure infrastructure for dynamic expressions based on a new breed of components the expression builders. (I cover expression builders in Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics.)
Dynamic expressions have a syntax that is similar to data binding, except that they use the $ prefix instead of #. Dynamic expressions are evaluated when the page compiles. The content of the expression is extracted, transformed into code, and injected into the code created for the page. A few predefined expression builders exist, as listed in Table 9-5.
Syntax | Description |
---|---|
AppSettings:XXX | Returns the value of the specified setting from the <appSettings> section of the configuration file. |
ConnectionStrings:XXX[.YYY] | Returns the value of the specified XXX string from the <connectionStrings> section of the configuration file. The optional YYY parameter indicates which attribute is read from the section. It can be either connectionString (default) or providerName. |
Resources:XXX, YYY | Returns the value of the YYY global resource read from the XXX resource file (.resx). |
To declaratively bind a control property to the value of the expression, you follow the schema shown here:
<%$ expression %>
The exact syntax is defined by the builder associated with each expression. Note, though, that literal expressions are not permitted in the body of the page. In other words, you can use expression only to set a control property. You can't have the following:
<h1><%$ AppSettings:AppVersionNumber %></h1>
Instead, you should wrap the expression in a server control, the simplest of which would be the Literal control. The following code generates the page in Figure 9-9:
<h1><asp:Literal runat="server" Text="<%$ Resources:Resource, AppWelcome %>" /></h1> <hr /> <b>Code version <asp:Literal runat="server" Text="<%$ AppSettings:AppVersionNumber %>" /></b>
Needless to say, you need to have an AppVersionNumber string resource in the App_GlobalResource and an AppWelcome setting in the web.config file:
<appSettings> <add key="AppVersionNumber" value="8.2.2001" /> </appSettings>
The remaining expression ConnectionStrings is extremely helpful with data source controls to avoid hard-coding the connection string in the .aspx file.
Note | Microsoft provides the few built-in expression builders listed in Table 9-5. Developers can define others by simply writing new classes that inherit from ExpressionBuilder. To be recognized and properly handled, custom expression builders must be registered in the web.config file. I'll touch on this topic in Programming Microsoft ASP.NET 2.0 Applications: Advanced Topics. |