Chapter 6. Page Templates

In This Chapter

Built-in Page Templates

Creating a New Page Template

Custom Page Templates

URL Routing

Page Template Lookup Rules

Implementation of CRUD (Create, Read, Update and Delete) operations has been an ongoing challenge in web applications. The problem is not so much their complexity, but rather a sheer number of the pages that have to be implemented in a typical enterprise application. It is common in web applications to have a separate web page for implementing each type of operation, with one page for editing a particular entity type and another for displaying it in Read-only mode. The resulting number of pages can quickly become unmanageable in enterprise applications that have dozens and sometimes hundreds of entity types.

In traditional ASP.NET WebForms applications, developers sometimes try to address this problem by creating multi-purpose pages, such as a single page that allows users to both create and modify instances of a particular entity type. Visual Studio even offers limited support for creating multi-purpose pages by generating the Item, Edit, and Insert templates for FormView controls. Although this approach reduces the overall number of pages in the application, it does so at the cost of significantly increasing complexity of the individual pages.

The ASP.NET MVC framework attempts to solve this problem by offering scaffolding functionality in Visual Studio, which generates separate CRUD pages for a given entity at design time. Although design-time scaffolding helps create the initial version of the CRUD pages, the task of maintaining the generated code falls back onto developers’ shoulders. In a way, the design-time scaffolding solves the problem of supporting CRUD operations by creating a bigger problem of rapidly increasing the number of pages and amount of code developers have to maintain.

ASP.NET Dynamic Data offers an alternative solution to the CRUD problem. Instead of generating pages at design time, like the WebForms and MVC tools do, it generates pages at runtime with the help of page templates.

Built-in Page Templates

When users of a web application need to perform a particular action, such as creating, reading, or modifying an instance of a particular entity type, Dynamic Data can use a single dynamic page template to handle this action across all entity types:

namespace System.Web.DynamicData
{
  public static class PageAction
  {
    public static string Details { get { return "Details"; } }
    public static string Edit    { get { return "Edit";    } }
    public static string Insert  { get { return "Insert";  } }
    public static string List    { get { return "List";    } }
  }
}

Out of the box, Dynamic Data understands actions defined in the PageAction class and provides page templates (see Figure 6.1) for inserting, displaying details, editing, and listing entities as part of the new website and web application projects in Visual Studio.

Image

Figure 6.1. Page templates in a Dynamic Data web application project.

Page templates are special ASPX forms located in the DynamicData/PageTemplates folder of the web application. They rely on URL routing functionality of ASP.NET to handle page requests for different entity types. Dynamic Data applications register at least one DynamicDataRoute during the application startup. Here is an example of the route registration code you normally find in the Global.asax:

RouteTable.Routes.Add(new DynamicDataRoute("{table}/{action}.aspx"));

The static Routes property of the RouteTable class provides access to the global collection of Route objects. This code uses it to register a new DynamicDataRoute that defines a URL as a combination of a table name and a page action. The {table} marker defines the place where you expect to find a table name in the URL, and the {action} marks the place where you expect to find the page action. If an incoming request has a URL in this format, the default MetaModel of the web application contains a table with the matching name, and a page template exists matching the action, Dynamic Data uses this page template to handle the request. In the following URL, Products matches the table parameter, and List matches the action:

http://localhost/Products/List.aspx

Details Page Template

The Details page template generates web pages that display a single entity instance in a form view. Figure 6.2 shows an example of such page generated for the Product entity. Notice the URL in the address bar of the web browser; it includes the name of the table (Products) and a query string that specifies value of the entity’s primary key (ProductID). The Details page template uses this information to retrieve the appropriate entity instance from the database.

Image

Figure 6.2. Web page generated by the Details page template.

Listing 6.1 shows the source code of the Details page template, Details.aspx, located in the DynamicDataPageTemplates folder of the web application. Most of its contents should be already familiar to you from Chapter 2, “Entity Framework,” and Chapter 4, “Entity Templates.”

Listing 6.1. Details Page Template (Markup)


<%@ Page Language="C#"
  MasterPageFile="~/Site.master" CodeBehind="Details.aspx.cs"
  Inherits="WebApplication.DynamicData.PageTemplates.Details" %>

<asp:Content ContentPlaceHolderID="main" runat="Server">
  <asp:DynamicDataManager runat="server">
    <DataControls>
      <asp:DataControlReference ControlID="formView" />
    </DataControls>
  </asp:DynamicDataManager>

  <h2>Entry from table <%= table.DisplayName %></h2>

  <asp:UpdatePanel runat="server">
    <ContentTemplate>
      <asp:ValidationSummary runat="server" HeaderText="List of validation errors"/>
      <asp:DynamicValidator runat="server" ControlToValidate="formView"
        Display="None" />

      <asp:FormView runat="server" ID="formView" DataSourceID="dataSource"
        OnItemDeleted="FormView_ItemDeleted">
        <ItemTemplate>
          <asp:DynamicEntity runat="server" />
          <div class="DDFormActions">
            <asp:DynamicHyperLink runat="server" Action="Edit" Text="Edit" />
            <asp:LinkButton runat="server" CommandName="Delete" Text="Delete"
              OnClientClick='return
                confirm("Are you sure you want to delete this item?");'/>
            <asp:DynamicHyperLink runat="server" Action="List" Text="Back to List"/>
          </div>
        </ItemTemplate>
      </asp:FormView>

      <asp:EntityDataSource ID="dataSource" runat="server" EnableDelete="true" />
      <asp:QueryExtender TargetControlID="dataSource" runat="server">
        <asp:DynamicRouteExpression />
      </asp:QueryExtender>
    </ContentTemplate>
  </asp:UpdatePanel>
</asp:Content>


The EntityDataSource control is used to retrieve the required entity instance from the database. However, as you probably noticed, it does not specify the DefaultContainerName and the EntitySetName properties normally required for the control to know which ObjectContext to instantiate and which object set to query. This information is supplied to the EntityDataSource by the DynamicDataManager, one of the new controls provided by Dynamic Data.

The DynamicDataManager control has a DataControls collection that stores one or more DataControlReference objects. In the Details page template, it stores a single reference to the FormView control. During the initialization phase of the page lifecycle, the DynamicDataManager uses this reference to find the EntityDataSource control associated with the FormView and configures it. In particular, it extracts the table name from the current URL route and obtains the MetaTable object describing the table. The MetaTable object provides the information necessary to initialize the EntityDataSource, including the type of the ObjectContext as well as the name of the entity set to query.

Knowing the context type and the entity set is not enough to retrieve a specific entity from the database. The query sent to the database server must also include a WHERE clause that identifies the table row where it is stored. This task is performed by the QueryExtender control, which is associated with the EntityDataSource. In the Details page template, the QueryExtender uses a special type of data source expression, called DynamicRouteExpression. At runtime, this object extracts the values of the primary key columns from the URL and modifies the query generated by the EntityDataSource to include appropriate filter criteria.

The FormView control defines an ItemTemplate to display the entity with the help of the DynamicEntity control. As you remember, this control uses Dynamic Data entity templates to generate either a generic, single column view of the entity or a custom view if a custom template was defined for the entity. In the Details page template, the DynamicEntity control does not specify the Mode property, so a read-only entity template is used.

