Communication entre les différents éléments d'une page ASP.NET

Pascal ROZE

Cet article vous présente comment faire communiquer les différents éléments d'une page ASP.NET. On y verra comment faire communiquer une ContentPage avec sa MasterPage, ainsi que comment faire communiquer un UserControl avec sa page mère.

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : Communication entre les différents éléments d'une page ASP.NET

- Auteur : Pascal ROZE

- Parution : 23 mars 2009

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2009 Pascal ROZE. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts. Droits de diffusion permanents accordés à Developpez LLC.

Sommaire

- Introduction

- 1. Communication entre une MasterPage et une ContentPage

- 1-A. Communication de la ContentPage vers la MasterPage

- 1-A-1. La méthode « pas propre »

- 1-A-2. La méthode « propre »

- 1-B. Communication de la MasterPage vers la ContentPage

- 1-B-1. Côté MasterPage

- 1-B-2. Côté ContentPage

- 2. Communication entre une Page et un UserControl

- 2-A. Le UserControl

- 2-B. La Page

- Conclusion

- Liens

- Remerciements

Introduction

À travers cet article, vous allez voir comment faire communiquer une ContentPage avec sa MasterPage. Puis vous verrez comment faire communiquer un UserControl avec sa page mère.

Le site web utilisé dans l'article est développé en C# / ASP.NET avec Visual Studio 2008, et se nomme MasterPageContentPageUserControl.
La structure de la MasterPage, nommée DvpMasterPage, est la suivante:

Le menu haut contiendra les liens permettant de naviguer entre les différentes pages du site.
Le menu bas contiendra un bouton et une liste déroulante.
Le pied de page contiendra un label.
Le code source est disponible à la fin de cet article.

1 - Communication entre une MasterPage et une ContentPage

1-A - Communication de la ContentPage vers la MasterPage

Comme évoqué plus haut, le pied de page de la MasterPage contient un label nommé lblFooter, dont le texte par défaut est « Pied de page par défaut » :

<div id="footer">
<asp:Label ID="lblFooter" runat="server">Footer par défaut</asp:Label>
</div>

Le but ici est de pouvoir modifier le texte du label à partir de la ContentPage.
On va imaginer le fonctionnement suivant :
- une textbox et un bouton dans la ContentPage. Un clic sur le bouton entraîne une mise à jour du texte du pied de page avec le texte saisi dans la textbox.

Code aspx :

Modifier le texte du footer: <asp:TextBox ID="tbFooter" runat="server"></asp:TextBox>
&nbsp;
<asp:Button id="btnSubmit" runat="server" Text="Valider" OnClick="btnSubmit_Click"/>

1-A-1 - La méthode « pas propre »

La 1re idée qui vient à l'esprit est d'utiliser la méthode FindControl de la classe MasterPage, de caster l'objet Control récupéré en Label puis d'assigner une valeur à sa propriété Text.
Cela donnerait :

protected void btnSubmit_Click(object sender, EventArgs e)
{
// On récupère le label du pied de page
var footer = (Label)this.Master.FindControl("lblFooter");
footer.Text = tbFooter.Text;
}

Cela fonctionne, mais ce n'est pas vraiment idéal. Si vous vous trompez dans le nom du label passé à la fonction FindControl, ou si quelque temps plus tard vous modifiez le nom du label dans la MasterPage, vous aurez droit à une belle erreur lors de l'exécution du site web, alors que tout compile correctement.

1-A-2 - La méthode « propre »

L'idée ici est de définir, au niveau de la MasterPage, une propriété qui mettra à jour le texte du label du pied de page.
Dans notre cas, une propriété SetOnly suffit. Ce qui donne :

public string FooterText
{
set
{
lblFooter.Text = value;
}
}

L'étape suivante est d'accéder à cette propriété depuis la ContentPage.
Ce qui donnerait :

protected void btnSubmit_Click(object sender, EventArgs e)
{
this.Master.FooterText = tbFooter.Text;
}

Le problème est que ce code ne compile pas. D'ailleurs l'IntelliSense ne propose pas la propriété FooterText.

La raison est simple: this.Master retourne un objet de type MasterPage, et non un objet de type DvpMasterPage.

Une solution serait de caster cet objet en DvpMasterPage. Ce qui donnerait:

protected void btnSubmit_Click(object sender, EventArgs e)
{
((DvpMasterPage) this.Master).FooterText = tbFooter.Text;
}

Cela fonctionne et, maintenant, l'IntelliSense nous propose la propriété FooterText.
Il existe cependant une solution plus élégante: la directive MasterType
Cette directive permet de fortement typer notre MasterPage. Au niveau du code aspx de notre page, cela donne:

<%@ MasterType VirtualPath="~/DvpMasterPage.Master" %>

À partir de maintenant, la propriété Master de la ContentPage est fortement typée comme vous pouvez le voir :

Et la propriété FooterText est directement accessible. Cela donne finalement :

protected void btnSubmit_Click(object sender, EventArgs e)
{
this.Master.FooterText = tbFooter.Text;
}

Pour info, le fait d'insérer la directive MasterType surcharge la propriété Master dans le fichier designer.cs de la ContentPage, d'où le typage fort :

/// <summary>
/// Master property.
/// </summary>
/// <remarks>
/// Auto-generated property.
/// </remarks>
public new DVP.MasterPageContentPageUserControl.DvpMasterPage Master {
get {
return ((DVP.MasterPageContentPageUserControl.DvpMasterPage)(base.Master));
}
}

1-B - Communication de la MasterPage vers la ContentPage

Le but ici est de récupérer dans la ContentPage, un clic sur le bouton ou une sélection dans la liste déroulante de la MasterPage.

Le principe est le suivant :

- du côté de la MasterPage: déclarer des événements et les lever lors du clic sur le bouton ou lors de la sélection d'un élément dans la liste déroulante ;

- du côté de la ContentPage: s'abonner aux événements de la MasterPage.

1-B-1 - Côté MasterPage

On va déclarer deux événements : un pour le clic sur le bouton et un pour la sélection d'un élément dans la liste déroulante.
Cela donne:

public event EventHandler ColorChanged;
public event EventHandler BoutonClicked;

Il reste maintenant à lever ces événements au moment adéquat :

protected void btnMenu_Click(object sender, EventArgs e)
{
if (BoutonClicked != null)
BoutonClicked(sender, e);
}

protected void ddlColors_SelectedIndexChanged(object sender, EventArgs e)
{
if (ColorChanged != null)
ColorChanged(sender, e);
}

On va également avoir besoin d'une propriété qui permet de récupérer l'élément sélectionné dans la liste déroulante :

public string SelectedColor
{
get
{
return ddlColors.SelectedValue;
}
}

1-B-2 - Côté ContentPage

Il faut commencer par créer les gestionnaires d'événements (« Event Handler »). Un gestionnaire d'événement est la méthode qui va s'exécuter en réponse à un événement.

private void Master_BoutonClicked(object sender, EventArgs e)
{
label.Text = string.Format("Vous avez cliqué sur le bouton de la MasterPage à {0}", DateTime.Now.ToLongTimeString());
}

private void Master_ColorChanged(object sender, EventArgs e)
{
label.Text = "Vous avez sélectionné la couleur " + this.Master.SelectedColor;
}

Il faut ensuite s'abonner à ces événements lors du chargement de la page. C'est là qu'interviennent les gestionnaires d'événements définis précédemment :

protected void Page_Load(object sender, EventArgs e)
{
this.Master.ColorChanged += Master_ColorChanged;
this.Master.BoutonClicked += Master_BoutonClicked;
}

On peut voir que l'IntelliSense propose les événements définis dans la MasterPage

Dorénavant, la ContentPage est automatiquement « avertie » de toute action sur le bouton ou la liste déroulante contenus dans la MasterPage.

2 - Communication entre une Page et un UserControl

On va imaginer le scénario suivant :

- un UserControl qui contient un contrôle Calendar ;

- une page aspx qui contient ce UserControl ;

- à partir de la page, on souhaite pouvoir spécifier le mois à afficher dans le Calendar ;

- on souhaite récupérer au niveau de la page la date sélectionnée par l'utilisateur dans le Calendar.

2-A - Le UserControl

Côté design, le UserControl comporte uniquement un contrôle Calendar :

<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="UcCalendar.ascx.cs" Inherits="DVP.MasterPageContentPageUserControl.UcCalendar" %>
<asp:Calendar ID="Calendar1" runat="server" onselectionchanged="Calendar1_SelectionChanged"></asp:Calendar>

Il faut créer une propriété qui sera utilisée pour spécifier le mois à afficher. Lorsque cette propriété sera settée, on affichera également le Calendar qui est invisible par défaut :

public Constants.Months Month
{
set
{
Calendar1.VisibleDate = new DateTime(DateTime.Now.Year, (int)value, 1);
Calendar1.Visible = true;
}
}

Le type Months est enum défini dans une classe static :

public static class Constants
{
public enum Months
{
Janvier = 1,
Fevrier = 2,
Mars = 3,
Avril = 4,
Mai = 5,
Juin = 6,
Juillet = 7,
Aout = 8,
Septembre = 9,
Octobre = 10,
Novembre = 11,
Decembre = 12
}
}

