Peter Bucher - Mein Experiment, meine Spielereien, meine Welt...   ·   Stefan Falz   ·   Jürgen Gutsch   ·   Golo Roden   ·   ASP.NET Zone   ·   Microsoft ASP.NET
Willkommen bei ASP.NET Zone. Anmelden | Registrieren | Hilfe
WerbeanzeigeApose

Zwei Ansätze wie mit den ClientIDs von ASP.NET umgegangen werden kann

Wie den meisten schon bekannt sein wird, erzeugt ASP.NET ClientIDs die auf dem Client genutzt werden können um Namenskollisionen aus dem Weg zu gehen.
Jedoch ist dieses Verhalten in manchen (Bei mir in den meisten Fällen) fehl am Platz und es ist mühselig die ClientIDs clientseitig zur Verfügung zu stellen.

Es gibt zwar, diese oder jene Lösung - jedoch war für mich keine wirklich zufriedenstellend.
Ich möchte euch Heute zwei Ansätze mit auf den Weg geben, damit die Arbeit mit Javascript und ASP.NET für euch nicht immer zur ID-Jagt wird.

Der erste Ansatz basiert unter anderem auf einer Idee von Renè Drescher-Hackel, auch ein ASP.NET Zone Genosse :)
Renè erstellt mithilfe von JSON ein Javascript Objekt, mit dem einfach auf die ClientIDs und ggf. noch weitere Eigenschaften zugegriffen werden kann.

ClientIdProvider mit einem HttpModule und statischen Hilfsmethoden

Ich wollte eine Lösung bei der ich möglichst wenig schreiben muss, nur das wichtigste bekomme und auch die Performance stimmt.
Die Lösung ist relativ einfach gehalten. Es braucht ein Methodenaufruf um ein Control für den Provider zu registrieren, danach steht die ClientID in Javascript automatisch über ClientId.<ControlName> bereit.

Dabei muss natürlich beachtet werden, dass nicht zwei gleichlautende Controls registriert werden.

Die Anwendung sieht dann so aus:

ASPX:



<ul>
    <a href="#" onclick="addBorder('test');">Test1 (Div)</li>
    <a href="#" onclick="addBorder(ClientId.pnlTest);">Test2 (Panel)</li>
    <a href="#" onclick="addBorder(ClientId.pnlTest2);">Test3 (Panel)</li>
</ul>
<div id="test">
    test
</div>
<asp:Panel ID="pnlTest" runat="server">
    test2
    <asp:Panel ID="pnlTest2" runat="server">
        test3
    </asp:Panel>
</asp:Panel>

Codebehind:



protected void Page_Load(object sender, EventArgs e) {
    Tools.ClientIdProvider.RegisterControlForClientId(pnlTest);
    Tools.ClientIdProvider.RegisterControlForClientId(pnlTest2);
}

Das wars auch schon für den Anwender... nur was läuft dahinter ab?

Die Methode "RegisterControlForClientId(Control c)" speichert jeweils eine Referenz auf das Control
in einer Liste, die in der aktuellen HttpContext.Items Collection gespeichert ist.

Diese Liste ist vom Begin bis zum Ende des Requests unter dem Key (clientIdCollKey) verfügbar.
Um das JSON auf die Seite zu rendern, muss nur noch die Methode "WriteClientIdCollection(Page page)" zum richtigen Zeitpunkt aufgerufen werden.

Der richtige Zeitpunkt ist hier wichtig, genau ab Page_PreRender sind alle ClientIDs bestimmt vorhanden.
Also könnte diese Methode einfach in der überschrieben OnPreRender-Methode einer Seite aufgerufen werden.

Jedoch gibt das wieder Schreibarbeit und ist fehleranfällig.
Um dies zu automatisieren, kann ein HttpModule als Hilfe benutzt werden.

Die statischen Hilfsmethoden:



namespace pb.Web
{
    public static partial class Tools
    {
        public static class ClientIdProvider
        {
            private const string clientIdCollKey = "pbclientIdColl";

            /// <summary>
            /// Registriert ein Control für die ClientIdCollection,
            /// sodass diese auf der Seite verfügbar ist.
            /// </summary>
            /// <param name="c">Das zu registrierende Control</param>
            public static void RegisterControlForClientId(Control c) {
                HttpContext context = HttpContext.Current;
                object item = context.Items[clientIdCollKey];

                if (item == null || !(item is IList))
                    item = new List<Control>();

                (item as IList<Control>).Add(c);

                context.Items[clientIdCollKey] = item;
            }

            /// <summary>
            /// Schreibt die aktuelle ClientIdCollection in die
            /// derzeitige Page Instanz
            /// </summary>
            public static void WriteClientIdCollection() {
                WriteClientIdCollection(Tools.CurrentHandler<Page>());
            }