In addition to the DynamicEntity, the ItemTemplate of the FormView control also includes a set of links for users to edit or delete the entity or to go back to the List page. The Edit and List actions are implemented by the DynamicHyperLink controls. When databound to an entity instance, a DynamicHyperLink control generates a URL that points to another dynamic page based on the value of the control’s Action property. In this example, the URL generated by the first DynamicHyperLink control looks like this:

http://localhost:49212/Products/Edit.aspx?ProductID=1

In this URL, Products is the name of the MetaTable describing the current entity instance, Edit is the name of the action specified in the Action property of the DynamicHyperLink control, and ProductID=1 is the name and value of the primary key column of the current entity instance. Because the second DynamicHyperLink’s Action property set to List, the URL it generates looks like this instead:

http://localhost:49212/Products/List.aspx?ProductID=1

The Delete action, on the other hand, is implemented by a traditional LinkButton. This control specifies the CommandName property and takes advantage of the deletion support built into the FormView. When the user clicks the link generated by this control, a Delete command is submitted back to the FormView, which in turn passes the deletion request to the data source control associated with it. To allow deletion, the EntityDataSource control has its EnableDelete property set to true.

Not all of the functionality is implemented directly in the markup of the Details page template. You might have noticed that the caption line relies on the table field defined in the code-behind.

<h2>Entry from table <%= table.DisplayName %></h2>

Additional code is required to initialize controls and handle entity deletion. Listing 6.2 shows the code-behind of the Details page template.

Listing 6.2. Details Page Template (Code-Behind)


using System;
using System.Web.DynamicData;
using System.Web.UI.WebControls;

namespace WebApplication.DynamicData.PageTemplates
{
  public partial class Details : System.Web.UI.Page
  {
    protected MetaTable table;

    protected void Page_Init(object sender, EventArgs e)
    {
      this.table = DynamicDataRouteHandler.GetRequestMetaTable(this.Context);
      this.Title = this.table.DisplayName;
      this.dataSource.EntityTypeFilter = this.table.EntityType.Name;
      this.dataSource.Include = this.table.ForeignKeyColumnsNames;
    }

    protected void FormView_ItemDeleted(object sender, FormViewDeletedEventArgs e)
     {
      if (e.Exception == null ¦¦ e.ExceptionHandled)
        this.Response.Redirect(this.table.ListActionPath);
    }
  }
}


The Page_Init event handler initializes the table field by extracting the table name from the URL route and looking up the matching MetaTable object in the default MetaModel. DisplayName of the table is used to initialize the Title of the page, but more importantly, this code performs the missing initialization steps required for the EntityDataSource.

The EntityTypeFilter property of the EntityDataSource specifies the name of the entity type, which might be different from the entity set if your data model uses entity inheritance. The Northwind sample data model doesn’t take advantage of this feature, but imagine that a Contact table was used to store both Customer and Employee information with a ContactType column serving as the discriminator. The same entity set, Contacts, could be used to retrieve both Customer and Employee entity type, and you would need to specify the EntityTypeFilter to make sure a Customer entity is not displayed as if it were an Employee. The Page_Init handler sets the EntityTypeFilter property using the entity type name retrieved from the MetaTable object.

The Include property of the EntityDataSource determines whether related entities will be retrieved along with the main one. In the case of the Product entity, Category and Supplier entities also need to be retrieved from the database to display the category and supplier names in human-readable form, as opposed to the raw numeric values of the CategoryID and SupplierID columns. The Page_Init handler sets the Include property using the names of the foreign key navigation properties of the entity type, retrieved from the MetaTable object.

The FormView_ItemDeleted event handler is invoked when the user clicks the Delete LinkButton and the current entity instance has been successfully deleted. Instead of displaying an empty page, this method redirects the user back to the List page that displays all remaining entities of that type.

Edit Page Template

The Edit page template generates a web page for editing a single entity instance. Figure 6.3 shows such a page generated for a Product entity.

Image

Figure 6.3. Web page generated by the Edit page template.

This page is quite similar to the Details page discussed earlier. The URL also includes table and action names as well as the names and values of the primary key columns of the current entity instance.

http://localhost:49212/Products/Edit.aspx?ProductID=1

Users can navigate to this page by clicking the Edit link on the pages generated by the Details page template, as well as on the pages produced by the List page template, which is discussed shortly.

As you would expect, implementation of the Edit page template is similar to the Details page template. Listing 6.3 shows the markup file of this template, with significant differences between the templates indicated by bold font.

Listing 6.3. Edit Page Template (Markup)


<%@ Page Language="C#" MasterPageFile="~/Site.master" CodeBehind="Edit.aspx.cs"
  Inherits="WebApplication.DynamicData.PageTemplates.Edit" %>

<asp:Content ContentPlaceHolderID="main" runat="Server">
  <asp:DynamicDataManager runat="server">
    <DataControls>
      <asp:DataControlReference ControlID="formView" />
    </DataControls>
  </asp:DynamicDataManager>

  <h2>Edit entry from table <%= table.DisplayName %></h2>

  <asp:UpdatePanel runat="server">
    <ContentTemplate>
      <asp:ValidationSummary runat="server" HeaderText="List of validation errors"/>
      <asp:DynamicValidator runat="server" ControlToValidate="formView"
        Display="None"/>

      <asp:FormView runat="server" ID="formView" DataSourceID="dataSource"
        DefaultMode="Edit" OnItemCommand="FormView_ItemCommand"
        OnItemUpdated="FormView_ItemUpdated">
        <EditItemTemplate>
          <asp:DynamicEntity runat="server" Mode="Edit" />
          <div class="DDFormActions">
            <asp:LinkButton runat="server" CommandName="Update" Text="Update" />
            <asp:LinkButton runat="server" CommandName="Cancel" Text="Cancel"
                CausesValidation="false" />
          </div>
        </EditItemTemplate>
      </asp:FormView>

      <asp:EntityDataSource ID="dataSource" runat="server" EnableUpdate="true" />
      <asp:QueryExtender TargetControlID="dataSource" runat="server">
        <asp:DynamicRouteExpression />
      </asp:QueryExtender>
    </ContentTemplate>
  </asp:UpdatePanel>
</asp:Content>


The Edit page also includes an EntityDataSource and QueryExtender controls to implement data access logic as well as a FormView and DynamicEntity controls to render the user interface. However, unlike the read-only Details page template, which allows deleting the current entity, the Edit page template enables you to edit the current entity, but not delete it. Consequently, it sets the Mode property of the FormView and DynamicEntity controls to Edit and sets EnableUpdate property of the EntityDataSource control to true.

As you recall from the discussion of entity templates in Chapter 5, “Filter Templates,” setting the Mode of the DynamicEntity control to Edit instructs this control to choose an appropriate edit-mode entity template, which includes editable controls, like TextBox and DropDownList instead of the read-only Literal and DynamicHyperLink controls generated by the read-only entity templates.

Setting DefaultMode of the FormView control to Edit makes it instantiate the EditItemTemplate, which contains the DynamicEntity control along with two LinkButton controls. The FormView control has built-in support for the Update command, so when the user clicks the Update link, the current property values of the entity displayed by the FormView are automatically validated and submitted to the EntityDataSource control to be saved in the database. The Cancel link is used to abandon editing the current entity and go back to the List page.

