ASP.NET 4.0 Enhancements to Data Controls

Picking up where I left off some time back on ASP.NET 4.0, today we take a look at some of the enhancements we’re making to data controls in ASP.NET 4.0. Let’s start with the easy stuff.

Cleaner HTML

Over the years we’ve made significant advances in making the markup generated by ASP.NET server controls standards compliant, flexible and easy to style the way you want with CSS. Whether it be enhancements to the controls themselves, the introduction of new controls such as the ListView control or the creation of the CSS Friendly Control Adapters, all have contributed to greater flexibility and standards compliance in the generated markup.

One control that still suffers from enforcing a table-based layout is the FormView. The markup:

 <asp:FormView ID="FormView1" runat="server">
  <ItemTemplate>
    <div>
      <%# Eval("MyData") %>
    </div>
  </ItemTemplate>
</asp:FormView>

Results in the following HTML:

 <TABLE style='BORDER-COLLAPSE: collapse' id=TABLE1 border=0 cellSpacing=0>
  <TBODY>
    <TR>
      <TD colSpan=2>
        <DIV>Some Data </DIV>
      </TD>
    </TR>
  </TBODY>
</TABLE>

ie my ItemTemplate is wrapped in a <table> element and there’s nothing I can do about it. The FormView in ASP.NET 4.0 has a new property called RenderTable:

 <asp:FormView ID="FormView1" RenderTable="false" runat="server">
  <ItemTemplate>
    <div>
      <%# Eval("MyData") %>
    </div>
  </ItemTemplate>
</asp:FormView>

Which results in the following when the page renders:

 <div>
  Some Data
</div>

Simpifying Your Markup

The ListView control was introduced in ASP.NET 3.5 as a powerful, flexible alternative to the GridView control. It gives you complete control over the generated markup. It does have an obsession with LayoutTemplates and ItemPlaceholders though.

The following markup works fine in ASP.NET 4.0 but results in the YSOD in ASP.NET 3.5, complaining that “An item placeholder must be specified on ListView 'ListView1'. Specify an item placeholder by setting a control's ID property to "itemPlaceholder". The item placeholder control must also specify runat="server".

 <asp:ListView ID="ListView1" runat="server">
  <ItemTemplate>
    <div>
      <%# Eval("MyData") %></div>
  </ItemTemplate>
</asp:ListView>

It just makes your life that little bit easier.

Persisted Selection

Persisted selection provides a more intuitive user selection experience on both the GridView and ListView controls. In ASP.NET 3.5, if the user selects an item presented in a GridView or ListView and then pages the control, a corresponding item will be selected on the new page. This is because the row selection is simply based on the row index on the page. Thus this ListView control:

 <asp:ListView ID="ListView1" runat="server" DataSourceID="SqlDataSource1" DataKeyNames="HomeID">
  <ItemTemplate>
    <div>
      <asp:LinkButton CommandName="Select" runat="server">Select</asp:LinkButton>
      <%# Eval("ImageURL") %>
    </div>
  </ItemTemplate>
  <SelectedItemTemplate>
    <div>
      <em><%# Eval("ImageURL") %></em>
    </div>
  </SelectedItemTemplate>
</asp:ListView>

Results in the following paging experience for the user:

PersistedSelectionFalse1 PersistedSelectionFalse2 PersistedSelectionFalse3 PersistedSelectionFalse4

Setting EnablePersistedSelection="true" on the ListView (or GridView) changes that to the more intuitive experience below:

PersistedSelectionTrue1 PersistedSelectionTrue2 PersistedSelectionTrue3 PersistedSelectionTrue4

QueryExtenders

The QueryExtender control works in conjunction with either the LinqDataSource or EntityDataSource controls to modify the query expression generated by the DataSource control to apply additional filter operations (where clause) such as search, range and property matching. You can also include ordering and build your own custom expressions. Let’s take a look at an example starting with this simple LinqDataSource control which queries against my database of fictitious properties

 <asp:LinqDataSource ID="LinqDataSource1" runat="server" ContextTypeName="DataClassesDataContext"
  EntityTypeName="" 
  Select="new (HomeID, Bedrooms, ImageURL, Price, Available, Description)" 
  TableName="Homes">
</asp:LinqDataSource>

Using SQL Server Profiler, I can see that this results in the following query against my DB

 SELECT 
  [t0].[HomeID], 
  [t0].[Bedrooms], 
  [t0].[ImageURL], 
  [t0].[Price], 
  [t0].[Available]
FROM [dbo].[Homes] AS [t0]

By adding a QueryExtender I can modify the query expression and therefore the query that ultimately runs against my DB

 <asp:QueryExtender ID="QueryExtender1" TargetControlID="LinqDataSource1" runat="server"> 
  <asp:OrderByExpression DataField="Price" Direction="Descending"></asp:OrderByExpression>
</asp:QueryExtender>

This results in

 SELECT 
  [t0].[HomeID], 
  [t0].[Bedrooms], 
  [t0].[ImageURL], 
  [t0].[Price], 
  [t0].[Available], 
  [t0].[Description]
FROM [dbo].[Homes] AS [t0]
ORDER BY [t0].[Price] DESC

Of course I can take things a lot further by adding additional expressions to my QueryExtender thus

 <asp:QueryExtender ID="QueryExtender1" TargetControlID="LinqDataSource1" runat="server"> 
  <asp:OrderByExpression DataField="Price" Direction="Descending"></asp:OrderByExpression>
  <asp:RangeExpression DataField="Price" MinType="Inclusive" MaxType="Inclusive">
    <asp:Parameter DefaultValue="200000" DbType="Int32" />
    <asp:Parameter DefaultValue="750000" DbType="Int32" />
  </asp:RangeExpression>
  <asp:PropertyExpression>
    <asp:Parameter Name="Available" DefaultValue="True" DbType="Boolean" />
  </asp:PropertyExpression>
  <asp:SearchExpression DataFields="Description" SearchType="Contains">
    <asp:Parameter DefaultValue="private" DbType="String" />
  </asp:SearchExpression>
</asp:QueryExtender>

Which results in the following T-SQL executing against my DB

 exec sp_executesql 
N'SELECT [t0].[HomeID], 
  [t0].[Bedrooms], 
  [t0].[ImageURL], 
  [t0].[Price], 
  [t0].[Available], 
  [t0].[Description]
FROM 
  [dbo].[Homes] AS [t0]
WHERE 
  ([t0].[Description] LIKE @p0) AND 
    ([t0].[Available] = @p1) AND 
    ([t0].[Price] >= @p2) AND ([t0].[Price] <= @p3)
ORDER BY 
  [t0].[Price] DESC',
N'@p0 nvarchar(4000),@p1 int,@p2 int,@p3 int',
@p0=N'%private%',
@p1=1,
@p2=200000,
@p3=750000

So the QueryExtender gives me a simple, but powerful declarative mechanism for customising my queries with either the EntityDataSource or LinqDataSource controls by taking advantage of the the deferred execution and extensible nature of LINQ expressions.

Technorati Tags: asp.net,data,linq