1.3. Building Custom Field Templates

The field templates included in the default Dynamic Data template attempt to match UI functionality by data type. Custom field templates take the idea of representing data one step further and give you an opportunity to provide domain-specific behavior to each field.

Centralizing the UI elements for a single data member can yield many benefits. Instead of having to add validation controls on each and every page where user input is required, field templates can house the validators and use some simple logic to decide whether to enable validation constraints. Encapsulating the UI into field templates begins to give your ASP.NET application reusable building blocks to make creating and maintaining data-driven web sites easier than in the past.

The following exercise will take you through the steps of building a custom field template for URL fields. Instead of simply displaying a URL on the screen to a user, this template will render an anchor tag that will allow people to click on the link when the data is being rendered as Read Only. The editing experience provides a textbox for data entry, but also features a link to test the URL so those doing data entry are confident that the URL is correct.

While building this simple example you will learn how to create custom field templates and how to extend them to read arguments from the metadata as well as from the DynamicControl.

1.3.1. Build the URL Field Templates

Begin by adding a new user control under the solution folder DynamicData FieldTemplates and name it URL.ascx.

Open the Source view and enter the following markup:

<a href="<%# FieldValueString %>"><asp:Literal ID="litURL" Text="<%#
FieldValueString %>" runat="server" /></a>

This code simply renders a clickable URL onto the screen. The base class for this field template exposes properties named FieldValue and FieldValueString for your use. The value the template is manipulating lives in the strongly typed FieldValue property, but the class also exposes FieldValueString, which is the same value already converted to a string. Therefore, when rendering the value to a user in markup, you use FieldValueString.

Next switch to the code-behind view and update your class to look like

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

public partial class DynamicData_FieldTemplates_Url : FieldTemplateUserControl
{
    public override Control DataControl
    {
        get { return this.litURL; }
    }
}

The FieldTemplateUserControl base class exposes an overrideable property named DataControl. When Dynamic Data needs to access the underlying control in any field template, it will access it via the DataControl property.

Testing this new field template is easy because the mapping work is already done. If you created the DBMetaData.cs file as described in the previous section, all you have to do is launch the site and click on the "Contacts" link.

You will now see a clickable URL rendered where there was simply text before (see Figure 7).

Figure 7. Figure 7

The reason this works is because of the metadata information found in App_Code DBMetaData.cs.

[UIHint("Url", null, "EnableTest", "true")]
    public object Url { get; set; }

The benefit of this approach is that in all places where the URL field is displayed, the URL field template is used.

Each field template may have two modes: Read Only and Edit. By convention, when you create the "Edit version" of a field template, you must name the user control the same name as what you chose for the Read Only version appended with _Edit to the end of the filename.

The edit field template for the URL will allow users to test the URL before deciding to persist the data to the database (see Figure 8).

Figure 8. Figure 8

Create a new user control under the solution folder DynamicData FieldTemplates, name it Url_Edit.ascx, and enter the following markup:

<asp:TextBox
    ID="txtUrl"
    Text='<%# FieldValueEditString %>'
    CssClass="droplist"
    runat="server" />

<a href="javascript:void(0);"
    id="ancTest"
    onclick="window.open($get('<%= txtUrl.ClientID %>').value)"
    runat="server">Test</a>

<asp:RequiredFieldValidator
    ID="rfvUrl"
    ControlToValidate="txtUrl"
    Enabled="false"
    CssClass="droplist"
    Display="Dynamic"
    runat="server" />

<asp:RegularExpressionValidator
    ID="regUrl"
    ControlToValidate="txtUrl"
    Enabled="false"
    CssClass="droplist"

Display="Dynamic"
    runat="server" />

<asp:DynamicValidator
    ID="dyvUrl"
    ControlToValidate="txtUrl"
    CssClass="droplist"
    Display="Dynamic"
    runat="server" />

The markup you just entered has several controls that will help Dynamic Data interface with the metadata model for any field marked to use this field template. First, a standard ASP.NET TextBox control is used to present the editable value to the user.

The CssClass of droplist may seem a little un-intuitive for a TextBox control, but this is the class name that the default Dynamic Data site template uses to style input controls.

The next element is an anchor tag that will reach into the TextBox and pull the value out to open a JavaScript pop-up window to test the URL. A RequiredFieldValidator is added and will conditionally be enabled by the base class if the field is marked not to allow nulls in the database.