The Edit page template includes the ValidationSummary and DynamicValidator controls. Although these controls are also included in the Details page template, they haven’t been discussed because validation plays a much more prominent role in the process of editing entities compared to deletion. As you recall from the discussion of field templates in Chapters 4, the individual field values are validated by the field templates with the help of the ASP.NET validation controls, including the new DynamicValidator control, which enforces validation rules based on the Data Annotation attributes. If any validation errors are encountered, the validators embedded in the field templates indicate invalid fields by displaying an asterisk next to the corresponding data entry controls. However, the complete error messages are displayed by the ValidationSummary control on the Edit page template.

In field templates, the DynamicValidator controls are associated with MetaColumn objects describing specific entity properties. However, the DynamicValidator control used by the Edit page template is not associated with any particular column and specifies the FormView as its ControlToValidate. When configured this way, the DynamicValidator control catches unhandled validation exceptions thrown by the EntityDataSource associated with the FormView. Chapter 8, “Implementing Entity Validation,” provides detailed discussion of this topic.

Listing 6.4 shows the code-behind file of the Edit page template.

Listing 6.4. Edit Page Template (Code-Behind)


using System;
using System.Web.DynamicData;
using System.Web.UI.WebControls;

namespace WebApplication.DynamicData.PageTemplates
{
  public partial class Edit : System.Web.UI.Page
  {
    protected MetaTable table;

    protected void Page_Init(object sender, EventArgs e)
    {
      this.table = DynamicDataRouteHandler.GetRequestMetaTable(Context);
      this.Title = table.DisplayName;
      this.dataSource.EntityTypeFilter = this.table.EntityType.Name;
      this.dataSource.Include = this.table.ForeignKeyColumnsNames;
    }

    protected void FormView_ItemCommand(object sender, FormViewCommandEventArgs e)
    {
      if (e.CommandName == DataControlCommands.CancelCommandName)
        this.Response.Redirect(this.table.ListActionPath);
    }

    protected void FormView_ItemUpdated(object sender, FormViewUpdatedEventArgs e)
    {
      if (e.Exception == null ¦¦ e.ExceptionHandled)
        this.Response.Redirect(this.table.ListActionPath);
    }
  }
}


Initialization code in the Page_Init event handler is identical to that in the Details page template. The FormView_ItemUpdated event handler redirects the user to the List page after he clicks the Update link on the Edit page and the modified entity has been successfully saved in the database. The FormView_ItemCommand event handler also redirects the user to the List page, but in response to him clicking the page’s Cancel link.

List Page Template

The List page template generates web pages that display a list of entities and allow searching and sorting the list by individual columns. Figure 6.4 shows an example of such a page generated for the Products table. At the top of the page, a dynamically generated set of filter controls is displayed for all properties of the entity type for which Dynamic Data has appropriate filter templates. The list of entities occupies the main part of the page. Changing any of the filter controls automatically refreshes the list of entries. The grid columns can be sorted in ascending or descending order by clicking their headers; a set of action links for editing, deleting, or displaying details of individual entity instances is displayed in the left-most column for each row. At the bottom of the grid, there are controls for navigating between pages and changing the number of rows displayed per page.

Image

Figure 6.4. Web page generated by the List page template.

The Edit and Details links point to the dynamic Edit and Details pages discussed earlier. Examples of these pages can be found in Figures 6.3 and 6.2, respectively. The foreign key navigation properties, such as the Category and Supplier, are also displayed in the form of hyperlinks that, when clicked, take users to the corresponding Details pages. One-to-many navigation properties, such as the Order Items, take users to the List page of the target entity where the search criteria has been prepopulated to display only the child entities of the parent entity clicked. For example, when a user clicks View Order Items of the product with the name Chai, an Order Items list page is displayed with the Product filter control preset to Chai, as shown in Figure 6.5.

Image

Figure 6.5. Prefiltered List page.

The List page template is implemented in List.aspx, located in the DynamicDataPageTemplates folder of the web application. Listing 6.5 shows the markup of the built-in template after some cleanup to improve readability and highlight important sections in bold.

The structure of the List page template is actually quite similar to the structure of the Details page template. It also includes DynamicDataManager, EntityDataSource, and QueryExtender controls. The only significant differences are the GridView control, which this template uses to display entities, and the QueryableFilterRepeater used to generate the filter controls.

Listing 6.5. List Page Template (Markup)


<%@ Page Language="C#" MasterPageFile="~/Site.master" CodeBehind="List.aspx.cs"
  Inherits="WebApplication.DynamicData.PageTemplates.List" %>
<%@ Register Src="~/DynamicData/Content/GridViewPager.ascx"
  TagName="GridViewPager" TagPrefix="dd" %>
<asp:Content ContentPlaceHolderID="main" runat="Server">
  <asp:DynamicDataManager runat="server">
    <DataControls>
      <asp:DataControlReference ControlID="gridView" />
    </DataControls>
  </asp:DynamicDataManager>
  <h2><%= table.DisplayName %></h2>

  <asp:UpdatePanel runat="server">
    <ContentTemplate>
      <div class="DD">
        <asp:ValidationSummary runat="server" HeaderText="Validation errors" />
        <asp:DynamicValidator runat="server" ControlToValidate="gridView"
          Display="None"/>

        <asp:QueryableFilterRepeater runat="server" ID="FilterRepeater">
          <ItemTemplate>
            <asp:Label runat="server" Text='<%# Eval("DisplayName") %>'
              OnPreRender="Label_PreRender" />
            <asp:DynamicFilter runat="server" ID="DynamicFilter"
              OnFilterChanged="DynamicFilter_FilterChanged" />
            <br />
          </ItemTemplate>
        </asp:QueryableFilterRepeater>
        <br />
      </div>

      <asp:GridView ID="gridView" runat="server" DataSourceID="dataSource"
        EnablePersistedSelection="true" AllowPaging="True" AllowSorting="True">
        <Columns>
          <asp:TemplateField>
            <ItemTemplate>
              <asp:DynamicHyperLink runat="server" Action="Edit" Text="Edit" />
              <asp:LinkButton runat="server" CommandName="Delete" Text="Delete"
                OnClientClick='return
                  confirm("Are you sure you want to delete this item?");'/>
              <asp:DynamicHyperLink runat="server" Text="Details" />
            </ItemTemplate>
          </asp:TemplateField>
        </Columns>
        <PagerTemplate>
          <dd:GridViewPager runat="server" />
        </PagerTemplate>
      </asp:GridView>

      <asp:EntityDataSource ID="dataSource" runat="server" EnableDelete="true" />
      <asp:QueryExtender TargetControlID="dataSource" runat="server">
        <asp:DynamicFilterExpression ControlID="FilterRepeater" />
      </asp:QueryExtender>

      <div class="DDBottomHyperLink">
        <asp:DynamicHyperLink ID="insertLink" runat="server" Action="Insert">
          <img runat="server" src="~/DynamicData/Content/Images/plus.gif"/>
          Insert new item
        </asp:DynamicHyperLink>
      </div>
    </ContentTemplate>
  </asp:UpdatePanel>
</asp:Content>


