Mehr von Jürgen Gutsch

Mehr von Jürgen Gutsch

Empfehlungen von Jürgen Gutsch

Blog-Empfehlungen von Jürgen Gutsch

Willkommen bei ASP.NET Zone. Anmelden | Registrieren | Hilfe

Jürgen Gutsch

ASP.NET und mehr...

News

Web Server Controls ganz einfach selber machen (Teil 1)

Hiermit starten Peter und ich mal eine kleine Artikelserie zur Erstellung von Web Server Controls.

Vielfach sollen Funktionen in Anwendungen mehrfach verwendet oder verteilt werden. Also in mehreren Webanwendungen zum Einsatz kommen. Das geht bekanntlich am einfachsten, indem diese Funktionen in separate Klassenbibliotheken ausgelagert werden. Die Anwendungen die diese Funktionen nutzen möchten, müssen diese Klassenbibliothek nur als Referenz einbinden.

Wie sieht das nun mit Web Server Controls aus? Ganz genauso. Ein Web Server Control ist ja im Prinzip nichts anderes als eine Klasse die von Control, WebControl oder einem konkreten Control (wie z. B: LinkButton, siehe auch hier: WebControls erweitern) erbt. Microsoft nennt diese selbsterstellten Web Server Controls übrigens “Web Custom Control”.

Das Visual Studio bietet bereits ein Template für die Erstellung von Custom Controls an. So lässt sich in einer neuen Klassenbibliothek einfach über “New Item…” ein neues “ASP.NET Server Control” einfügen. Wählt man dieses aus wird automatisch eine neue klasse eingefügt, die wie folgt aufgebaut ist:

[DefaultProperty("Text")]
[ToolboxData("<{0}:WebCustomControl1 runat=server></{0}:WebCustomControl1>")]
public class WebCustomControl1 : WebControl
{
  [Bindable(true)]
  [Category("Appearance")]
  [DefaultValue("")]
  [Localizable(true)]
  public string Text
  {
    get
    {
      String s = (String)ViewState["Text"];
      return ((s == null) ? String.Empty : s);
    } 
    set
    {
      ViewState["Text"] = value;
    }
  } 
  protected override void RenderContents(HtmlTextWriter output)
  {
    output.Write(Text);
  }
}

Wenn wir die Attribute (in eckigen Klammern) erst einmal missachten sehen wir eine Klasse welche von WebControl erbt, eine eigene Eigenschaft namens “Text” enthält, sowie die überschriebene Methode RenderContents().

Das Attribut “Text” speichert den Wert im ViewState der Seite ab, bzw. ließt diesen aus. In der Methode RenderContents() wird der Wert aus der Eigenschaft “Text” über den HtmlTextWriter ausgegeben.

Das Control kann ich, so wie es ist, schon benutzen und wie Folgt an einen Placeholder auf der Seite ausgeben:

WebCustomControl1 c1 = new WebCustomControl1
{
  ID = "c1",
  Text = "Hallo Welt"
};
PlaceHolder.Controls.Add(c1);

Das Ergebnis sieht so aus:

<span id="c1">Hallo Welt</span>

Custom Controls die von WebControl erben, umgibt immer ein <span>-Tag, was nicht immer gewollt ist. Aus diesem Grund ist es Sinnvoll die Eigenschaft TagKey zu überschrieben, um z. B: ein <div>-Tag zu rendern:

protected override HtmlTextWriterTag TagKey
{
  get { return HtmlTextWriterTag.Div; }
}

Ergebnis:

<div id="c1">
    Hallo Welt
</div>

siehe auch hier: Von WebControl abgeleitetes CustomControl - Haupt Html Element festlegen

Ein Nachteil an dem Beispiel oben ist allerdings das Rendern des Controls per HtmlTextWriter. Man ist damit zwar sehr Flexibel und kann den HTML Code selber erzeugen, benötigt allerdings mehr Schreibarbeit. Das Ausgeben einer TextBox, mit Label und eines zugehörigen Buttons könnte so aussehen:

protected override void RenderContents(HtmlTextWriter output)
{
  output.WriteBeginTag("label");
  output.WriteAttribute("for", this.ClientID +"_TextBox");
  output.Write(">");
  output.WriteEncodedText(Text);
  output.WriteEndTag("label");
  output.WriteBeginTag("input");
  output.WriteAttribute("type", "text");
  output.WriteAttribute("id", this.ClientID + "_TextBox");
  output.WriteAttribute("name", this.UniqueID + "$TextBox");
  output.Write(" />");

  output.WriteBeginTag("input");
  output.WriteAttribute("type", "submit");
  output.WriteAttribute("id", this.ClientID + "_Submit");
  output.WriteAttribute("name", this.UniqueID + "$Submit");
  output.WriteAttribute("value", "Submit");
  output.Write(" />");
}

Eine andere schnellere Variante ist das erzeugen und das Hinzufügen der Controls in der OnInit. Das Rendern übernimmt dann die klasse WebControl, bzw. Control von der wir geerbt haben:

private Label label;
private TextBox textBox;
private Button button;