The RegularExpressionValidator is added to the page ready to fire if the RegularExpression attribute is defined in the metadata information. Finally, the DynamicValidator exists to catch any exceptions thrown by the underlying data access operations.

Next, switch to the code view and update the controls' code-behind to match the following:

using System;
using System.Collections.Specialized;
using System.Web.DynamicData;
using System.Web.UI;

public partial class DynamicData_FieldTemplates_Url_Edit : FieldTemplateUserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        txtUrl.MaxLength = Column.MaxLength;
        txtUrl.ToolTip = Column.Description;

        SetUpValidator(rfvUrl);
        SetUpValidator(regUrl);
        SetUpValidator(dyvUrl);
    }

    protected override void ExtractValues(IOrderedDictionary dictionary)
    {
        dictionary[Column.Name] = ConvertEditedValue(txtUrl.Text);
    }

    public override Control DataControl
    {
        get {return txtUrl;}
    }
}

The main points-of-interest here are that the Column property encapsulates the column you are working with and has several properties that help you configure the field templates. The ExtractValues method is the standard way the field template interrogates data collected from the user control.

Run the site and use the new user control to add and review new records in the Contact table.

1.3.1.1. Passing Arguments to Field Templates

One of the best ways to make a field template flexible is to allow the control to respond to arguments passed into the class. There are two methods available to pass arguments into a field template. The first option is to add an argument in the metadata where you have centralized control over arguments provided to all instances of a field template. The second option allows you the option to provide different argument values to each instance of the same field template.

The decision to choose the right approach, as always, depends on what you are trying to do. Consider a field template that is responsible for entering information about when the record was last modified. Usually, audit fields like these include not only the date when the record was last modified, but also the username of the editor. You could create a single field template to handle both the text-based username as well as the date information, by defining in the metadata whether the property being customized was a username or date field.

Alternatively, you may want to turn an option on or off in your field template. You may choose to pass an argument into the instance of your field template rather than setting up something available to each instance of the template.

The following exercise shows you how to set up both methods, and as you begin working with field template arguments, you will know which option is best for your situation.

1.3.1.1.1. Metadata Arguments

The metadata information you entered at the beginning of this Wrox Blox already made an argument available to the Url_Edit.ascx field template. To review, the entry looks like this:

[UIHint("Url", null, "EnableTest", "true")]
public object Url { get; set; }

Dissecting the UIHint attribute a bit you'll notice a number of arguments being passed to the attribute. The first value is the name of the field attribute, in this case, the URL. The second argument's name as exposed by IntelliSense is presentationLayer, and as of this writing must always be a null value, as use of this argument is an un-implemented feature. The final set of arguments represents a key/value pair of custom arguments you can pass to the field template. These arguments work just like the params keyword in C#, where you can pass in as many items as you like. The only requirement is that you must pass in a pair of values, the first being the name of the argument and the second being the value.

In order to use this information, you must update the code-behind for Url_Edit.ascx. The updates are highlighted:

using System;
using System.Collections.Specialized;
using System.Web.DynamicData;
using System.Web.UI;

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

public partial class DynamicData_FieldTemplates_Url_Edit : FieldTemplateUserControl
{
    protected void Page_Load(object sender, EventArgs e)
    {
        txtUrl.MaxLength = Column.MaxLength;
        txtUrl.ToolTip = Column.Description;

        ancTest.Visible = this.GetUIHintArg<bool>("EnableTest");

        SetUpValidator(rfvUrl);
        SetUpValidator(regUrl);
        SetUpValidator(dyvUrl);
    }

    protected override void ExtractValues(IOrderedDictionary dictionary)
    {
        dictionary[Column.Name] = ConvertEditedValue(txtUrl.Text);
    }

    public override Control DataControl
    {
        get {return txtUrl;}
    }

    private T GetUIHintArg<T>(string key)
    {
        UIHintAttribute hint = null;
        T returnValue = default(T);
        string value = string.Empty;

        hint = (UIHintAttribute)this.Column.Attributes[typeof(UIHintAttribute)];

        if (hint != null)
        {
            if (hint.ControlParameters.ContainsKey(key))
            {
                value = hint.ControlParameters[key].ToString();
                TypeConverter convert = new TypeConverter();

                var converter = TypeDescriptor.GetConverter(typeof(T));
                returnValue = (T)(converter.ConvertFromInvariantString(value));
            }
        }

        return returnValue;
    }
}