The GridView control is configured to allow paging and sorting. It includes a single TemplateField to define a column that displays action links—Edit, Delete, and Details. The actual data columns are generated dynamically by a DefaultAutoFieldGenerator. During page initialization, the DynamicDataManager control sets the ColumnsGenerator property of the GridView with a new instance of the DefaultAutoFieldGenerator class. So, instead of being specified explicitly in page markup, as shown in the example below, the DynamicField instances are created dynamically, based on the metadata information describing properties of the current entity type.

<Columns>
  <asp:DynamicField DataField="ProductName"/>
  <asp:DynamicField DataField="UnitsInStock"/>
</Columns>

Paging functionality is implemented by the GridViewPager control. This is not a built-in ASP.NET control; it is a user control included in the Dynamic Data project templates of Visual Studio and created in the DynamicDataContent folder of your web application when you first create the project.

The QueryableFilterRepeater control, which was discussed in Chapter 5, is used to generate a set of DynamicFilter controls, one for each property that can be filtered using filter templates available in the DynamicDataFilters folder of the web application. Notice that this markup does not specify the DataField property of the DynamicFilter control. This is done automatically when the QueryableFilterRepeater instantiates its ItemTemplate for each filterable property in the entity type. The ID property of the DynamicFilter control has to be "DynamicFilter," which is what the QueryableFilterRepeater looks for by default.

The QueryableFilterRepeater control is associated with the QueryExtender by the DynamicFilterExpression in the page markup. In its turn, the QueryExtender control is associated with the EntityDataSource, which allows the individual filter templates to modify the LINQ query it generates at run time based on the values selected by the user.

Listing 6.6 shows the code-behind file of the List page template, again, cleaned-up to improve readability and highlight important points.

Listing 6.6. List Page Template (Code-Behind)


using System;
using System.Collections.Generic;
using System.Web.DynamicData;
using System.Web.Routing;
using System.Web.UI.WebControls;

namespace WebApplication.DynamicData.PageTemplates
{
  public partial class List : System.Web.UI.Page
  {
    protected MetaTable table;

    protected void Page_Init(object sender, EventArgs e)
    {
      this.table = DynamicDataRouteHandler.GetRequestMetaTable(this.Context);
      this.Title = this.table.DisplayName;
      this.dataSource.EntityTypeFilter = this.table.EntityType.Name;
      this.dataSource.Include = table.ForeignKeyColumnsNames;

      IDictionary<string, object> columnValues =
        this.table.GetColumnValuesFromRoute(this.Context);
      this.gridView.SetMetaTable(this.table, columnValues);

      this.insertLink.NavigateUrl = this.table.GetActionPath(
        PageAction.Insert, new RouteValueDictionary(columnValues));
    }

    protected void Label_PreRender(object sender, EventArgs e)
    {
      var label = (Label)sender;
      var dynamicFilter = (DynamicFilter)label.FindControl("DynamicFilter");
      var template = dynamicFilter.FilterTemplate as QueryableFilterUserControl;
      if (template != null && template.FilterControl != null)
      {
        label.AssociatedControlID =
          template.FilterControl.GetUniqueIDRelativeTo(label);
      }
    }

    protected void DynamicFilter_FilterChanged(object sender, EventArgs e)
    {
      this.gridView.PageIndex = 0;
    }
  }
}


The Page_Init event handler performs the familiar tasks of finding the MetaTable describing the current entity type using information in the URL route, updating the page title with the display name of the entity, and configuring the EntityDataSource. However, this code also performs an important additional step, which was not necessary in the Details and Edit page templates discussed so far.

The code in the Page_Init event handler calls the GetColumnValuesFromRoute method of the MetaTable class to obtain a dictionary of property names and values from the request URL. This is important when the user navigates to a list page by clicking a “View Children” link on any of the other dynamically generated pages, such as the View Order Items link on the Product list page in Figure 6.4. In this scenario, the primary key of the parent entity is passed to the page template in a request URL similar to the one shown here:

http://localhost:49212/Order_Details/List.aspx?ProductID=1

When the dictionary of property names and values has been obtained, the Page_Init code calls the SetMetaTable extension method defined in the DynamicDataExtensions class to associate both the MetaTable object and the dictionary of what it calls default values with the GridView control. The filter templates rely on this information to provide initial values in filter controls (see the DefaultValue property of the QueryableFilterUserControl class).


Note

Normally it is not necessary to call the SetMetaTable extension method if you have a DynamicDataManager control associated with a databound control, such as the GridView in this example. The DynamicDataManager calls this method automatically, but in this case, the template calls it explicitly to pass the default values to filter templates.


The rest of the code in the Page_Init event handler deals with initializing the Insert new item hyperlink at the bottom of the page. Using the same dictionary of property names and values, this code generates a link to the dynamic Insert page, with the default values in the URL. As you see shortly, the Insert page template also supports column values passed through the request URL and relies on them to prepopulate some of the field values.

The Label_PreRender method handles the PreRender event of the Label controls generated by the QueryableFilterRepeater. It associates each label with the filter control generated by its matching DynamicFilter. This makes the generated label behave like a true control label as opposed to regular selectable text content on the page. Clicking a label in the web browser automatically selects the corresponding control.

The DynamicFilter_FilterChanged method handles the FilterChanged event of the DynamicFilter controls, which fires any time the user changes the filter criteria. Although the QueryableFilterRepeater automatically notifies the EntityDataSource control and causes the query to be reloaded, this method flips the GridView to the first page to avoid confusion and errors when the new filter criteria makes the number of pages in the result set less than the current page number.

Insert Page Template

The Insert page template is the last of the built-in page templates Dynamic Data projects provide out of the box. This page template generates web pages that allow users to create new entity instances, such as the Add Product page shown in Figure 6.6. From a user interface and behavior standpoint, the Insert pages are similar to the Edit pages discussed earlier in this chapter. However, they are integrated with the List pages, and it will be easier to discuss the Insert page template now that you have reviewed implementation of its List cousin.

Image

Figure 6.6. Web page generated by the Insert page template.

One subtle but important difference between Insert and Edit pages is the meaning of the query parameters in their URLs. For the Edit pages, the URL contains the names and values of the primary key properties of an existing entity. For the Insert pages, however, either there are no parameters, or the URL contains the default values for the foreign key properties of the new entity to be created. Notice how in Figure 6.6, the URL for the Add Product page contains a SupplierID value of 1. This is because the page in this screenshot was displayed by clicking the Insert new item link on the Products List page that displayed all products offered by a specific supplier, Exotic Liquids. The List page passed the SupplierID to the Insert page via the generated Insert new item link. This requires the Insert page template to have logic similar to that of the List page template for extracting parameter values from the request URL and sharing it with the field templates.

The Insert page template is implemented in the Insert.aspx located in the DynamicDataPageTemplates folder of the web application. Listing 6.7 shows the markup of this template. Most of it is very similar to the markup of the Edit.aspx shown in Listing 6.3, except that the FormView, DynamicEntity, and EntityDataSource are used in Insert mode. Because the Insert page does not need to retrieve an existing entity instance from the database, it does not need a QueryExtender, only the data source control.

Listing 6.7. Insert Page Template (Markup)


