Resumé

AutoCompleteExtender and ASP.NET Dynamic Data

Cristian Merighi () 5.00

Extend the base FieldTemplateUserControl that handles string properties by adding suggest functionality. Provided example targets an Entity Framework-based website.
This article is obsolete. Some functionalities might not work anymore. Comments are disabled.

In this article I'm going to show how to enrich an ASP.NET Dynamic Data FieldTemplate in order to offer new centralized functionalities to an input field.

This particular example works with an Entity Framework based ASP.NET Dynamic Data WebSite and describes how to add the Auto Suggest (read AjaxControlToolkit AutoCompleteExtender) feature to a DataField that targets the Text_Edit template.

Most of the architecture of the Dynamic Data technology rests on Metadata in general and Attributes in particular.
Let's begin this tutorial by writing a new one (C# code):

namespace Pacem.DynamicData

{

    [AttributeUsage(AttributeTargets.Property)]

    public class SuggestAttribute : Attribute

    {

        public SuggestAttribute(bool suggest)

        {

            this.Suggest = suggest;

        }

 

        public bool Suggest

        {

            get;

            private set;

        }

    }

}

We can now create new instances of this class and add 'em to the Metadata descriptors of our entities' properties
(If you're new to Metadata concept in ASP.NET Dynamic Data, please take a look to the instructive and interesting video tutorials available on the official ASP.NET Dynamic Data site).

Let us suppose to have a property of an entity whose textual value could be common for different instances (a purchaser for an order, just for giving a dummy example...)

    [MetadataType(typeof(OrderMetadata))]

    [ScaffoldTable(true)]

    public partial class Order

    {

    }

 

    class OrderMetadata

    {

        [Pacem.DynamicData.Suggest(true)]

        public string Purchaser { get; set; }

    }

Now our model domain is ready. We need to modify our presentation layer to make it responsive to the newly added features.
Let's pick and edit the \DynamicData\FieldTemplates\Text_Edit.ascx control...

Markup:

<%@ Control Language="C#" CodeFile="Text_Edit.ascx.cs" Inherits="Text_EditField" %>

 

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

 

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

<asp:RegularExpressionValidator runat="server" ID="RegularExpressionValidator1" CssClass="droplist" ControlToValidate="TextBox1" Display="Dynamic" Enabled="false" />

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

 

<ajax:AutoCompleteExtender runat="server" ID="ace" ServiceMethod="GetHints" ServicePath="~/services/AjaxService.asmx"

TargetControlID="TextBox1" MinimumPrefixLength="2" UseContextKey="true" CompletionSetCount="5"

CompletionListItemCssClass="CompletionListItem" CompletionListHighlightedItemCssClass="CompletionListItemHighlighted" />

As you can see, an AutoCompleteExtender from the AjaxControlToolkit now targets the TextBox1 DataControl. It is meant to use ContextKey (see further) when calling GetHints method of WebService ~/services/AjaxService.asmx.

Codebehind:

public partial class Text_EditField : System.Web.DynamicData.FieldTemplateUserControl {

 

    protected void Page_Load(object sender, EventArgs e) {

        TextBox1.MaxLength = Column.MaxLength;

        if (Column.MaxLength < 20)

            TextBox1.Columns = Column.MaxLength;

        TextBox1.ToolTip = Column.Description;

 

        SetUpValidator(RequiredFieldValidator1);

        SetUpValidator(RegularExpressionValidator1);

        SetUpValidator(DynamicValidator1);

        // enable or not the extender?

        var suggest = Column.Attributes.OfType<Pacem.DynamicData.SuggestAttribute>().FirstOrDefault();

        ace.Enabled = suggest != null && suggest.Suggest;

        // set useful contextkey to use on webservice side...

        ace.ContextKey = string.Format("{0},{1}", Table.Name, Column.Name);

    }

 

    protected override void ExtractValues(IOrderedDictionary dictionary) {

        dictionary[Column.Name] = ConvertEditedValue(TextBox1.Text);

    }

 

    public override Control DataControl {

        get {

            return TextBox1;

        }

    }

}

Highlighted and commented code should be clear enough, we can skip verbose explanations and step into the webservice code...

[WebService(Namespace = "http://pacem.it/")]

[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]

[System.Web.Script.Services.ScriptService]

public class AjaxService : System.Web.Services.WebService {

 

    [WebMethod, ScriptMethod]

    public string[] GetHints(string prefixText, int count, string contextKey)

    {

        string[] splitted = contextKey.Split(',');

        if (splitted.Length != 2) return null;

        string tableName = splitted[0];

        string columnName = splitted[1];

        string[] retval = null;

        using (Pacem.DbContext db = new Pacem.DbContext())

        {

            db.Connection.Open();

            // here is given priority to matches that start with prefixText on those who simply contain prefixText

            string sql = string.Format(@"

SELECT v.ColumnText, MAX(v.Priority) AS Priority FROM (

(SELECT v1.{1} AS ColumnText, 1 AS Priority FROM {0} AS v1 WHERE v1.{1} LIKE @pText+'%') UNION

(SELECT v1.{1}, 0 FROM {0} AS v1 WHERE v1.{1} LIKE '%'+@pText+'%')

) AS v

GROUP BY v.ColumnText

ORDER BY Priority DESC

LIMIT(@maxCount)", tableName, columnName);

            var ctx = new ObjectQuery<System.Data.IExtendedDataRecord>(sql, db);

            ctx.Parameters.Add(new ObjectParameter("pText", prefixText));

            ctx.Parameters.Add(new ObjectParameter("maxCount", count));

            var list = ctx.Execute(MergeOption.NoTracking).ToList();

            retval = new string[list.Count];

            for (int i = 0; i < list.Count; i++)

            {

                retval[i] = list[i]["ColumnText"].ToString();

            }

        }

        return retval;

    }

}

The core of the method is obviously the E-SQL instruction that results by properly formatting the command text with the provided MetaTable and MetaColumn (see ContextKey).
Parameters prefixText and count are respectively used as query key and returned records limiter.

screenshot

Useful? (Hope so)...

Take care. Bye.

Feedbacks

  • awesome work man

    ola lawal Thursday, September 16, 2010 5.00

    Thanks for this cool story , you are a genius

feedback
 

Syndicate

Author

Cristian Merighi facebook twitter google+ youtube

Latest articles

Top rated

Archive

Where am I?

Author

Cristian Merighi facebook twitter google+ youtube

I'm now reading

Feeds