ASP.NET Tutorial – Eigene Server Controls bauen – Teil 1

Wie ich in Teil 1& Teil 2der ASP.NET Webcastserie bereits ausführlich erklärt habe, verwendet ASP.NET das Konzept von Server Controls als eine Abstraktion über konkrete HTML Tags.

In diesem Posting zeige ich, wie Sie eigene Server Controls bauen können, was nicht nur für Control-Developer interessant ist, sondern auch einen Einblick in die Funktionsweise von ASP.NET gibt.

Als Beispiel bauen wir zwei ASP.NET Controls – Textbox und Button – in einer vereinfachten Version komplett neu.

Beginnen wir mit dem Button. Alle ASP.NET WebControls leiten sich von System.Web.UI.WebControls.WebControlab. Nochmals zur Erinnerung: WebControls haben einheitliche Eigenschaftsnamen, die aber nicht 1:1 den Original-HTML Tags entsprechen (im Gegensatz zu den HtmlControls, die das jeweilige HTML Tag genau wrappen).

Für den Button wird eine neue Klasse benötigt, die von WebControl ableitet und eine Texteigenschaft, sowie ein Event besitzt:

    1:  public class CustomButton : WebControl {
    2:     public string Text
    3:     {
    4:         get { return (string)this.ViewState["Text"];  }
    5:         set { this.ViewState["Text"] = value; }
    6:     }
    7:  
    8:     static readonly object EventClick = new object();
    9:     public event EventHandler Click
   10:     {
   11:         add { base.Events.AddHandler(EventClick, value); }
   12:         remove { base.Events.RemoveHandler(EventClick, value); }
   13:     }
   14:  }

Damit der Button sich die Texteigenschaft auch über HTML-Form-Posts hinweg merken kann, wird der Wert im ViewState gespeichert. Der ViewState wird in der HTML Seite als Hidden-Field gerendert, das bei einem Postback als State der Seite wieder zum Server mitgeschickt wird:

 <input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" 
            value="/wEPDwUJMjg1MTUxMzYwD2QWAgozjUghfwDH9ZQHmo=" />

Die Events werden in einer zentralen Events-Liste gespeichert.

Rendern der HTML Tags Um den Button zu rendern wird die Render Methode innerhalb des Button-WebControls überschrieben. Diese Methode bietet ein HtmlTextWriter Objekt an, mit dessen Hilfe in den Ausgabestream geschrieben werden kann.

     protected override void Render(HtmlTextWriter writer)
    {
        // Something is missing here -- Point A
        writer.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
        writer.AddAttribute(HtmlTextWriterAttribute.Id, this.ClientID);
        writer.AddAttribute(HtmlTextWriterAttribute.Type, "button"); ;
        writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Text);
        // Something is missing here -- Point B
        writer.RenderBeginTag(HtmlTextWriterTag.Input);
        writer.RenderEndTag();
    }

Durch die Methode AddAttribute werden Attribute für das HTML Tag festgelegt. RenderBeginTag / RenderEndTag rendert dann letztlich das Input Tag mit den Attributen.

Den Button ausprobieren
Um das Control im selben Projekt auszuprobieren, muss in der Web.config eine Referenz auf den Namespace hinzugefügt werden:

 <pages>
  <controls>
    <add tagPrefix="cc" namespace="Knom.Web.UI.CustomControls"
                assembly="Knom.Web.UI.CustomControls"/>
    // ...
  </controls>
</pages>

Nun kann der Button in die ASP.NET Seite eingebunden werden:

 <cc:CustomButton ID="Button1" runat="server" Text="Save" />

Alternativ kann der Button (meist erst nach einem Rebuild) auch direkt aus der Toolbox in die Seite gezogen werden:

image

Wenn Sie die Seite nun starten, sehen Sie, wie der Button in HTML gerendert wird:

 <input type="button" value="Mein Text" Name="Button1" ID="Button1" />

Der Button sieht bereits richtig aus, allerdings funktioniert er noch nicht…

Eventverarbeitung
Damit ASP.NET am Server ein Event feuert, muss die Webseite das HTML Formular, in dem sich alle Tags befinden zum Server versenden. Dies passiert über eine Javascript Hilfesmethode, die ASP.NET in jede Seite einbaut:

 function __doPostBack(eventTarget, eventArgument) {
    if (!theForm.onsubmit || (theForm.onsubmit() != false)) {
        theForm. __EVENTTARGET.value = eventTarget;
        theForm. __EVENTARGUMENT.value = eventArgument;
        theForm.submit(); 
    }
}

__doPostBack setzt bevor das Formular abgeschickt wird noch die Hidden-Fields EVENTTARGET und EVENTARGUMENT. Diese beiden werden vom Server ausgewertet, um festzustellen, welches Control welches Event ausgelöst hat.

Der erste Schritt für Events ist es, das gerenderte HTML um ein onClick Javascript Event zu erweitern, das __doPostBack aufruft:

 <input type="button" ... onClick="__doPostBack('Button1','')" />

Dies geschieht wiederum in der Render Methode. Am Point B fügen Sie folgende Zeile ein:

 writer.AddAttribute(HtmlTextWriterAttribute.Onclick, 
   this.Page.ClientScript.GetPostBackEventReference(this, ""));

GetPostBackEventReference ist eine Hilfsmethode, die den richtigen Aufruf für __doPostBack einfügt.

Damit der Button das Event beim PostBack am Server auch empfangen kann, muss auf der CustomButton Klasse noch das IPostBackEventHandler Interface implementiert werden:

 public class CustomButton : WebControl, IPostBackEventHandler {
    // ...
    public void RaisePostBackEvent(string eventArgument)
    {
        EventHandler handler = (EventHandler)Events[EventClick];
        if (handler != null)
            handler(this, EventArgs.Empty);
    }
}

Das Interface definiert die RaisePostBackEvent Methode, die wir nutzen um die Eventhandler aus der Eventliste zu feuern.

Die Eventverarbeitung ist nun fertig implementiert und läuft nochmals zusammengefasst so ab:

  • HTML-Input-Button wird gedrückt
  • __doPostBack(‘Button1’,’’) löst einen Formular Postback zum Server aus
  • Aufgrund des EVENTTARGETs (‘Button1’) wird der Button gesucht
  • Da der Button IPostBackEventHandler implementiert, wird die Methode RaisePostBackEvent aufgerufen.
  • Alle Eventhandler für das Click Event werden aufgerufen.

Nun kann der CustomButton in der ASP.NET Seite durch einen Eventhandler erweitert werden, um das Click Event zu testen. Auch eine Farbe darf nicht fehlen:

 <cc:CustomButton ID="Button1" runat="server" Text="Save" 
         onClick="Button1_Click" BackColor="Red"/>

ASP.NET Styles mitrendern
Alle WebControls haben gewisse Standardeigenschaften wie BackColor, BorderColor, BorderStyle, Font, etc. Diese Eigenschaften werden von unserem CustomButton derzeit noch ignoriert. Damit sie als CSS-Eigenschaften gerendert werden, muss folgende Zeile in der Render Methode (an Point A) eingefügt werden:

 this.AddAttributesToRender(writer);

Wenn Sie jetzt die Seite neustarten, wird der Button rot gerendert.

Wie man eine eigene TextBox bauen kann lernen Sie im ASP.NET Tutorial – Eigene Server Controls bauen – Teil 2!

Download: Beispielcode zum Mini-Tutorial