<%@ Page Language="C#" MasterPageFile="~/Site.master" CodeBehind="Insert.aspx.cs"
  Inherits="WebApplication.DynamicData.PageTemplates.Insert" %>

<asp:Content ContentPlaceHolderID="main" runat="Server">
  <asp:DynamicDataManager runat="server">
    <DataControls>
      <asp:DataControlReference ControlID="formView" />
    </DataControls>
  </asp:DynamicDataManager>
  <h2>Add new entry to table <%= table.DisplayName %></h2>

  <asp:UpdatePanel runat="server">
    <ContentTemplate>
      <asp:ValidationSummary runat="server" HeaderText="List of validation errors"/>
      <asp:DynamicValidator runat="server" ControlToValidate="formView"
        Display="None" />

      <asp:FormView runat="server" ID="formView" DataSourceID="dataSource"
        DefaultMode="Insert" OnItemCommand="FormView_ItemCommand"
        OnItemInserted="FormView_ItemInserted">
        <InsertItemTemplate>
          <asp:DynamicEntity runat="server" Mode="Insert" />
          <div class="DDFormActions">
            <asp:LinkButton runat="server" CommandName="Insert" Text="Insert" />
            <asp:LinkButton runat="server" CommandName="Cancel" Text="Cancel"
              CausesValidation="false" />
          </div>
        </InsertItemTemplate>
      </asp:FormView>

      <asp:EntityDataSource ID="dataSource" runat="server" EnableInsert="true" />
    </ContentTemplate>
  </asp:UpdatePanel>
</asp:Content>


The most important difference between the code-behinds of the Insert (see Listing 6.8) and Edit (see Listing 6.4) page templates is the additional initialization step the Insert page templates performs for the FormView control. Just like the List page template, it extracts the names and values of the entity properties from the request URL and associates them with the FormView control by calling the SetMetaTable extension method. The field templates generated by the DynamicEntity control use this information to provide default field values in their data entry controls. The FieldValue and FieldValueEditString properties of the FieldTemplateUserControl class respect the default values associated with their parent databound control, such as the FormView in this example. This is why the Supplier drop-down list generated by the ForeignKey_Edit field template in Figure 6.6 has been prepopulated with Exotic Liquids.

Listing 6.8. Insert Page Template (Code-Behind)


using System;
using System.Web.DynamicData;
using System.Web.UI.WebControls;

namespace WebApplication.DynamicData.PageTemplates
{
  public partial class Insert : System.Web.UI.Page
  {
    protected MetaTable table;

    protected void Page_Init(object sender, EventArgs e)
    {
      this.table = DynamicDataRouteHandler.GetRequestMetaTable(Context);
      this.Title = this.table.DisplayName;
      this.dataSource.EntityTypeFilter = table.EntityType.Name;
      this.formView.SetMetaTable(this.table,
        this.table.GetColumnValuesFromRoute(Context));
    }

    protected void FormView_ItemCommand(object sender, FormViewCommandEventArgs e)
    {
      if (e.CommandName == DataControlCommands.CancelCommandName)
        Response.Redirect(this.table.ListActionPath);
    }

    protected void FormView_ItemInserted(object sender, FormViewInsertedEventArgs e)
    {
      if (e.Exception == null ¦¦ e.ExceptionHandled)
        Response.Redirect(this.table.ListActionPath);
    }
  }
}


The FormView_ItemCommand event handler in the Insert page template serves the same function the method with this name performs in the Edit page template—redirecting the user back to the List page when she clicks Cancel. The FormView_ItemInserted event handler also does that when a new entity has been successfully saved in the database.

Creating a New Page Template

Dynamic Data does not limit the set of page actions and page templates. You can easily define a new action and create a new page template to handle it. As an example, consider the action of deleting an entity instance. The default page templates support deletion directly in the List and Details pages. When you click the Delete link on one of these pages, a JavaScript prompt window is displayed, asking you to confirm the deletion. Although this approach works, it definitely has a Windows-like feel to it. A more traditional way to do this in a web application is to take the user to a separate page where she can review the entity she is about to delete and confirm the action.

Listing 6.9 shows the page template that implements the Delete page action. It is almost identical to the built-in Details page template. Just like the other page templates, this ASPX file needs to be placed in the DynamicDataPageTemplates folder of the web application project, in a file called Delete.aspx.

Listing 6.9. Delete Page Template (Markup)


<%@ Page Language="C#" MasterPageFile="~/Site.master" CodeBehind="Delete.aspx.cs"
  Inherits="WebApplication.DynamicData.PageTemplates.Delete" %>

<asp:Content ContentPlaceHolderID="main" runat="server">
  <asp:DynamicDataManager runat="server">
    <DataControls>
      <asp:DataControlReference ControlID="formView" />
    </DataControls>
  </asp:DynamicDataManager>

  <h2>Are you sure you want to delete this from table <%= table.DisplayName %>?</h2>

  <asp:UpdatePanel runat="server">
    <ContentTemplate>
      <asp:ValidationSummary runat="server" HeaderText="List of validation errors"/>
      <asp:DynamicValidator runat="server" ControlToValidate="formView"
        Display="None"/>

      <asp:FormView ID="formView" runat="server" DataSourceID="dataSource"
        OnItemDeleted="FormView_ItemDeleted">
        <ItemTemplate>
          <asp:DynamicEntity runat="server" />
          <div class="DDFormActions">
            <asp:Button runat="server" CommandName="Delete" Text="Delete"/>
            <asp:DynamicHyperLink runat="server" Action="List" Text="Back to list"/>
          </div>
        </ItemTemplate>
      </asp:FormView>
      <asp:EntityDataSource ID="dataSource" runat="server" EnableDelete="true" />
      <asp:QueryExtender TargetControlID="dataSource" runat="server">
        <asp:DynamicRouteExpression />
      </asp:QueryExtender>
    </ContentTemplate>
  </asp:UpdatePanel>
</asp:Content>


The only substantial difference between this new Delete template and the built-in Details template is the set of button and link controls. The new Delete template uses a Button control instead of a LinkButton to make confirmation of deletion visually different from merely requesting it. It also does not have an Edit link the Details page template uses to take users to the Edit page.

Although they might seem unnecessary, the ValidationSummary and DynamicValidator controls still play an important role in the Delete page template. If the user tries to delete a Customer who placed one or more orders, a database error will occur due to a foreign key constraint violation. As discussed in Chapter 8, database errors can be presented to the Dynamic Data as instances of the ValidationException class. The DynamicValidator control at the top of the page automatically displays unhandled ValidationException errors in the ValidationSummary instead of generating a yellow screen of death.

After implementing the new Delete page template, you also need to modify the existing templates to redirect the user to a new dynamic page generated by the new template instead of displaying the JavaScript prompt. In both List and Details page templates, replace the following LinkButton:

<asp:LinkButton runat="server" CommandName="Delete" Text="Delete"
OnClientClick='return confirm("Are you sure you want to delete this
item?");'/>

with the following DynamicHyperLink control:

<asp:DynamicHyperLink runat="server" Action="Delete" Text="Delete" />

And because the Details page will no longer delete the records, you can remove the EnableDelete property from its EntityDataSource control in page markup and remove the ItemDeleted event handler from the FormView control in the code-behind.