            /// <summary>
            /// Schreibt die aktuelle ClientIdCollection in die
            /// derzeitige Page Instanz
            /// </summary>
            public static void WriteClientIdCollection(Page page) {
                HttpContext context = HttpContext.Current;
                object item = context.Items[clientIdCollKey];

                if (item == null)
                    return;

                IList<Control> list = (item as List<Control>);
                StringBuilder sb = new StringBuilder();

                foreach (Control c in list) {
                    sb.Append("");
                    sb.AppendFormat("\"{0}\" : \"{1}\"", c.ID, c.ClientID);
                    sb.Append(",");
                }

                // TODO: Ab ASP.NET 3.5 kann hier stattdessen der
                //TODO: System.Web.Script.Serialization.JavaScriptSerializer benutzt werden
                page.ClientScript.RegisterStartupScript(page.GetType(),
                                                                    "ClientId",
                                                                    string.Format(
                                                                    "\r\n var ClientId = new Object(); \r\n eval('ClientId = {{{0}}}');\r\n",
                                                                    sb.ToString().Substring(0, sb.ToString().Length - 1)),
                                                                    true);
            }
        }
    }
}

 

Das HttpModule ist ziemlich einfach aufgebaut, es hängt sich in den PreRequestHandlerExecute-Event,
ruft den aktuellen Handler ab und führt in dessen PreRender-Event die Methode der Tools-Klasse aus, um
das JSON zu rendern.

Somit geschieht alles vollautomatisch, es muss nur das HttpModule in der web.config registriert sein.

 

Das ClientIdProviderModule:



namespace pb.Web.HttpModules
{
    /// <summary>
    /// Registriert alle ClientIds als Startupscript in der aktuellen Page,
    /// falls Controls registriert wurden.
    /// </summary>
    public class ClientIdProviderModule : XHttpModuleBase
    {
        protected override void _application_PreRequestHandlerExecute(object sender, EventArgs e) {
            base._application_PreRequestHandlerExecute(sender, e);

            IHttpHandler handler = Tools.CurrentHandler<IHttpHandler>();
            if (handler != null && handler is Page) {
                Page page = handler as Page;
                page.PreRender += delegate {
                                                   Tools.ClientIdProvider.WriteClientIdCollection(page);
                                               };
            }
        }
    }
}

 

Beispiel web.config-Eintrag:



<?xml version="1.0"?>
<configuration>
    <system.web>
        <httpModules>
            <add name="ClientIdProviderModule" type="pb.Web.HttpModules.ClientIdProviderModule, ClientIdProviderTest"/>
        </httpModules>
    </system.web>
</configuration>

Wichtig ist hier aber die Einschränkung auf Controls die alleinestehen, oder aber in einem TemplateInstance.SingleInstance - Template stehen.
Mit MultiInstance - Templates funktioniert diese Lösung nicht.

Zu Single- vs MultiInstance, siehe hier:

Überschreiben von IDs, damit diese gleich lauten wie die serverseitigen IDs

Wilco Bauwer, der unter anderem auch noch viele andere asp.nette Dinge auf seiner Seite anbietet,
hat ein Control entwickelt, dass die ClientIDs so überschreibt, dass sie gleich lauen wie die serverseitigen IDs.

Für manch ein Anwendungsfall ist das ein Muss und für die anderen vielfach eine gute Lösung mit wenig Aufwand.
Allerdings gilt auch hier, dass nur einzigartige IDs benutzt werden können, ansonsten kommt es zu einer Kollision.

Was ist jetzt besser?

Keine der beiden Lösungen ist besser.
Bei dem ClientIdProvider bestehen die ClientIDs immer noch genau gleich, jedoch gibt es clientseitig einen einfachen Zugriff darauf.
Beim ID-Overriding gibt es keine ClientIDs mehr bzw. die ClientID lautet gleich wie die ID auf dem Server. Das kann in vielen Szenarien Probleme verursachen (Bspw. Controls die IPostBackEventHandler implementieren funktionieren nicht mehr, etc...).

Daher gibt es keine Patentlösung, sondern jeweils für die Anforderung eine Lösung die passt.

Download des Beispielprojekts:

Benutzte Links:

Veröffentlicht Dienstag, 2. September 2008 00:57 von Peter Bucher

Kommentare

# re: Zwei Ansätze wie mit den ClientIDs von ASP.NET umgegangen werden kann

Hey Peter, schöner Artikel. Ich hab schon drauf gewartet *g*

Dienstag, 2. September 2008 08:29 by Jürgen Gutsch
Anonyme Kommentare sind nicht zugelassen