Maîtriser les événements ASP.NET - Partie 4/5

   Auteur : Frédéric De Lène Mirouze (amethyste16@hotmail.com)


8 - Architecture des événements ASP côté serveur

Les événements sans données de publication

Premier exemple

Cas où le composant doit générer le javascript d'appel de l'événement

Cas où il y a plusieurs événements

Les événements avec données de publication

Cas d'un composant avec un seul contrôle

Cas d'un composant avec plusieurs contrôles

Percolation d'un événement à travers la hiérarchie des composants 

9 - Les événements asynchrones

 

8 - Architecture des événements ASP côté serveur

Nous avons vu comment ASP lève un événement du point de vue de la page HTML. Mais que se passe t'il côté serveur?

ASP s'appuie sur deux interfaces: IPostBackEventHandler et IPostBackDataHandler. Dès qu'un contrôle implémente l'une d'entre elle ou bien les deux, celui-ci est automatiquement inséré dans la chaîne de gestion des événements ASP. Il n'y a rien de plus à faire.

Il y a deux interfaces parce que l'on distingue deux types d'événement qui vont chacun correspondre à une plomberie spécifique:

  1. Les événements SANS données de publication: par exemple un clic sur un bouton
  2. Les événements AVEC données de publication: Evénement déclenché lorsque l'état d'un composant change, SelectedChanged d'un CheckBox par exemple

Voyons les détails ensembles.

Les événements sans données de publication

D'abord examinons notre interface IPostBackEventHandler . Elle ne déclare qu'une seule méthode: RaisePostBackEvent.
Cette méthode est automatiquement appelée par ASP pour demander au composant d'exécuter la logique qui lève ou non ses événements.

Il appartient bien sûr au concepteur du composant de l'implémenter.

Premier exemple

Nous allons développer un composant NewDdl qui hérite de DropDownList, mais implémente un nouvel événement. Cet événement se déclenche chaque fois que l'on sélectionne un article d'indice supérieur à 10 dans la liste.

Listing 8-1: Premier composant

Code C#

public sealed class NewDdl : DropDownList,IPostBackEventHandler
{
    public NewDdl()
    {
    }
 
    public event EventHandler ValeurElevee;
    protected void OnValeurElevee(EventArgs e)
    {
        if (ValeurElevee != null)
        {
            ValeurElevee(this, e);
        }
    }
 
    void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
    {
        if (this.SelectedIndex > 10)
        {
            OnValeurElevee(EventArgs.Empty);
        }
    }
 
    protected override bool LoadPostData(string postDataKey, System.Collections.Specialized.NameValueCollection postCollection)
    {
       this.Page.RegisterRequiresRaiseEvent(this);
       return base.LoadPostData(postDataKey, postCollection);
    }
 
    protected override void OnPreRender(EventArgs e)
    {
       base.OnPreRender(e);
       this.Page.RegisterRequiresRaiseEvent(this);
    }
 
    protected override void Render(HtmlTextWriter writer)
    {
        if (this.Page != null)
        {
            // indispensable pour que les propriétés du contrôle soient postées
            this.Page.VerifyRenderingInServerForm(this);
        }
        base.Render(writer);
    }
}

Code VB

Public NotInheritable Class NewDdl 
Inherits DropDownList 
Implements IPostBackEventHandler 
 
 Public Sub New() 
 End Sub 
 
 Public Event ValeurElevee As EventHandler 
 
 Protected Sub OnValeurElevee(ByVal e As EventArgs) 
   RaiseEvent ValeurElevee(Me, e) 
 End Sub 
 
 Sub IPostBackEventHandler.RaisePostBackEvent(ByVal eventArgument As String) 
   If Me.SelectedIndex > 10 Then 
     OnValeurElevee(EventArgs.Empty) 
   End If 
 End Sub 
 
Protected Overloads Overrides Function LoadPostData(ByVal postDataKey As String, ByVal postCollection As System.Collections.Specialized.NameValueCollection) As Boolean 
 Me.Page.RegisterRequiresRaiseEvent(Me) 
 Return MyBase.LoadPostData(postDataKey, postCollection) 