Figure 6.7 shows an example of the dynamically generated Delete page for the Product entity type. The entity itself (labels and field values) looks similar to the dynamically generated Details page, which is a great example of how page templates allow you to implement different actions and data access logic while reusing the common presentation logic encapsulated by the entity templates.

Image

Figure 6.7. Dynamic Delete page.

Custom Page Templates

The page templates discussed so far are dynamic, meaning they can generate web pages for any entity type in your data model. Thanks to the rich metadata information provided by Dynamic Data in the form of a MetaTable object, a single generic page template can handle a particular type of action, such as List or Details, for any entity type. The generated web pages vary depending on the properties of the entity type, but the variations are limited to the order of fields on the form, their labels, and data entry controls. When the user interface cannot be implemented by creating a custom entity template, developers can create custom page templates to implement unique functionality required for a particular entity type.

Consider the Orders table in the Northwind sample database. It stores order header information, such as Order Date, Customer, Shipper, and so on. However, the order items are stored in a separate table called Order Details. In the sample data model, these tables are represented by the Order and Order_Detail classes, respectively. Intuitively, an Order_Detail instance is a part of the parent Order instance and cannot be entered independently. This is different from how the Order and Customer instances relate to each other: An Order is associated with a Customer and can be entered independently. Unfortunately, Dynamic Data cannot distinguish between these types of relationships automatically and has to assume that the Order_Detail is an independent entity type. As a result, the default experience for viewing orders is rather cumbersome because it requires users to switch between the Order details page and the Order_Detail List page shown in Figure 6.8.

Image

Figure 6.8. Default Order Details List page.

Ideally, users should be able to see an entire order on a single page, similar to that shown in Figure 6.9. In a Dynamic Data web application, this can be accomplished by creating a custom Details page template for the Order entity type.

Image

Figure 6.9. Custom Order Details page.

Dynamic Data looks for custom page templates under DynamicDataCustomPages, in a subfolder with the name that matches the name of the MetaTable describing its entity type. Here is how the folder structure looks:

DynamicData
    CustomPages
      {Table1}
          {Template1}.aspx
          {Template2}.aspx
          ...
      {Table2}
          {Template1}.aspx
          {Template2}.aspx
          ...
      ...
    PageTemplates
      {Template1}.aspx
      {Template2}.aspx
      ...

To create a custom Details page template for the Order entity type, you need to create a subfolder called Orders (note the plural form, as in the name of the table). Begin implementing the custom page by copying the default Details.aspx page template from the PageTemplates folder to the newly created CustomPagesOrders folder. This gives you the FormView control that will display the Order information along with the EntityDataSource and QueryExtender controls it needs to be able to get the data from the database. The second part of the page needs a GridView control to display Order Details plus its own EntityDataSource and QueryExtender controls. Finally, a Literal control to display the order total calculated in the code-behind. Listing 6.10 shows the complete markup of the new page.

Listing 6.10. Custom Order Details Page Template (Markup)


<%@ Page Language="C#"
  MasterPageFile="~/Site.master" CodeBehind="Details.aspx.cs"
  Inherits="WebApplication.DynamicData.CustomPages.Orders.Details" %>

<asp:Content ContentPlaceHolderID="main" runat="Server">
  <asp:DynamicDataManager runat="server">
    <DataControls>
      <asp:DataControlReference ControlID="formView" />
    </DataControls>
  </asp:DynamicDataManager>

  <table>
  <tr valign="top">
    <td>
      <h2>Entry from table <%= ordersTable.DisplayName %></h2>
      <asp:FormView runat="server" ID="formView" DataSourceID="formDataSource">
        <ItemTemplate>
          <asp:DynamicEntity runat="server" />
          <div class="DDFormActions">
            <asp:DynamicHyperLink runat="server" Action="Edit" Text="Edit" />
            <asp:DynamicHyperLink runat="server" Action="Delete" Text="Delete" />
            <asp:DynamicHyperLink runat="server" Action="List" Text="List"/>
          </div>
        </ItemTemplate>
      </asp:FormView>

      <asp:EntityDataSource ID="formDataSource" runat="server"/>
      <asp:QueryExtender TargetControlID="formDataSource" runat="server">
        <asp:DynamicRouteExpression />
      </asp:QueryExtender>
    </td>

    <td>
      <h2><%= itemsTable.DisplayName %></h2>
      <asp:GridView ID="gridView" runat="server" DataSourceID="gridDataSource"
        AutogenerateColumns="false" AllowSorting="True">
        <Columns>
          <asp:DynamicField DataField="Product" />
          <asp:DynamicField DataField="UnitPrice"/>
          <asp:DynamicField DataField="Quantity"/>
          <asp:TemplateField>
            <ItemTemplate>
              <asp:DynamicHyperLink runat="server" Action="Edit" Text="Edit" />
              <asp:DynamicHyperLink runat="server" Action="Delete" Text="Delete" />
              <asp:DynamicHyperLink runat="server" Action="Details" Text="Details"/>
            </ItemTemplate>
          </asp:TemplateField>
        </Columns>
      </asp:GridView>

      <asp:EntityDataSource ID="gridDataSource" runat="server"
        ConnectionString="name=NorthwindEntities"
        DefaultContainerName="NorthwindEntities"
        EntitySetName="Order_Details" Include="Product"
        OnSelected="GridDataSource_Selected" />
      <asp:QueryExtender TargetControlID="gridDataSource" runat="server">
        <asp:DynamicRouteExpression />
      </asp:QueryExtender>

      Total: <asp:Literal ID="totalLiteral" runat="server"/>
    </td>
  </tr>
  </table>
</asp:Content>


Note that unlike in the generic List page template, the GridView in this custom page does not rely on the DynamicDataManager control for initialization. The main reason is that the DynamicDataManager determines which MetaTable to associate with its target controls based on the information in the URL route. In this case, the URL route contains the table name Orders, but you need to associate the GridView control with the Order Details table and need to do this in the code-behind, as shown in Listing 6.11.

You also do not want to rely on the DynamicDataManager to initialize the GridView control because we need a specific set of columns to display the order details. You could display them all by assigning the DefaultAutoFieldGenerator to the ColumnsGenerator property of the GridView control; however, this would also include the Order column, which you can see on the default List page of the Order Details table back in Figure 6.8. Because the entire Order will be already displayed on the new page, including this column would be redundant. Although you could hide it by manipulating the entity metadata, it is important to remember that this custom page template does not have to be dynamic. This task is much easier to accomplish by specifying the required Columns explicitly in the page markup, as the DynamicField instances for the Product, UnitPrice, and Quantity properties we want to display.

Another important difference here is that initialization of the gridDataSource instance of the EntityDataSource control is performed directly in markup, with ConnectionString, DefaultContainerName, EntitySetName, and Include properties being “hard-coded” for the purposes of this custom page, where you know exactly which entity it needs to query and don’t need to make the code dynamic. However, this code continues to rely on the DynamicRouteExpression in the second QueryExtender control, which conveniently limits the set of Order Details instances retrieved from the database based on the OrderID column value extracted from the page request URL.

Listing 6.11. Custom Order Details Page Template (Code-Behind)