protected override void OnInit(EventArgs e)
{
  base.OnInit(e);

  label = new Label
  {
    ID = "Label",
    AssociatedControlID = "TextBox"
  };
  Controls.Add(label);

  textBox = new TextBox
  {
    ID = "TextBox"
  };
  Controls.Add(textBox);

  button = new Button
  {
    ID = "Button",
    Text = "Submit"
  };
  Controls.Add(button);
}

protected override void OnPreRender(EventArgs e)
{
  base.OnPreRender(e);

  label.Text = Text;
}

Hinweis 1: Damit das erzeugen der Controls in der OnInit funktioniert, muss zwingend das Interface INamingContainer implementiert werden, da das Control sonst nicht mehrfach auf der Seite eingebunden werden kann und da das Control sonst nur ein PostBack durchführen darf, wenn es die Event-Validierung ausgeschalten wird

Hinweis 2: Eigenschaften unseres Controls (wie hier die Eigenschaft “Text”), sollten erst bei OnPreRender an zu rendernde Controls zugewiesen werden. Auf die Art können Eigenschaften auch gesetzt werden, nachdem unser Control der Control-Hierarchie der Seite zugewiesen wurde. Folgendes wird z. B: nicht funktionieren, wenn wir unsere Eigenschaft “Text” in der OnInit direkt dem Label zuweisen würden:

WebCustomControl4 c4 = new WebCustomControl4
{
  ID = "c4"
};
PlaceHolder.Controls.Add(c4);
c4.Text = "Hallo: ";

Damit der Text ausgegeben wird, müssten wir die Eigenschaft “Text” immer vorher setzen:

WebCustomControl4 c4 = new WebCustomControl4
{
  ID = "c4",
  Text = “Hallo: ”
};
PlaceHolder.Controls.Add(c4);

Setzen wir - wie oben gezeigt - die Eigenschaft aber erst bei OnPreRender, sind beide Varianten möglich.

Beispiel:

Hier noch ein kleines Beispiel eines einfachen kleinen Controls, welches eine ungeordnete Liste ausgibt. Die ListItems werden über eine ListItemCollection (wie sie auch z. B: für die DropDownList benutzt wird) festgelegt. Zusätzlich könne wir einen Text festlegen, für den Fall, dass keine es keine ListItems gibt:

public class SimpleList : Control
{
  private HtmlGenericControl unorderedList = new HtmlGenericControl("ul");
  private HtmlGenericControl emptyText = new HtmlGenericControl("span");

  public ListItemCollection Items { get; private set; }
  public String EmptyDataText { get; set; }

  public SimpleList()
  {
    Items = new ListItemCollection();
  }

  protected override void OnInit(EventArgs e)
  {
    base.OnInit(e);

    if (Items.Count > 0)
    {
      Controls.Add(unorderedList);
    }
    else
    {
      Controls.Add(emptyText);
    }
  }

  protected override void OnPreRender(EventArgs e)
  {
    base.OnPreRender(e);

    if (Items.Count > 0)
    {
      Controls.Add(unorderedList);

      foreach (ListItem listItem in Items)
      {
        HtmlGenericControl unorderedListItem =
          new HtmlGenericControl("li")
        {
          InnerText = listItem.Text
        };
        unorderedList.Controls.Add(unorderedListItem);
      }
    }
    else
    {
      emptyText.InnerText = EmptyDataText;
    }
  }
}

Anwendungsbeispiele:

// Liste mit Items
SimpleList mySimpleList1 = new SimpleList
{
  ID = "mySimpleList1",
  EmptyDataText = "Keine Daten vorhanden!"
};
mySimpleList1.Items.Add(new ListItem("Eintrag 01"));
mySimpleList1.Items.Add(new ListItem("Eintrag 02"));
mySimpleList1.Items.Add(new ListItem("Eintrag 03"));
mySimpleList1.Items.Add(new ListItem("Eintrag 04"));
mySimpleList1.Items.Add(new ListItem("Eintrag 05"));
PlaceHolder.Controls.Add(mySimpleList1);

// Liste ohne Items
SimpleList mySimpleList2 = new SimpleList
{
  ID = "mySimpleList2",
  EmptyDataText = "Keine Daten vorhanden!"
};
PlaceHolder.Controls.Add(mySimpleList2);

Beispielprojekt downloaden: WebControls.zip

Posted: Freitag, 13. Februar 2009 10:08 von Jürgen Gutsch

Kommentare

Peter Bucher sagte:

Sally Jürgen

Gute Einführung und inspirierend für nächste Artikel :)

# Februar 13, 2009 11:55

squadwuschel sagte:

Hallo,

Cooler Eintrag und ich hätte da mal eine Frage. Ich habe auch ein ServerControl erstellt was einfach von WebControls erbt und habe den TagKey auf Div gesetzt und meinem Control zwei weitere Controls Label und Textfeld hinzugefügt. Nun meine Frage wäre wie kann ich denn dem Umgebeneden Div was ich bei TagKey eingestellt habe z.B. noch eine Css-Klasse setzen.

danke squadwuschek

# Mai 16, 2009 11:35

Jürgen Gutsch sagte:

Hallo squadwuschel,

danke für dein Lob :-)

Die CSS-Klasse wir automatisch gesetzt, wen du deinem Control eine CSS-Klasse zuweist ;-)

# Mai 16, 2009 20:38
Anonyme Kommentare sind nicht zugelassen