End Function 
 
Protected Overloads Overrides Sub OnPreRender(ByVal e As EventArgs) 
 MyBase.OnPreRender(e) 
 Me.Page.RegisterRequiresRaiseEvent(Me) 
End Sub
 
 Protected Overloads Overrides Sub Render(ByVal writer As HtmlTextWriter) 
   If Not (Me.Page Is Nothing) Then 
     ' indispensable pour que les propriétés du contrôle soient postées
     Me.Page.VerifyRenderingInServerForm(Me) 
   End If 
   MyBase.Render(writer) 
 End Sub 
End Class

On commence par implémenter un événement ValeurElevee selon le modèle standard avec la méthode On (voir l'annexe dans la première partie du tutoriel).

Le contrôle implémente IPostBackEventHandler, ASP va donc appeler RaisePostBack à un certain moment du cycle de vie de la page, nous verrons quand dans la dernière partie du tutoriel. Le code de cette méthode est simple, il exécute un test pour savoir s'il doit lever ou non l'événement. Dans ce cas il appelle la méthode OnValeurElevee.

Note:
Les appels à RegisterRequiresRaiseEvent sont rendus nécessaires parce que le composant hérite d'un contrôle qui implémente IPostBackDataHandler. Sans cet appel RaisePostBack n'est pas appelé, nous verrons pourquoi plus loin. Si IPostBackDataHandler n'est pas implémenté, l'appel à RaisePostBack nest pas nécessaire comme nous le constaterons aussi avec les deux exemples qui suivent.

Ne me dites pas que c'est difficile!

Cas où le composant doit générer le javascript d'appel de l'événement

Examinons un autre cas de figure, le composant NewLabel:

Listing 8-2: Composant NewLabel

Code C#

public class NewLabel : WebControl, IPostBackEventHandler
{
    public NewLabel()
    {
 
    }
 
    public string Text
    {
        get
        {
            if (this.ViewState["Text"] == null)
            {
                return "Hello le monde";
            }
            return this.ViewState["Text"] as string;
        }
        set
        {
            this.ViewState["Text"] = value;
        }
    }      
 
    public event EventHandler Click;
    protected void OnClick(EventArgs e)
    {
        if (Click != null)
        {
            Click(this,e);
        }
    }
 
    public void RaisePostBackEvent(string eventArgument)
    {
        this.OnClick(EventArgs.Empty);
    }
 
    protected override void RenderContents(HtmlTextWriter writer)
    {
        writer.Write(this.Text);
    }
 
    protected override void Render(HtmlTextWriter writer)
    {
        if (this.Page != null)
        {
            // indispensable pour que les propriétés du contrôle soient postées
            this.Page.VerifyRenderingInServerForm(this);
        }
        base.Render(writer);
    }
}

Code VB

Public Class NewLabel 
Inherits WebControl 
Implements IPostBackEventHandler 
 
 Public Sub New() 
 End Sub 
 
 Public Property Text() As String 
   Get 
     If Me.ViewState("Text") Is Nothing Then 
       Return "Hello le monde" 
     End If 
     Return CType(ConversionHelpers.AsWorkaround(Me.ViewState("Text"), GetType(String)), String) 
   End Get 
   Set 
     Me.ViewState("Text") = value 
   End Set 
 End Property 
 
 Public Event Click As EventHandler 
 
 Protected Sub OnClick(ByVal e As EventArgs) 
   RaiseEvent Click(Me, e) 
 End Sub 
 
 Sub IPostBackEventHandler.RaisePostBackEvent(ByVal eventArgument As String)   
   Me.OnClick(EventArgs.Empty) 
 End Sub 
 
 Protected Overloads Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) 
   writer.Write(Me.Text) 
 End Sub 
 
 Protected Overloads Overrides Sub Render(ByVal writer As HtmlTextWriter) 
   If Not (Me.Page Is Nothing) Then 
     Me.Page.VerifyRenderingInServerForm(Me) 
   End If 
   MyBase.Render(writer) 
 End Sub 