Working from the bottom up, the largest addition to the code is the GetUIHintArg method. This method uses generics to allow you to cast any argument to its appropriate data type. In the example for EnableTest, the result is a Boolean value. In this instance, you must format the method call as GetUIHintArg<bool>("EnableTest"). The use of generics enables this method's support for enumerations or any other serializable type to a string in the metadata.

The body of the method simply looks at the current column's attribute collections and searches for a UIHint attribute. If a UIHint is found, then it looks for the specified key. If these conditions are met, then it attempts to extract the value from the metadata as a string and return the value cast as the provided type. The result is that the test anchor tag's visibility is set in Page_Load to whatever is defined in the metadata.

Finally, the file is updated with a few using statements that expose the TypeConverter and UIHintAttribute classes.

Run the page and verify that the test link is visible on the page. Then update the metadata information to turn off the URL test:

[UIHint("Url", null, "EnableTest", "false")]
public object Url { get; set; }

Recompile the application and run the page again. The test link is now gone.

1.3.1.1.2. Instance Arguments

Control for a feature like whether to test a URL upon data entry is probably better managed on a case-by-case basis. The following section updates support for the argument and allows the field template instance to override what is set in the metadata.

Begin by creating a new Web Form named ContactEditor.aspx and enter the following markup:

<form id="form1" runat="server">
<asp:FormView ID="frmContacts" runat="server" DataSourceID="lds">
    <ItemTemplate>
        <asp:DynamicControl
            EnableTest="true"
            Mode="Edit"
            DataField="Url"
            runat="server" />
    </ItemTemplate>
</asp:FormView>
<asp:LinqDataSource
    ID="lds"
    TableName="Contacts"
    ContextTypeName="DBDataContext"
    Select="new (Url)"
    runat="server" />
</form>

The FormView natively supports the new DynamicControl that is responsible for displaying the results of a Dynamic Data field template.

Normally when using the DynamicControl, you only need to define the DataField property. The Mode property defaults to Read-Only, so here the code forces the field template to render in Edit mode. The EnableTest property is a custom property added to the field template in the next section.

Open the Url_Edit.ascx.cs file and update the code-behind to match the following:

using System;
using System.Collections.Specialized;
using System.Web.DynamicData;
using System.Web.UI;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

public partial class DynamicData_FieldTemplates_Url_Edit : FieldTemplateUserControl
{
    public bool? EnableTest { get; set; }

    protected void Page_Load(object sender, EventArgs e)
    {
        txtUrl.MaxLength = Column.MaxLength;
        txtUrl.ToolTip = Column.Description;

        ancTest.Visible = this.GetUIHintArg<bool>("EnableTest");

        if (EnableTest.HasValue)
        {
            ancTest.Visible = EnableTest.Value;
        }

        SetUpValidator(rfvUrl);
        SetUpValidator(regUrl);
        SetUpValidator(dyvUrl);
    }

    protected override void ExtractValues(IOrderedDictionary dictionary)
    {
        dictionary[Column.Name] = ConvertEditedValue(txtUrl.Text);
    }

    public override Control DataControl
    {
        get {return txtUrl;}
    }

    protected T GetUIHintArg<T>(string key)
    {
        UIHintAttribute hint = null;
        T returnValue = default(T);
        string value = string.Empty;

        hint = (UIHintAttribute)this.Column.Attributes[typeof(UIHintAttribute)];

        if (hint != null)
        {
            if (hint.ControlParameters.ContainsKey(key))
            {
                value = hint.ControlParameters[key].ToString();

TypeConverter convert = new TypeConverter();

                var converter = TypeDescriptor.GetConverter(typeof(T));
                returnValue = (T)(converter.ConvertFromInvariantString(value));
            }
        }
        return returnValue;
    }
}

The URL field template now includes a public nullable Boolean property that encapsulates the field template's instance setting of whether to enable the test. Page_Load overrides what is set in the metadata if the setting is overridden for this instance.

Run ContactEditor.aspx alternately with setting EnableTest in the markup to true or false or simply removing it to view how the UI responds according to your settings.

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

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