using System;
using System.Collections.Generic;
using System.Data.Objects;
using System.Linq;
using System.Web.DynamicData;
using System.Web.UI.WebControls;
using DataModel;
namespace WebApplication.DynamicData.CustomPages.Orders
{
  public partial class Details : System.Web.UI.Page
  {
    protected MetaTable ordersTable;
    protected MetaTable itemsTable;

    protected void Page_Init(object sender, EventArgs e)
    {
      this.ordersTable = DynamicDataRouteHandler.GetRequestMetaTable(this.Context);
      this.Title = this.ordersTable.DisplayName;
      this.formDataSource.Include = this.ordersTable.ForeignKeyColumnsNames;

      this.itemsTable = Global.DefaultModel.GetTable(typeof(Order_Detail));
      this.gridView.SetMetaTable(this.itemsTable);
    }

    protected void GridDataSource_Selected(object sender,
      EntityDataSourceSelectedEventArgs e)
    {
      IEnumerable<Order_Detail> orderItems = e.Results.Cast<Order_Detail>();
      decimal totalAmount = orderItems.Sum(item => item.UnitPrice * item.Quantity);
      this.totalLiteral.Text = totalAmount.ToString();
    }
  }
}


The Page_Init event handler performs the remaining initialization steps, similar to how this is done in the dynamic page templates. The first part of this method still relies on the GetRequestMetaTable method of the DynamicDataRouteHandler class to retrieve the Orders MetaTable using information in the URL route. This was simply carried over from the dynamic Details page template. On the other hand, the Order Details MetaTable has to be obtained directly from the default MetaModel object registered in the Global.asax. This MetaTable object (itemsTable) is then associated with the GridView control to provide the DynamicField instances access to the metadata information they need to instantiate appropriate field templates.

The GridDataSource_Selected method handles the Selected event of the EntityDataSource control that retrieves instances from the Order Details table. It calculates the total order amount as a sum of UnitPrice values multiplied by the Quantity of the individual order items. The result is then displayed in the Literal control on the page.

URL Routing

Dynamic Data web applications rely on the general-purpose URL routing capabilities built into ASP.NET. Routes are typically configured at the application start time, in the Application_Start event handler located in the Global.asax file.

RouteTable.Routes.Add(new DynamicDataRoute("{table}/{action}.aspx"));

As discussed in the beginning of this chapter, {table} and {action} are placeholders for the two route parameters that have special meaning in ASP.NET Dynamic Data. The first parameter supplies the name of the table whose data needs to be displayed by a dynamic page, and the second determines what kind of page needs to be displayed, or perhaps more precisely, how the table data needs to be presented to the user. Dynamic Data expects the table parameter to match the name of one of the MetaTable objects that describe entity types registered in the global MetaModel, whereas the action parameter provides the information necessary to find the appropriate page template to handle the request. The route definition just shown allows Dynamic Data to determine that the following page request URL applies to the table called Products and the page template called List:

http://localhost/Products/List.aspx

Action Route Parameter

Routes do not have to include the action parameter as a part of the URL itself. Instead, it can be specified by explicitly setting the Action property of the DynamicDataRoute class as shown here:

RouteTable.Routes.Add(new DynamicDataRoute("{table}")
{
  Action = PageAction.List
});

Given this route definition, the URL for displaying a list of products will not include the name of the page template at all and look like this instead:

http://localhost/Products

Table Route Parameter

Similarly, the table parameter does not have to come from the URL either; it can be specified using the Table property of the DynamicDataRoute class. Notice how in the following example, the route definition does not include any parameters and simply hard-codes the URL to "Product" (singular), which does not match the plural name of the table.

RouteTable.Routes.Add(new DynamicDataRoute("Product")
{
  Action = PageAction.Details,
  Table = "Products"
});

Based on this route definition, the Dynamic Data web application can use the following URL:

http://localhost/Product?ProductID=1

instead of the more verbose and less user-friendly:

http://localhost/Products/Details.aspx?ProductID=1

URL Parameters Versus Query Parameters

URL parameters are an alternative to the Query parameters used by traditional ASP.NET WebForms applications. Whereas the traditional query parameters require both names and values to be present in the query string, the URL parameters allow web applications to determine parameter names automatically, based on the position of their values in the URL. Consider the following route, which replaces the ProductID query parameter with a positional URL parameter:

RouteTable.Routes.Add(new DynamicDataRoute("Product/{ProductID}")
{
  Action = PageAction.Details,
  Table = "Products"
});

This route definition allows the web application to implement the product catalog without query parameters and support URLs similar to that shown below. This not only makes the URLs shorter and easier for users to read, but also makes it easier for the search engines to index dynamic pages, which is very important for public-facing web sites:

http://localhost:49212/Product/3

Dynamic Data still supports traditional Query parameters, which you can activate by registering a route that does not include any URL parameters and does not specify parameter values explicitly:

RouteTable.Routes.Add(new DynamicDataRoute("Dynamic"));

The route registered just shown makes Dynamic Data use URLs that look like this instead:

http://localhost/Dynamic?Table=Products&Action=List

Dynamic Hyperlinks

Routes have a dual purpose. In addition to helping the ASP.NET runtime determine which ASPX page will be used to handle a particular incoming request, they also determine how hyperlinks pointing to the dynamic pages will be generated. There are several different methods for generating hyperlinks in ASP.NET web applications. One of them involves the DynamicHyperLink control supplied by Dynamic Data. This control inherits from the ASP.NET HyperLink control and allows you to generate hyperlinks to dynamic pages. In the following example, the name of the table and the required action are explicitly specified.

<asp:DynamicHyperLink runat="server"
  ContextTypeName="DataModel.NorthwindEntities"
  TableName="Products" Action="List" Text="Show all" />

The TableName property specifies the name of the MetaTable describing the target entity, and the ContextTypeName specifies the name of the context class where the target entity is defined. Depending on how the routing is configured, this control generates different URLs without you having to modify any of its markup. Table 6.1 shows examples of the different route definitions and URLs this control would generate based on them.

Table 6.1. Examples of URLs Generated by DynamicHyperLink

Image

The DynamicHyperLink control can also be databound to an entity instance, in which case it can automatically determine the ContextTypeName and the TableName and will generate URLs that include names and values of its primary key columns in the query portion of the address. The Details page template discussed earlier takes advantage of this functionality by including the DynamicHyperLink controls as part of the ItemTemplate in the FormView control as shown in the following extract:

<asp:FormView runat="server" ID="formView" DataSourceID="dataSource">
  <ItemTemplate>
    <asp:DynamicEntity runat="server" />
    <div class="DDFormActions">
      <asp:DynamicHyperLink runat="server" Action="Edit" Text="Edit" />
      <asp:DynamicHyperLink runat="server" Action="Delete" Text="Delete" />
      <asp:DynamicHyperLink runat="server" Action="List" Text="Show all"/>
    </div>
  </ItemTemplate>
</asp:FormView>

Notice how the DynamicHyperLink controls in this example do not have the ContextTypeName and TableName properties specified. The FormView control databinds its ItemTemplate to the entity instance provided by the associated EntityDataSource control. The controls locate the MetaTable object based on the entity type and generate URLs that look like this:

http://localhost/Products/Edit.aspx?ProductID=1
http://localhost/Products/Delete.aspx?ProductID=1
http://localhost/Products/List.aspx?ProductID=1