End Class 

Le composant fonctionne correctement, il expose un événement Click auquel on peut s'abonner. Malheureusement rien n'est prévu pour le déclencher bien que nous ayons implémenté IPostBackEventHandler.

La raison est qu'il manque côté client le javascript nécessaire. Dans l'exemple précédent nous héritions de DropDownList et récupérions son événement standard SelectedIndexChanged. L'ajout du script client était donc déjà implémenté.

Dans notre cas il nous appartient de le faire. Nous avons vu au chapitre 7 comment s'y prendre, il n'y a rien de nouveau ici.

Listing 8-3: Ajout du déclenchement des événements

Code C#

protected override void AddAttributesToRender(HtmlTextWriter writer)
{
    base.AddAttributesToRender(writer);
 
    string DoPostBack = 
     this.Page.ClientScript.GetPostBackEventReference(new PostBackOptions(this));
    writer.AddAttribute(HtmlTextWriterAttribute.Onclick, DoPostBack);

Code VB

Protected Overloads Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter) 
 MyBase.AddAttributesToRender(writer) 
 
 Dim DoPostBack As String = 
  Me.Page.ClientScript.GetPostBackEventReference(New PostBackOptions(Me)) 
 writer.AddAttribute(HtmlTextWriterAttribute.Onclick, DoPostBack) 
End Sub 

Nous disposons donc d'un contrôle de type Label qui lève un événement Click lorsque l'utilisateur clique dessus. Je ne vais pas jusqu'à dire que c'est le genre de comportement qu'il est normal de développer, mais je voulais un exemple à partir d'un contrôle que l'on ne puisse soupçonner d'être pollué par une plomberie pré existante.

Notez également que contrairement à l'exemple précédent, nous n'avons nul besoin de faire un appel à RegisterRequiresRaiseEvent.

Cas où il y a plusieurs événements

Notre nouveau composant affiche une valeur numérique et en dessous deux labels Plus et Moins qui incrémentent ou décrémentent la valeur numérique.

Nous souhaitons disposer de deux événements: un pour chacune des actions. Tant que l'on y est on passera dans l'événement la valeur affichée.

Listing 8-4: Composant Compteur

Code C#

public class Compteur : WebControl, IPostBackEventHandler
{
    public Compteur()
    {
 
    }
 
    public int Total
    {
        get
        {
            if (this.ViewState["Total"] == null)
            {
                return 0;
            }
            return Convert.ToInt32(this.ViewState["Total"]);
        }
        set
        {
            this.ViewState["Total"] = value;
        }
    }
 
    public delegate void CompteurEventHandler(object sender, CompteurEventArgs e);
 
    public event CompteurEventHandler Plus;
    protected void OnPlus(CompteurEventArgs e)
    {
        if (Plus != null)
        {
            Plus(this, e);
        }
    }
 
    public event CompteurEventHandler Moins;
    protected void OnMoins(CompteurEventArgs e)
    {
        if (Moins != null)
        {
            Moins(this, e);
        }
    }
 
    void IPostBackEventHandler.RaisePostBackEvent(string eventArgument)
    {
        if (eventArgument == "plus")
        {
            this.Total++;
            this.OnPlus(new CompteurEventArgs(this.Total));
        }
        else
        {
            this.Total--;
            this.OnMoins(new CompteurEventArgs(this.Total));
        }
    }
 
    protected override void RenderContents(HtmlTextWriter writer)
    {
        writer.Write(this.Total);
        writer.WriteBreak(); // <br/>
 
        string DoPostBack = 
          this.Page.ClientScript.GetPostBackEventReference(this, "moins");
        writer.AddAttribute(HtmlTextWriterAttribute.Onclick, DoPostBack);
        writer.RenderBeginTag(HtmlTextWriterTag.Span);
        writer.Write("Moins");
        writer.RenderEndTag();
 
        writer.Write("&nbsp;");
 
        DoPostBack = 
          this.Page.ClientScript.GetPostBackEventReference(this, "plus");
        writer.AddAttribute(HtmlTextWriterAttribute.Onclick, DoPostBack);
        writer.RenderBeginTag(HtmlTextWriterTag.Span);
        writer.Write("Plus");
        writer.RenderEndTag();
    }
 
