Cliquez ici pour télécharger le code de cet article : CuttingEdge0601.exe (131Ko)
Dans ASP.NET 2.0, les contrôles GridView et DetailsView sont conçus pour fonctionner ensemble. Ils ne fournissent pas seulement des services complémentaires, mais partagent également plusieurs classes et composants d'assistance. La sortie du contrôle GridView se compose d'une suite de lignes, dotées chacune d'un nombre fixe de colonnes. Chaque colonne de la table correspond à une colonne de données dans la source de données liée. Le contrôle DetailsView comporte un nombre fixe de lignes (une pour chaque colonne de la source de données liée) et un nombre constant de colonnes (en-tête et valeur).
GridView et DetailsView peuvent être utilisés ensemble pour créer des pages maître/détails, comme je l'ai expliqué dans mon dernier article (voir Cutting Edge : Flexible Custom Data Views (en anglais)). Les deux représentent des colonnes de données dans la source de données liée. En fait, une colonne de GridView et une ligne de DetailsView correspondent au même type d'objet et sont représentées, dans ASP.NET 2.0, par la même classe : DataControlField.
DataControlField est une classe abstraite qui ne peut être utilisée pour la liaison de données réelles. Plusieurs classes de champs enfants sont dérivées pour lier des données aux contrôles GridView et DetailsView. Dans mon dernier article, j'ai abordé certains de ces types de champs. J'ai notamment expliqué comment utiliser la classe TemplateField pour ajouter aux champs liés des fonctionnalités spéciales d'édition pour l'interface utilisateur et de validation. La classe TemplateField fonctionne comme un conteneur générique de balisage. Elle est idéale pour afficher des types de données personnalisées et toute combinaison de champs existants dont vous pouvez avoir besoin. Le type TemplateField peut être également utilisé pour afficher des colonnes de clés étrangères dans des listes déroulantes ou des dates dans des calendriers.
Mais que faire si vous recherchez une classe de champs de calendriers plus compacte, autrement dit un type CalendarField qui affiche des données liées sous la forme d'une chaîne en mode affichage et utilise un contrôle Calendar plus efficace en modes édition et insertion ? Comme je le disais, vous pouvez obtenir le même résultat à l'aide d'un TemplateField, mais cela reviendrait à utiliser des objets à liaison tardive au lieu d'objets à liaison anticipée. Une page liée aux données à l'aide de CalendarField est plus facile à lire et même un peu plus rapide à traiter à l'exécution.
Nous allons maintenant traiter de la conception et de l'implémentation des types de champs de données personnalisés. Je vais vous expliquer comment les coder à l'aide d'exemples - CalendarField pour afficher des valeurs de date et DropDownField pour afficher des valeurs de clés étrangères définies sur une source de données externe.
La classe DataControlField dans ASP.NET 2.0, similaire au type DataGridColumn très utilisé avec les contrôles DataGrid dans ASP.NET 1.x, est exclusivement utilisée avec les contrôles GridView et DetailsView. S'il vous arrive, toutefois, d'écrire un contrôle personnalisé lié aux données qui exige la liaison à des colonnes de données, vous pouvez utiliser le type DataControlField pour lier les colonnes de données à l'interface utilisateur de votre contrôle.
Comme je le mentionnais plus haut, DataControlField est une classe abstraite qui hérite de System.Object. Elle implémente l'interface IStateManager. La Figure 1 répertorie les méthodes et les propriétés de la classe.
Comme vous pouvez le voir, elle prend en charge plusieurs propriétés de style - ItemStyle, HeaderStyle, FooterStyle et ControlStyle. Dans ASP.NET, les propriétés de style ne sont pas conservées dans l'état d'affichage via le conteneur ViewState habituel, comme c'est le cas des propriétés des types les plus simples. La classe Style d'où sont dérivées toutes les propriétés de style implémente IStateManager afin de prendre en charge la persistance de l'état d'affichage. Pour utiliser cette fonctionnalité particulière, les classes qui exposent les propriétés de style implémentent souvent IStateManager elles-mêmes afin de déléguer la persistance de l'état d'affichage aux méthodes définies sur les objets qui représentent les propriétés de style. Mais nous reviendrons sur ce point.
Les types dérivés de DataControlField sont affichés dans la Figure 2. Bien que la classe DataControlField soit marquée comme abstraite, elle contient beaucoup de code et de nombreux membres concrets. La seule méthode abstraite est CreateField, qui est définie comme suit :
Protected MustOverride Function CreateField() As DataControlField
Cette méthode est censée renvoyer une instance de l'objet du champ de contrôle de données particulier. D'autres méthodes doivent également être remplacées dans les classes dérivées, car leur implémentation intégrée est vide (bien que non abstraite). Il s'agit des méthodes suivantes : ExtractValuesFromCell et InitializeCell. Vous devez les remplacer pour créer un type de champ réellement nouveau. Selon le type de nouvelle fonctionnalité que vous souhaitez intégrer dans le champ, vous devrez peut-être remplacer aussi d'autres membres.
Le composant CalendarField hérite de DataControlField et ajoute trois nouvelles propriétés : DataField, ReadOnly et DataFormatString. DataField est une propriété de chaîne qui indique le nom de la colonne de la source de données qui sera liée au champ. ReadOnly est une propriété booléenne qui indique si le contenu du champ est modifiable. Pour vérifier si le champ doit être ajouté dans l'interface utilisateur du mode insertion, vous pouvez utiliser la propriété booléenne de base InsertVisible. Enfin, DataFormatString indique le format désiré de la date. Le contenu de la propriété DataFormatString est transmis à la méthode ToString de la classe DateTime, ce qui signifie que {0:d} et les expressions similaires ne sont pas valides. Voici un exemple du format de persistance de CalendarField :
<msdn:CalendarField DataField="OrderDate"
HeaderText="Date" ShowHeader="true"
DataFormatString="dd MMM, yyyy" ReadOnly="False" />
Le code suivant décrit l'implémentation de la propriété DataField (souvenez-vous que les autres propriétés personnalisées suivent le même schéma d'implémentation) :
Public Overridable Property DataField As String
Get
Dim o As Object = MyBase.ViewState("DataField")
If (Not o Is Nothing) Then Return CType(o, String)
Return String.Empty
End Get
Set(ByVal value As String)
MyBase.ViewState("DataField") = value
OnFieldChanged()
End Set
End Property
OnFieldChanged est une méthode virtuelle protégée définie sur le DataControlField de la classe parent et conçue pour signaler au contrôle hôte (le GridView) la modification de la valeur d'une propriété sur le champ. Chaque composant du champ de contrôle connaît son contrôle hôte à travers la propriété Control définie sur DataControlField. La propriété Control est protégée et définie lorsque le contrôle hôte initialise le composant champ.
La méthode Initialize (voir Figure 1) est appelée par le contrôle hôte (GridView ou DetailsView) pour initialiser le champ. L'initialisation intervient avant l'ajout du champ à la collection Fields de GridView et DetailsView. GridView et DetailsView appellent la méthode Initialize depuis leur méthode CreateChildControls. La méthode Initialize reçoit son comportement de base dans la classe de base DataControlField et vous n'avez pas réellement besoin de la remplacer dans une classe dérivée :
Public Overridable Function Initialize( _
sortingEnabled As Boolean, ctl As Control) As Boolean
Initialize reçoit une valeur booléenne qui indique si le tri est pris en charge. Elle reçoit également une référence au contrôle hôte (GridView ou DetailsView) qui est stocké dans la propriété Control interne. La valeur de retour indique au contrôle hôte si un appel à DataBind est nécessaire pour finaliser l'initialisation du type de champ.
ValidateSupportsCallback est une autre méthode de DataControlField que vous pouvez souhaiter remplacer dans certains cas. L'implémentation par défaut de la méthode dans DataControlField génère simplement une exception indiquant que le type de champ n'est pas conçu pour prendre en charge les rappels de scripts. Dans une classe dérivée, vous pouvez remplacer cette méthode pour empêcher la génération de l'exception dans certains cas ou systématiquement. Par exemple, vous pouvez fournir une implémentation de méthode vide pour indiquer qu'un champ prend en charge les rappels ou génère une exception en fonction de la valeur d'une propriété propre au champ :
Public Overrides Sub ValidateSupportsCallback()
If Not SupportsCallback Then
Throw New NotSupportedException("Callbacks Not Supported")
End If
End Sub
InitializeCell et ExtractValuesFromCell ont besoin de substitutions dans tous les contrôles importants. InitializeCell vous permet de contrôler totalement le contenu de la cellule dans laquelle la valeur du champ est affichée. ExtractValuesFromCell extrait les valeurs de la cellule pour suivre les opérations de mise à jour ou d'insertion. Avant d'étudier, de manière plus approfondie, ces méthodes et leurs substitutions pour le composant CalendarField, jetons un coup d'oeil à la Figure 3 dans laquelle sont répertoriées d'autres méthodes substituables protégées.
La classe CalendarField fournit les trois propriétés citées ci-dessus, DataField, ReadOnly et DataFormatString, et remplace InitializeCell, ExtractValuesFromCell, CopyProperties et CreateField.
CreateField suit un schéma commun et obtient la même forme d'implémentation dans tous les types de champs intégrés :
Protected Overrides Function CreateField() As DataControlField Return New CalendarField() End Function
Le remplacement de cette méthode est obligatoire si vous dérivez de DataControlField et facultatif, mais recommandé, si vous dérivez à partir de types de champs existants.
Dans InitializeCell, vous commencez en appelant la méthode de base pour importer l'essentiel de la logique qui gère l'affichage de l'en-tête et du pied de page. Vous ajoutez ensuite du code pour personnaliser la cellule active, tel que décrit ci-dessous :
Sub InitializeCell(cell As DataControlFieldCell, _
cellType As DataControlCellType, _
rowState As DataControlRowState, rowIndex As Integer)
' Call the base method
MyBase.InitializeCell(cell, cellType, rowState, rowIndex)
' Initialize the contents of the cell
If cellType = DataControlCellType.DataCell Then
InitializeDataCell(cell, rowState)
End If
End Sub
La méthode InitializeCell reçoit quatre arguments : une référence à l'objet cellule, le type de cellule, ainsi que l'état et l'index de la ligne en cours d'affichage. La plupart des composants de champs existants délèguent leurs tâches d'affichage à une autre méthode interne, souvent appelée InitializeDataCell. La Figure 4 décrit l'implémentation d'InitializeDataCell pour CalendarField.
La méthode InitializeDataCell détermine essentiellement le contrôle à lier aux données : la cellule pour le mode affichage ou un contrôle Calendar pour les modes édition ou insertion. Le contrôle est alors associé à un gestionnaire d'événements DataBinding afin de récupérer et d'afficher les données. Dans le gestionnaire, vous déterminez le mode de fonctionnement, récupérez la valeur à afficher et configurez le contrôle selon les besoins.
En mode affichage, la méthode d'assistance LookupValueForView détermine la valeur à afficher. Elle accepte une référence au conteneur de dénomination du contrôle (cellule de table ou calendrier) qui constitue actuellement l'interface utilisateur du champ. Le conteneur de dénomination est transmis à la méthode DataBinder.GetDataItem pour obtenir une référence à l'objet ligne :
Dim dataItem As Object = DataBinder.GetDataItem(container)
Dim dt As DateTime = _
CType(DataBinder.GetPropertyValue(dataItem, DataField), DateTime)
DataBinder.GetPropertyValue extrait ensuite le champ demandé de l'objet ligne. Pour un contrôle CalendarField, cette valeur est un objet DateTime qui peut être ensuite formaté avant affichage. En mode édition ou insertion, aucun changement important de comportement n'est nécessaire, excepté si vous souhaitez faire une distinction entre édition et insertion auquel cas vous devez indiquer une valeur par défaut :
Protected Overridable Function LookupValueForEdit( _
container As Control) As DateTime
If Not _inInsertMode Then
Dim dataItem As Object = DataBinder.GetDataItem(container)
Dim value As Object = DataBinder.GetPropertyValue( _
dataItem, DataField)
Return CType(value, DateTime)
End If
Return DateTime.Now
End Function
Les contrôles GridView et DetailsView analysent leur propre liste de champs liés afin de recueillir les valeurs d'entrée quand une commande de mise à jour ou d'insertion est sélectionnée. Pour ce faire, ils appellent la méthode ExtractValuesFromCell sur les types de champs. La Figure 5 décrit en détail l'implémentation d'ExtractValuesFromCell dans le composant CalendarField.
La méthode ExtractValuesFromCell est appelée quand les champs sont en mode édition ou insertion. Vous localisez le contrôle d'entrée et capturez toutes les valeurs importantes qu'il contient. Dans le code affiché dans la Figure 4, le contrôle d'entrée de CalendarField est le premier dans la collection de contrôles de la cellule. (Notez que cette position est arbitraire. Elle dépend de la structure de la cellule que vous créez dans InitializeCell.)
Dès que vous obtenez la valeur actuellement sélectionnée du contrôle d'entrée, vous l'ajoutez à l'objet dictionnaire qui a été transmis à la méthode ExtractValuesFromCell. Le dictionnaire contient des paires nom/valeur dans lesquelles l'entrée de nom contient nom de la propriété DataField.
Un champ de contrôle de données personnalisé doit être enregistré avec la page (ou l'application), comme n'importe quel autre contrôle de serveur personnalisé. Pour ce faire, utilisez la directive @ Register de la manière suivante :
<%@ Register Namespace="Samples.CustomFields"
TagPrefix="expo" Assembly="HelperFields" %>
Ajoutez le code suivant à l'élément <fields> d'un contrôle DetailsView ou GridView :
<msdn:CalendarField DataField="OrderDate" HeaderText="Date" />
Passez la page en mode conception et explorez les menus et les fenêtres de Visual Studio® 2005 pour obtenir ce qui est décrit à la Figure 6
.L'exemple de DetailsView comporte deux champs de données personnalisés : CalendarField, utilisé pour la ligne Date et un DropDownField, utilisé pour la ligne "Posted-by" (Envoyé par). Je vous fournirai bientôt d'autres informations sur le contrôle DropDownField. Comme vous pouvez le voir, les deux champs de contrôle possèdent une interface utilisateur légèrement différente, en raison du gestionnaire d'événements DataBinding. Pour l'essentiel, vous cochez la propriété DesignMode et générez des balises si la sortie doit s'afficher dans Visual Studio 2005 :
If DesignMode Then Return "<select><option>Databound Date</option> </select>" End If

Figure 6 : DetailsView
DesignMode est une propriété protégée de la classe DataControlField. Elle enveloppe la propriété DesignMode du contrôle hôte. Le code que vous venez de voir renvoie une interface utilisateur de type déroulant et caractérise le champ de contrôle personnalisé. Les champs de contrôle de données personnalisés doivent être codés manuellement dans l'éditeur, mais vous disposez d'une prise en charge visuelle totale pour modifier les propriétés. Les balises actives et la boîte de dialogue Propriétés affichent correctement les champs personnalisés.
La Figure 7 illustre un exemple de page maître/détail en action. Le contrôle DetailsView utilise le composant CalendarField pour afficher la date d'une commande liée aux données.

Figure 7 : Champs de données personnalisés en action - Mode Edition
Le champ de données DropDownField est utilisé avec les champs de clés étrangères, autrement dit avec des colonnes de données contenant des index vers des données intégrées dans une autre table. Imaginez une table de commandes contenant des colonnes pour assurer le suivi du client qui a passé la commande et de l'employé qui a traité physiquement la commande. Dans la table Orders, vous n'inscrivez pas en principe le nom complet de l'employé et du client, mais vous stockez plutôt un pointeur vers une autre table. Vous obtenez finalement une ligne dans la table Orders contenant des numéros au lieu de simples noms. C'est une bonne chose pour les systèmes de gestion de base de données, mais pas pour les humains que nous sommes. Lorsque les données s'affichent, vous devez transformer ces chiffres en noms lisibles. Ce n'est que la moitié de la tâche que vous êtes en droit d'attendre d'un composant DropDownField. En mode édition ou insertion, le champ de données doit, en effet, pouvoir afficher une liste d'options possibles, c'est-à-dire tous les clients ou employés possibles.
J'ai créé le composant DropDownField pour exposer les propriétés DataTextField et DataValueField. La première indique la colonne de données à utiliser à des fins d'affichage, la seconde la colonne de données à utiliser pour l'échange des données d'E/S. La propriété DataTextField n'est utile que dans un seul scénario : quand votre requête contient des colonnes jointes. Examinez la commande SQL suivante :
SELECT o.*, e.lastname FROM orders o INNER JOIN employees e on o.employeeid=e.employeeid WHERE o.orderid=@id
Une ligne de cette requête qui est liée à un contrôle DetailsView contient à la fois l'ID de l'employé qui a traité la commande (table Orders) et son nom de famille (table jointe Employees). Dans ce cas, vous définissez DataValueField sur employeeid et DataTextField sur lastname. S'il n'y a pas de colonnes jointes, vous laissez la propriété DataTextField vide.
Le composant DropDownField compte également trois propriétés supplémentaires. DataSourceIDForEdit indique le contrôle de la source de données fournissant les options à lister en mode édition/insertion. DataValueFieldForEdit désigne le champ de la source de données utilisé pour déterminer la valeur des éléments de la liste déroulante. Enfin, DataValueFieldForEdit désigne le champ de la source de données utilisé pour déterminer le texte d'affichage des éléments de la liste déroulante.
Pour l'essentiel, le code de DropDownField est le même que celui de CalendarField. Il existe pourtant une différence importante lors de la liaison avec les données. Pour préparer le contrôle Calendar, il vous suffit de définir sa propriété SelectedDate sur la valeur que vous obtenez de la ligne liée. Avec un DropDownField, vous devez d'abord remplir la liste déroulante et sélectionner l'élément qui correspond à la valeur de DataValueField dans la ligne liée. Le contrôle de liste déroulante est lié à la liste des éléments de données sélectionnables via la propriété DataSourceID. Pour la liaison de données, cette procédure convient parfaitement. Toutefois, la définition de DataSourceID sur un contrôle lié aux données ne lance pas automatiquement le processus de liaison. Si vous essayez d'accéder à la collection Items immédiatement après avoir défini DataSourceID, vous obtenez une exception de référence nulle. Si vous appelez DataBind pour forcer la liaison, vous obtenez une exception de dépassement de capacité de la pile, car vous appelez DataBind depuis un événement DataBinding qui est déclenché par un appel à DataBind. L'astuce consiste à associer un gestionnaire d'événements DataBound au contrôle de liste déroulante :
Dim dd As DropDownList = CType(target, DropDownList) dataValue = LookupValueForEdit(target) AddHandler dd.DataBound, AddressOf OnDropDownDataBound dd.DataTextField = DataTextFieldForEdit dd.DataValueField = DataValueFieldForEdit dd.DataSourceID = DataSourceIDForEdit
L'événement DataBound qui est nouveau dans ASP.NET 2.0 pour les contrôles liés aux données, se déclenche quand le processus de liaison des données est terminé. De cette manière, le gestionnaire d'événements pour DataBound se déclenche quand la collection Items est entièrement remplie et vous pouvez sélectionner l'élément en toute sécurité :
Sub OnDropDownDataBound(sender As Object sender, e As EventArgs) Dim dd As DropDownList = CType(sender, DropDownList) Dim li As ListItem = dd.Items.FindByValue(dataValue) li.Selected = True End Sub
L'un des principaux objectifs du composant DropDownField est de pouvoir utiliser la valeur liée, DataValueField, pour trouver un champ associé, éventuellement dans une autre source de données. Cette opération implique une requête de base de données, une opération de JOINTURE INTERNE ou un accès au cache ASP.NET où sont mises en cache les données associées. Il est tout simplement hors de question d'exécuter une requête réelle sur une base de données pour les raisons suivantes : par souci des performances et parce que cela nuirait à la cohérence et vous obligerait à injecter une chaîne de connexion et des informations SQL dans le champ de contrôle de données. La seule approche raisonnable consiste à extraire le champ associé (par exemple, le nom de famille de l'employé dont on connaît l'ID) de la source de données utilisée pour remplir la liste déroulante en mode édition (vous remplissez la liste déroulante quand le champ de contrôle de données est initialisé). Pour minimiser l'impact sur les performances, il est préférable de n'accéder à la source de données que lorsque vous entrez en mode édition/insertion. La mise en cache doit être activée sur le contrôle de la source de données et un mécanisme de dépendance de clé de cache doit être utilisé pour actualiser les données du cache en cas de modifications.
Que se passe-t-il si la ligne liée à DetailsView contient un champ joint ? Dans ce cas, les informations demandées sont déjà disponibles. C'est la raison pour laquelle j'ai défini une propriété DataTextField facultative. Si elle est définie et que le champ spécifié existe vraiment, la source de données à modifier n'est chargée que lorsque le contrôle hôte entre réellement en mode édition/insertion. Si DataTextField n'est pas disponible, l'accès à la source de données intervient au moment de l'initialisation et le contrôle de liste déroulante d'entrée est créé au préalable et mis à la disposition des méthodes internes via une propriété privée. Voici le code de balisage qui lie un champ DropDownField à la colonne employeeid de la table Orders et la colonne lastname de la table Employees :
<msdn:DropDownField
DataValueField="employeeid"
HeaderText="Posted by"
DataSourceIDForEdit="EmployeeDataSource"
DataValueFieldForEdit="employeeid"
DataTextFieldForEdit="lastname" />
EmployeeDataSource est défini dans le code suivant :
<asp:SqlDataSource ID="EmployeeDataSource" runat="server" ConnectionString='<%$ ConnectionStrings:LocalNWind %>' SelectCommand="SELECT employeeid, lastname FROM employees" />
Le code source complet de DropDownField et de CalendarField est disponible avec le téléchargement de ce numéro de MSDN® Magazine.
Par ailleurs, n'oubliez par que le composant DropDownField dont il est ici question est optimisé pour les sources basées sur des données. Ce composant peut cependant être aisément utilisé pour permettre aux utilisateurs de sélectionner une valeur dans une liste fixe, par exemple, un type enum ou toute collection de données énumérable. Pour prendre en charge ces scénarios, vous pouvez choisir d'ajouter une propriété DataSource et de lier par programmation son contenu à la propriété DataSource du contrôle de liste déroulante.
Le type DataControlField définit certaines propriétés de style pour les éléments des en-têtes, des pieds de pages et des données. Par ailleurs, la propriété ControlStyle vous permet de définir le style des contrôles d'entrée utilisés par le composant champ :
<asp:BoundField DataField="CompanyName" HeaderText="Company">
<ControlStyle BackColor="red" />
</asp:BoundField>
L'extrait de code précédent génère un champ lié permettant d'afficher ou de modifier la colonne de données CompanyName. L'arrière-plan de la zone de texte interne du champ est rouge. Lors de l'écriture des champs de contrôle de données personnalisés, vous pouvez utiliser la propriété ControlStyle pour définir le style des contrôles d'entrée de manière polymorphe. Le style des contrôles Calendar et de liste déroulante peut être défini par le biais de la propriété ControlStyle. En affectant des valeurs par défaut à la propriété, vous pouvez également définir de nouveaux paramètres par défaut pour les contrôles d'entrée.
Selon les caractéristiques et les fonctionnalités des contrôles d'entrée, vous pouvez avoir besoin de définir des propriétés de style personnalisées. Une nouvelle propriété de style peut être de type TableItemStyle ou un nouveau type personnalisé dérivé de TableItemStyle. Enfin, toute nouvelle propriété de style que vous ajoutez doit être correctement conservée dans l'état d'affichage. Vous devez remplacer les méthodes LoadViewState, SaveViewState et TrackViewState. La Figure 8 décrit comment procéder pour un exemple de propriété YourStyle.
Pour l'exemple de champ CalendarField, des propriétés de style personnalisées peuvent être définies pour tout style de calendrier que vous souhaitez contrôler au niveau de la page. Dans ce cas, la propriété ControlStyle n'est pas très utile, car elle n'applique que le style de l'élément principal du calendrier. Pour définir le style des composants internes du contrôle d'assistance Calendar, vous devez exposer certains styles internes de Calendar à travers de nouvelles propriétés.
DataControlField est la classe de base des champs de données, composants d'assistance utilisés par les contrôles GridView et DetailsView pour créer leur propre interface utilisateur liée aux données. DataControlField comporte plusieurs classes dérivées, ImageField, CheckBoxField, et les plus célèbres TemplateField et BoundField, qui peuvent être utilisées dans les pages et les applications ASP.NET. Vous pouvez également créer des champs de données personnalisés. Deux exemples courants de ce fonctionnement sont DropDownField pour les clés étrangères et CalendarField pour les dates. Outre la description de ces deux composants contenue dans cet article, la documentation de DataControlField décrit un autre exemple de champ, le RadioButtonField. Dans le code de la documentation, RadioButtonField hérite du CheckBoxField et propose une autre interface utilisateur pour les choix booléens. Pour toute information complémentaire sur ce sujet, consultez cette documentation.
Envoyez vos questions et commentaires pour Dino à cutting@microsoft.com.
Dino Esposito est un pilier de Solid Quality Learning et l'auteur de Programming Microsoft ASP.NET 2.0 (Microsoft Press, 2005). Basé en Italie, Dino participe régulièrement aux différents événements de par le monde. Contactez Dino à l'adresse cutting@microsoft.com ou rejoignez le blog sur weblogs.asp.net/despos.
Article de janvier 2006 de MSDN Magazine.