Dynamic hyperlinks can be also generated with the help of the RouteUrl markup expression. This is a special syntax that ASP.NET parser recognizes and uses to generate a dynamic URL based on parameter values you specify:

<asp:HyperLink runat="server" Text="Show all"
  NavigateUrl="<%$RouteUrl:table=Products, action=List%>" />

Under the hood, both the RouteUrl markup expression and the DynamicHyperLink control rely on the low-level API the ASP.NET provides for routing. The next code sample illustrates how they work by creating a dynamic hyperlink programmatically. It creates a RouteValueDictionary to hold the Table and Action parameter values and then calls the GetVirtualPath method of the global Routes collection to generate the URL in a form of a virtual path:

HyperLink hyperLink = // ...

RequestContext context =
  DynamicDataRouteHandler.GetRequestContext(HttpContext.Current);

RouteValueDictionary routeValues = new RouteValueDictionary();
routeValues["table"] = "Products";
routeValues["action"] = "List";

VirtualPathData pathData = RouteTable.Routes.GetVirtualPath(
  context, routeValues);

hyperLink.NavigateUrl = pathData.VirtualPath;

Route Evaluation and Constraints

Although you will probably never need to write code that creates dynamic hyperlinks programmatically, it is important to understand how they are generated by the ASP.NET runtime. In particular, notice the Routes collection is used to generate a dynamic URL and not a single particular route object. This is significant because the Routes collection evaluates routes one by one, in the order they were registered, looking for the route matching the supplied set of parameter values. As soon as the match is found, the further evaluation is stopped, and the matching route is used to generate the URL.

A similar evaluation process occurs when the ASP.NET runtime needs to find a page template to handle an incoming web request. It starts with the route that was registered first and goes down the list until it finds the first match. All other routes that were registered after the first match are ignored.

Keeping in mind how the route evaluation process works, register the most specific routes first, leaving the more generic routes until the end. For example, if you had registered the following two routes in this order, your web application would always ignore the second route when generating dynamic hyperlinks:

RouteTable.Routes.Add(new DynamicDataRoute("{table}/{action}.aspx"));
RouteTable.Routes.Add(new DynamicDataRoute("{table}")
{
  Action = PageAction.List
});

In other words, a Product list hyperlink would always be presented to users as http:// localhost/Products/List.aspx and never as http://localhost/Products. However, when handling incoming requests, the second URL would not match the first route, “fall through,” and still get picked up by the second route.

Changing the order of route registrations gives you a fair amount of control over the URL resolution process. However, when it is insufficient, you can use route constraints to make it more specific. Route constraints are defined in the Constraints dictionary of a route object as pairs of two string values, where first is a parameter name, and second is a regular expression pattern that the parameter value must match. The following example defines a route where the table parameter value can be either Products or Orders:

RouteTable.Routes.Add(new DynamicDataRoute("{table}")
{
  Action = PageAction.List,
  Constraints = new RouteValueDictionary(){{"table","Products¦Orders"}}
});
RouteTable.Routes.Add(new DynamicDataRoute("{table}/{action}.aspx"));

Based on the route definitions in this last example, only the Products and Orders tables will have the List URLs generated as http://localhost/Products or http://localhost/Orders. All other tables will use the second route and have URLs similar to http://localhost/Suppliers/List.aspx.

ViewName Route Parameter

In the examples of URL routing discussed so far, the action parameter has always matched the name of the page template configured to handle it, such as the Details.aspx page template, which handled the Details page action, and the List.aspx page template, which handled the List action. Having a page template implement a single action makes it focused and easy to maintain. Compared to a traditional ASP.NET WebForms approach (where a single page often tries to support all three item-level actions—Insert, Edit, and Details), single-action page templates are usually simpler. However, you are not limited to having one-to-one relationships between actions and page templates.

If this makes sense in your application, you can have a single page template handle multiple actions. Visual Studio project templates for Dynamic Data web applications include an example of such a page template called ListDetails.aspx. This template combines a GridView control similar to the one in the List page template and a FormView control that displays details of the row currently selected in the grid. This page template can be registered to handle both List and Details actions as shown in this example:

RouteTable.Routes.Add(new DynamicDataRoute("{table}")
{
  Action = PageAction.List,
  ViewName = "ListDetails"
});

RouteTable.Routes.Add(new DynamicDataRoute("{table}")
{
  Action = PageAction.Details,
  ViewName = "ListDetails"
});

It is actually the ViewName property of the DynamicDataRoute class that determines the name of the page template that will be used to handle the request. If the ViewName was not provided by the route definition, Dynamic Data will assume it to be the same as the Action parameter. Separation of Action from ViewName allows Dynamic Data web applications to change implementation of page templates without breaking the navigation in existing ASPX pages.

Page Template Lookup Rules

Figure 6.10 summarizes the algorithm Dynamic Data uses to find an appropriate page template and handle an incoming web request. Most of the power and flexibility in this process comes from the ASP.NET routing.

Image

Figure 6.10. Page template lookup algorithm.

A Dynamic Data web application first uses the registered URL routes to determine the Table and the Action names. When the matching route is found, unless it provides an explicit ViewName, the Action name is used instead to identify the name of the page template.

Having determined both Table and ViewName from the matching route, Dynamic Data first tries to find a matching custom page template in a subfolder with the name of the table inside of the DynamicDataCustomPages. If a custom template exists, it is immediately used to handle the incoming web request.

If a matching custom page template does not exist, Dynamic Data then tries to find a dynamic page template in the DynamicDataPageTemplates folder of the web application. If neither custom nor dynamic page template with the matching ViewName can be found, an HTTP error 404 (Resource cannot be found) is returned back to the user.

Summary

Page templates are responsible for generating web pages that perform CRUD and other generic actions. A single set of page templates can be reused to implement operations for multiple entity types. Out of the box, Dynamic Data web application projects come with page templates for inserting, editing, displaying, and searching for entities of any type. New dynamic page templates can be created to support additional action types, such as deleting an entity.

A page template implements most of the data access and some of the presentation logic required by a dynamic page. Dynamic Data relies on the information provided by ASP.NET routing to determine which page template to use and for which entity type it needs to generate a web page. Dynamic page templates typically rely on special controls, such as DynamicEntity and DynamicFilter to generate user interface based on metadata information describing the entity type. When dynamically generated user interface is not sufficient, custom page templates can be used to create a unique user interface required for a particular entity type. Page templates do not replace the regular, custom ASP.NET web pages that still need to be used for screens such as home page, login, dashboards, and so on.

Compared to the traditional multi-purpose WebForms pages, which tend to be verbose and have a large number of simple controls, Dynamic Data page templates are typically much smaller and rely on a number of advanced controls, such as DynamicEntity, DynamicFilter, and QueryExtender. By their nature, dynamic controls are much more abstract than the traditional TextBox and DropDownList and therefore can be harder to understand for developers who are new to Dynamic Data.

Dynamic page templates work well for applications that need to display a large number of similar-looking pages, which tends to be the case for internal enterprise applications. Custom page templates, on the other hand, are better suited when unique functionality is required for a particular entity type, such as when implementing a public-facing web site.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.109.234