    protected override void Render(HtmlTextWriter writer)
    {
        if (this.Page != null)
        {
            // indispensable pour que les propriétés du contrôle soient postées
            this.Page.VerifyRenderingInServerForm(this);
        }
        base.Render(writer);
    }
}
 
public class CompteurEventArgs : EventArgs
{
    public CompteurEventArgs()
    {
    }
 
    public CompteurEventArgs(int total)
    {
        this.Total = total;
    }
 
    private int _Total;
    public int Total
    {
        get
        {
            return _Total;
        }
        set
        {
            _Total = value;
        }
    }

Code VB

Public Class Compteur 
Inherits WebControl 
Implements IPostBackEventHandler 
 
 Public Sub New() 
 End Sub 
 
 Public Property Total() As Integer 
   Get 
     If Me.ViewState("Total") Is Nothing Then 
       Return 0 
     End If 
     Return Convert.ToInt32(Me.ViewState("Total")) 
   End Get 
   Set 
     Me.ViewState("Total") = value 
   End Set 
 End Property 
 
 Public Delegate Sub CompteurEventHandler(ByVal sender As Object, ByVal e As CompteurEventArgs) 
 
 Public Event Plus As CompteurEventHandler 
 
 Protected Sub OnPlus(ByVal e As CompteurEventArgs) 
   RaiseEvent Plus(Me, e) 
 End Sub 
 
 Public Event Moins As CompteurEventHandler 
 
 Protected Sub OnMoins(ByVal e As CompteurEventArgs) 
   RaiseEvent Moins(Me, e) 
 End Sub 
 
 Sub IPostBackEventHandler.RaisePostBackEvent(ByVal eventArgument As String
   If eventArgument = "plus" Then 
     System.Math.Min(System.Threading.Interlocked.Increment(Me.Total),Me.Total-1) 
     Me.OnPlus(New CompteurEventArgs(Me.Total)) 
   Else 
     System.Math.Max(System.Threading.Interlocked.Decrement(Me.Total),Me.Total+1) 
     Me.OnMoins(New CompteurEventArgs(Me.Total)) 
   End If 
 End Sub 
 
 Protected Overloads Overrides Sub RenderContents(ByVal writer As HtmlTextWriter) 
   writer.Write(Me.Total) 
 
   writer.WriteBreak ' <br/>
 
   Dim DoPostBack As String = Me.Page.ClientScript.GetPostBackEventReference(Me, "moins") 
   writer.AddAttribute(HtmlTextWriterAttribute.Onclick, DoPostBack) 
   writer.RenderBeginTag(HtmlTextWriterTag.Span) 
   writer.Write("Moins") 
   writer.RenderEndTag 
 
   writer.Write("&nbsp;") 
 
   DoPostBack = Me.Page.ClientScript.GetPostBackEventReference(Me, "plus") 
   writer.AddAttribute(HtmlTextWriterAttribute.Onclick, DoPostBack) 
   writer.RenderBeginTag(HtmlTextWriterTag.Span) 
   writer.Write("Plus") 
   writer.RenderEndTag 
 End Sub 
 
 Protected Overloads Overrides Sub Render(ByVal writer As HtmlTextWriter) 
   If Not (Me.Page Is Nothing) Then 
     Me.Page.VerifyRenderingInServerForm(Me) 
   End If 
   MyBase.Render(writer) 
 End Sub 
End Class 
 
Public Class CompteurEventArgs 
Inherits EventArgs 
 
 Public Sub New() 
 End Sub 
 
 Public Sub New(ByVal