Création d'un Custom WebControl

Depuis que je développe en .NET, j'entend toujours des gens expliquer qu'asp.net génère du code HTML de très mauvaise qualité, etc. Il n'en est rien dans la pratique. En effet, il est tout à fait possible de travailler en ASP.NET et de générer du beau code HTML. J'ai décidé suite à certaines implémentations réalisées il n'y a pas si longtemps ... de réaliser quelques articles sur ce sujet (en effet, nombre d'entre nous ne connait pas toute la puissance d'ASP.NET). Le premier porte ici sur la création d'un contrôle simple affichant une textbox. Côté .NET, nous pouvons récupérer la valeur. J'expliquerai prochainement comment ajouter des validations client JS propres ainsi que la réalisation de contrôles plus complexes.

La première étape est de créer une nouvelle classe héritant de WebControl. Nous décidons de l'appeler PercentageControl.

[ToolboxData("<{0}:PercentageControl runat=server></{0}:PercentageControl>")]
public class PercentageControl : WebControl
{
}

Il s'agit d'un contrôle "pourcentage" qui est censé nous donner un pourcentage. Nous décidons donc de lui rajouter une propriété renvoyant le pourcentage. Nous définissons également, par attribut, que la "default property" est cette propriété. Nous allons également garder en ViewState cette valeur si elle existe.
 
[DefaultProperty("Value")]
[ToolboxData("<{0}:PercentageControl runat=server></{0}:PercentageControl>")]
public
 class PercentageControl : WebControl
{
    [Bindable(true)]
    [Category("Custom Category")]
    [DefaultValue("")]
    [Description("The percentage value.")]
    [Localizable(false)]
    public double? Value
    {
        get
        {
            var value = this.ViewState["Value"];
            if (value != null)
            {
                return (double)value;
            }
            return default(double?);
        }
        set { this.ViewState["Value"] = value; }
    }
}
 
Nous avons maintenant notre propriété mais nous n'avons encore aucun HTML généré... L'idée est donc maintenant d'implémenter le code C# qui après processing affichera du code HTML.
 
protected override HtmlTextWriterTag TagKey
{
  get { return HtmlTextWriterTag.Input; }
}
protected override void AddAttributesToRender(HtmlTextWriter writer)
{
   base.AddAttributesToRender(writer);
   writer.AddAttribute(HtmlTextWriterAttribute.Type, "text");
   writer.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
   writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Value.HasValue 
                                                      ? this.Value.ToString() 
                                                      : string.Empty);
}
 
Si nous exécutions ce code dans une page HTML, nous verrions donc une textbox. Mais, si nous ajoutions un bouton ou tout autre contrôle provoquant un PostBack, nous verrions que le contrôle serait chaque fois remis à la valeur enregistrée par défaut. Pour garder le contrôle à jour et pour mettre à jour la propriété "Value", il faut implémenter l'interface IPostBackDataHandler et enregistrer notre contrôle comme supportant le "postback". Via ce mécanisme, nous pouvons par exemple lancer un event pour annoncer que notre pourcentage a été modifié lors du postback.
 
protected override void OnPreRender(System.EventArgs e)
{
   base.OnPreRender(e);
   this.Page.RegisterRequiresPostBack(this);
}

#region IPostBackDataHandler Members
public bool LoadPostData(string postDataKey, NameValueCollection postCollection)
{
   var result = postCollection[this.UniqueID];
   if (string.IsNullOrEmpty(result))
   {
       this.Value = default(double?);
       return !this.Value.HasValue;
   }
   double newValue;
   if (double.TryParse(result, out newValue))
   {
      bool hasChanged = this.Value != newValue;
      this.Value = newValue;
      return hasChanged;
   }
   return false;
}

public void RaisePostDataChangedEvent()
{
}
#endregion
 
Nous obtenons un contrôle fonctionnel. Il ne nous reste plus qu'à l'utiliser. Afin d'améliorer un petit peu le look and feel pour nous développeurs, nous pouvons ajouter une icône qui sera visible dans la toolbox de Visual Studio. Nous ajoutons une image en "embedded resource" et nous ajoutons un attribut sur notre classe qui pointe vers ce fichier.
 
[ToolboxBitmap(typeof(PercentageControl), "Percentage.bmp")]
 
 
Voici le fichier final:  
 
[DefaultProperty("Value")]
[ToolboxData("<{0}:PercentageControl runat=server></{0}:PercentageControl>")]
[ToolboxBitmap(typeof(PercentageControl), "Percentage.bmp")]
public class PercentageControl : WebControlIPostBackDataHandler
{
    [Bindable(true)]
    [Category("Custom Category")]
    [DefaultValue("")]
    [Description("The percentage value.")]
    [Localizable(false)]
    public double? Value
    {
        get
        {
            var value = this.ViewState["Value"];
            if (value != null)
            {
                return (double)value;
            }
            return default(double?);
        }
        set { this.ViewState["Value"] = value; }
    }
    protected override HtmlTextWriterTag TagKey
    {
        get { return HtmlTextWriterTag.Input; }
    }
    protected override void AddAttributesToRender(HtmlTextWriter writer)
    {
        base.AddAttributesToRender(writer);
        writer.AddAttribute(HtmlTextWriterAttribute.Type, "text");
        writer.AddAttribute(HtmlTextWriterAttribute.Name, this.UniqueID);
        writer.AddAttribute(HtmlTextWriterAttribute.Value, this.Value.HasValue 
                                                            ? this.Value.ToString() 
                                                            : string.Empty);
    }
    protected override void OnPreRender(System.EventArgs e)
    {
        base.OnPreRender(e);
        this.Page.RegisterRequiresPostBack(this);
    }
    #region IPostBackDataHandler Members
    public bool LoadPostData(string postDataKey, NameValueCollection postCollection)
    {
        var result = postCollection[this.UniqueID];
        if (string.IsNullOrEmpty(result))
        {
            this.Value = default(double?);
            return !this.Value.HasValue;
        }
        double newValue;
        if (double.TryParse(result, out newValue))
        {
            bool hasChanged = this.Value != newValue;
            this.Value = newValue;
            return hasChanged;
        }
        return false;
    }
    public void RaisePostDataChangedEvent()
    {
    }
    #endregion
}
 
Nous pouvons dès à présent le référencer et l'utiliser dans notre code ASP.NET.
 
<div>
    <Custom:PercentageControl runat="server" Value="200" />
    <asp:Button Text="Click" runat="server" />
</div>
 

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.