Il faut également créer une propriété qui retournera la date sélectionnée dans le Calendar :

public DateTime SelectedDate
{
get
{
return Calendar1.SelectedDate;
}
}

La dernière étape consiste à mettre en place le mécanisme qui a pour but d'informer la page qui contient le UserControl lorsque l'utilisateur sélectionne une date dans le Calendar.

Une solution serait d'utiliser la méthode FindControl afin de récupérer le Label puis d'assigner une valeur à sa propriété Text. Cela donnerait :

protected void Calendar1_SelectionChanged(object sender, EventArgs e)
{
((Label)this.Parent.FindControl("lblSelectedDate")).Text = string.Format("La date sélectionnée est {0}", Calendar1.SelectedDate.ToLongDateString());
}

Encore une fois, cette solution est peu élégante. D'une part, on n'est pas à l'abri d'une faute de frappe sur le nom du Label ou d'un renommage de ce dernier ultérieurement. D'autre part, cela lie fortement le UserControl à la page, ce qui va à l'encontre du but d'un UserControl. En effet, un UserControl a pour but d'être réutilisé à différents endroits. Que se passera-t-il si vous utilisez ce UserControl dans une page qui ne contient pas de label intitulé lblSelectedDate? Vous aurez droit à une belle exception à l'exécution.

La bonne méthode est de créer un événement que l'on lèvera lors de la sélection d'une date dans le Calendar et qui sera intercepté par la page.

public event EventHandler SelectedDateChanged;

protected void Calendar1_SelectionChanged(object sender, EventArgs e)
{
if (SelectedDateChanged != null)
SelectedDateChanged(sender, e);
}

2-B - La Page

Dans notre exemple, la page qui contient le UserControl est très simple. Outre le UserControl, on y trouve également :
- une liste déroulante contenant la liste des mois ;
- un Label qui affichera la date sélectionnée dans le Calendar du UserControl.

La liste déroulante a sa propriété AutoPostBack à true. Ainsi, chaque sélection d'un élément entrainera automatiquement une mise à jour du Calendar.

<asp:DropDownList
ID="ddlMonths"
runat="server"
AutoPostBack="true"
onselectedindexchanged="ddlMonths_SelectedIndexChanged">
</asp:DropDownList>

La liste déroulante est databindée au Page_Load à partir des valeurs de l'énumération Months précédemment définie.

protected void Page_Load(object sender, EventArgs e)
{
if(!IsPostBack)
{
ddlMonths.DataSource = Enum.GetNames(typeof(Constants.Months));
ddlMonths.DataBind();
}
}

Lors de la sélection d'un élément dans la liste déroulante, et afin de mettre à jour le Calendar, il suffit d'attribuer une valeur à sa propriété Month. Ce qui donne :

protected void ddlMonths_SelectedIndexChanged(object sender, EventArgs e)
{
var selectedMonth = (Constants.Months)Enum.Parse(typeof(Constants.Months), ddlMonths.SelectedValue);
UcCalendar1.Month = selectedMonth;
}

La dernière étape est de récupérer la date sélectionnée dans le Calendar et de l'afficher dans un Label. Pour cela, il suffit de s'abonner à l'événement levé par le UserControl lors de la sélection d'une date.
Avant de s'abonner à l'événement, il faut créer le gestionnaire d'événement, cette méthode qui sera appelée en réponse à un événement. Ce gestionnaire aura la simple tâche d'afficher la date sélectionnée dans le Calendar. Pour cela, il suffit d'utiliser la propriété du UserControl définie dans ce but. Cela donne :

protected void UcCalendar1_SelectedDateChanged(object sender, EventArgs e)
{
lblSelectedDate.Text = string.Format("La date sélectionnée est {0}", UcCalendar1.SelectedDate.ToLongDateString());
}

Il y a deux façons de s'abonner à cet événement : de façon déclarative ou de façon programmatique.

De façon programmatique dans le Page_Load de la page :

protected void Page_Load(object sender, EventArgs e)
{
UcCalendar1.SelectedDateChanged += new EventHandler(UcCalendar1_SelectedDateChanged);
}

Ou alors de façon déclarative dans le code aspx :

<uc1:UcCalendar ID="UcCalendar1" runat="server" OnSelectedDateChanged="UcCalendar1_SelectedDateChanged" />

Notez qu'il faut ajouter le nom de l'événement avec le préfixe « On » lors de la déclaration de façon déclarative.

Conclusion

Cet article vous a montré comment faire communiquer une MasterPage avec une ContentPage, ainsi qu'un UserControl avec une Page.

Liens

Code source de l'exemple

Remerciements

Je remercie l'équipe Dotnet pour leurs relectures attentives du document.