Resumé

AutoCompleteExtender e ASP.NET Dynamic Data

Cristian Merighi () 5,00

Aggiungi funzionalità di "suggerimento" al FieldTemplateUserControl che gestisce i campi di testo in un'applicazione ASP.NET Dynamic Data. Il tutorial prevede l'utilizzo di Entity Framework.
Questo articolo è da considerarsi obsoleto. Alcune funzionalità potrebbero non essere più disponibili e non è possibile aggiungere commenti.

In questo articolo vorrei mostrare come arricchire un FieldTemplate di un sito web ASP.NET Dynamic Data.
Tendo ad essere invogliato ad apportare migliorìe di tal fatta: ottengo gratificazione nel vedere come una singola implementazione si "sparga" su tutto il contesto applicativo facilitandone la manutenzione ed il riutilizzo, aiuta a curare i particolari!

Nel caso specifico che vado a descrivere, lo scenario è quello di un sito ASP.NET Dynamic Data che poggia su Entity Framework; l'obiettivo che mi pongo è quello di aggiungere la funzionalità di Auto Suggest (leggi AjaxControlToolkit AutoCompleteExtender) per i DataFields che sfruttano il template Text_Edit.

Gran parte dell'architettura Dynamic Data prevede l'utilizzo esteso di Metadati in generale di di Attributes in particolare. Cominciamo quindi il tutorial con la stesura di un nuovo attributo (codice C#):

namespace Pacem.DynamicData

{

    [AttributeUsage(AttributeTargets.Property)]

    public class SuggestAttribute : Attribute

    {

        public SuggestAttribute(bool suggest)

        {

            this.Suggest = suggest;

        }

 

        public bool Suggest

        {

            get;

            private set;

        }

    }

}

Ora possiamo istanziare questa classe per aggiungere informazioni ai metadati delle nostre entities
(se non conosci l'utilizzo di classi Metadata puoi dare un'occhiata agli interessanti ed istruttivi video tutorials disponibili al sito ufficiale ASP.NET Dynamic Data).

Supponiamo di avere una entity con una proprietà che si presti particolarmente all'introduzione dell'auto-suggest (che so, un campo "committente" testuale di una tabella "ordini"...)

    [MetadataType(typeof(OrderMetadata))]

    [ScaffoldTable(true)]

    public partial class Order

    {

    }

 

    class OrderMetadata

    {

        [Pacem.DynamicData.Suggest(true)]

        public string Purchaser { get; set; }

    }

Ora che il nostro model domain è pronto possiamo passare a modificare il presentation layer, nel nostro caso il controllo \DynamicData\FieldTemplates\Text_Edit.ascx...

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" />

È stato in sostanza aggiunto un AutoCompleteExtender dall'AjaxControlToolkit cha punta al DataControl TextBox1. NB: è previsto che esso utilizzi una ContextKey (vedi seguito) nella chiamata al metodo GetHints del 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;

        }

    }

}

Il codice evidenziato e commentato dovrebbe essere sufficientemente chiaro, passiamo pure all'implementazione del webservice...

[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;

    }

}

Il cuore del codice è ovviamente l'istruzione E-SQL che risulta dall'accorpamento, alla stringa generica, dei nomi di MetaTable e MetaColumn passati tramite la ContextKey.
I parametri prefixText e count sono utilizzati rispettivamente come chiave della query e come limitatore del numero di record ritornati.

screenshot

Utile? (Spero di sì)...

Take care. Bye.

Feedbacks

  • awesome work man

    ola lawal giovedì 16 settembre 2010 5,00

    Thanks for this cool story , you are a genius

feedback
 

Syndicate

Autore

Cristian Merighi facebook twitter google+ youtube

Ultimi articoli

Top rated

Archivio

Dove sono?

Autore

Cristian Merighi facebook twitter google+ youtube

Le mie letture

Feeds