Migrer vers ASP.NET, pourquoi et comment ?
Tawfik Nassar
10/2002
D'après la littérature de Microsoft sur le sujet, .NET apporte deux éléments :
  • L'environnement 'Common Language Runtime' (CLR - Environnement d'exécution du langage commun) ;
  • Et la bibliothèque de classe '.NET Framework Class Library'

Ce qui m'a impressionné personnellement, était plutôt le CLR, qui me paraissait être la nouveauté qui distingue réellement le produit…
La bibliothèque de classes, elle, est un peu du 'déjà vu' : une sacoche pleine de boulons livrés, pour la plupart, en vrac… beaucoup sont d'ailleurs assez anciens… lustrés pour l'occasion.

Du point de vue du développeur, le Common Language Runtime (CLR) apporte les nouveaux avantages suivants :
  • Compilation du code indépendamment du langage de programmation utilisé, par l'implémentation d'un langage intermédiaire (Intermediate language) qui ouvre également des nouvelles possibilités de communication entre les modules et composantes des applications dites 'gérées' (qui tournent sous le CLR) ;
  • Une meilleure 'robustesse' du code par l'implémentation d'une infrastructure de vérification stricte des types de données et du code, nommée : Common Type System (CTS) (Système Commun de Typage)
  • Une meilleure gestion de la mémoire : élimination des indiscrétions (memory leaks) et des références invalides de mémoire (invalid memory references), gestion automatique de la libération de mémoire des objets inutilisés (garbage collection)… ;
  • Amélioration des performances des scripts et du code précédemment interprété ;
  • Une meilleure gestion de sécurité d'exécution du code : protection de l'utilisateur et de l'auteur du code ;

Pour un développeur sans 'héritage' cela peut être très bien…
Pour le vieux développeur qui se promène avec quelques millions de lignes de code à son 'actif'… le 'déménagement' n'ira pas sans quelques problèmes !
La bonne nouvelle est que Microsoft dit : « bien que le runtime soit conçu pour le software du futur (!), il supportera celui d'aujourd'hui et d'hier »
La migration pourra donc se faire en douceur… du moins : sans panique !

Il s'est passé beaucoup de choses dans les quinze dernières années.
La concurrence acharnée entre les producteurs des outils de développement a beaucoup fait évoluer l'environnement de travail du développeur.
L'entrée en force – presque soudaine ! – d'Internet dans la vie quotidienne des entreprises et des individus, et les intérêts économiques qui se sont associés, ont un peu bousculé cette évolution.
Dans l'urgence de répondre aux besoins des industriels, lancés à plein corps dans l'aventure d'Internet, l'environnement de développement s'est jeté dans une anarchie quasi irrationnelle… Le résultat est assez désastreux…
Des outils bâclés, gratuits ou payants, pour produire des milliards de pages web dont aucune ne s'affiche de la même manière sur deux machines !
Des graphistes qui passent leur temps à 'caler' les éléments de leurs pages avec des pixels vides (!!) ou à remplacer le texte des boutons par des images pour garantir un minimum de compatibilité entre les navigateurs (dont les fabricants sont en guerre !)…
Et des développeurs web qui doivent gérer des millions de lignes de code écrits souvent à la hâte et éparpillés en vrac dans des milliers de fichiers sans aucun lien…
Des environnements de développement incomplets et des langages de programmation troubles… c'est ainsi que la quasi-totalité des sites Internet, lorsqu'ils sont composés de plus 50 pages, peut être qualifiée d'ingérable !

Mon expérience personnelle s'est faite avec VBScript pour le code côté serveur, JavaScript pour le code côté client… j'en parlerai un peu plus loin…

L'environnement de développement lui était composé d'une multitude d'outils : Visual Interdev pour écrire le code (après quelques tentatives, j'ai du abandonner son utilisation pour le débogage !)… Front page pour composer les ébauches des tables complexes, SQL Entreprise Manager pour les rudes tâches de travail avec les bases de données… MS Access simplifiait parfois certaines de ces tâches… Mais, comme les éditeurs de requêtes SQL de ces deux outils étaient un peu rustres, j'avais recours à TextPad (mon éditeur fétiche de chez Helios) qui m'assistait dans les recherches et remplacements avant de recoller ensuite le texte résultant dans l'éditeur de requêtes en cours (navigation qui devait se faire plusieurs fois, car TextPad lui ne connaît rien sur le SQL ce qui produisait souvent des erreurs chez les autres voisins !!)
Il y avait, évidemment, d'autres problèmes à gérer (par d'autres outils !) comme par exemple les connexions aux serveurs (web, SQL…) souvent dans une ambiance assez houleuse en raison de l'humeur massacrante des techniciens d'hébergement (qui devaient, eux aussi, avoir leur bonne part dans la bataille !)

Le temps passé dans ces gymnastiques était, naturellement, pris sur la part du temps pour les tests et le débogage… ce qui finissait par produire des résultats 'insatisfaisants' qui, à leur tour, relançaient le même mécanisme infernal !

L'utilisation de VBScript et de JavaScript n'arrangeait rien à mon naturel 'dépressif' !

Étant des langages interprétés, il était impossible de tester la totalité des lignes de code. Une simple erreur de syntaxe dans une ligne de code n'étant détectable qu'au moment de l'exécution de celle-ci, il fallait 'provoquer' l'interpréteur pour lui faire exécuter toutes les lignes (disons : le maximum) pour simplement vérifier leur syntaxe…
Cela était très agaçant car c'était bêtement IMPOSSIBLE !

Exemple :
IF x = 5 THEN
	Kaka.kak.aka!!+°°°		'// erreur de syntaxe
END IF

Dans les lignes ci-dessus, tant que x n'est pas =5, l'erreur de syntaxe ne sera pas détectée (en général, x devient =5 en pleine présentation chez le client !!)

La syntaxe même de ces deux choses me laissait 'rêveur' !
Il me chagrinait beaucoup par exemple que les lignes suivantes puissent produire une erreur :
DIM	x	AS Integer
DIM	y	AS String

En effet, la syntaxe correcte était donc d'écrire seulement (sans préciser les types des variables !) :
DIM	x
DIM	y

Ce qui était plus facile à écrire, certes…
L'inquiétant était de savoir que le code suivant, bien que 'conforme' au langage, produira, sans doute, une 'vraie' erreur qui peut poser de sérieux problèmes :
x	= 5
x	= x +2			'// cela donne 7: ok
y	= "bonjour"
x	= y
x	= x +2			'// et ceci peut, probablement, donner "bonjour2" !

On ne peut même pas 'initialiser' une variable… on ne peut que lui affecter une valeur.
Exemple : on ne peut pas écrire :
DIM	x =5

Il faut faire cela en deux fois : déclarer la variable, puis lui affecter une valeur, comme ceci :
DIM	x
X	= 5

Avec de tels handicapes, on ne peut évidemment pas envisager la création de classes ni des structures (type dans VB)… les tentatives d'utilisation de ces techniques, notamment dans JavaScript, était une comédie sans nom !

Même la fausse élégance, empruntée du C++, dans JavaScript (les accolades, les '++' et les '--', et les commentaires avec '//') avait du mal à camoufler la véritable approche approximative (à la louche) du langage !

Dans des telles conditions, un seul fichier source de code devenait immanquablement ingérable dès que sa taille dépassait 300 lignes… et l'on devait gérer tout un site !

Dans ASP, pour ouvrir une connexion à une base de données, on pouvait écrire :
DIM	connex		'// on ne sait pas de quel type est connex.. pas grave!
DIM	connect_string	'// ceci sera la chaîne de connexion

'// on appel une fonction pour avoir la chaîne de connexion
connect_string	=get_connection_string()

'// on appel le serveur pour nous créer une instance connexion ADODB
Set connex	=Server.CreateObject("ADODB.CONNECTION")

'// on ouvre la connexion
connex.Open connect_string

Tout cela est assez hasardeux puisque la seule chose qui détermine que conn est une connexion ADODB est le nom de l'objet "adodb.connection" dans l'appel au serveur : Server.CreateObject("adodb.connection")
Une seule erreur de frappe dans "adodb.connection" donnerait un résultat désastreux, qui apparaîtra uniquement au moment de l'exécution !

Dans ASP.NET, les choses sont plus cohérentes dès la déclaration de la variable :
Dim	connect_string	AS String	'// la chaîne de connexion
Dim	connex As OleDb.OleDbConnection	'// on déclare la connexion

'// on obtien la chaîne de connexion
connect_string	= get_connection_string()

'// on crée une nouvelle instance (place mémoire) pour la connexion 
'// qui est un objet (OleDb.OleDbConnection) d'une bibliothèque
connex 	= New OleDb.OleDbConnection(connect_string)

'// on ouvre la connexion
Connex.Open()

Cela aurait pu être encore plus court, puisque .NET nous permet d'initialiser les variables:
Dim	connect_string	AS String	= get_connection_string()
Dim	connex 	As New OleDb.OleDbConnection(connect_string)

Connex.Open()

Le 'nouveau' ici est le mot clé : New qui crée une nouvelle instance d'un objet (réservation mémoire avec les initialisations nécessaires des éléments propres à l'objet concerné).
Comme le savent bien les programmeurs C++ : selon son implémentation, un objet (classe) peut permettre une ou plusieurs variantes de 'constructeurs' pour créer une instance.
L'objet OleDb.oleDbConnection, par exemple, nous permet de créer une nouvelle instance sans paramètres à fournir :
connex 		= New OleDb.OleDbConnection()

Ou en fournissant la chaîne de connexion :
connex 		= New OleDb.OleDbConnection(connect_string)

Selon les paramètres fournis (leur nombre, et types de données dans l'ordre), New fait appel au 'constructeur' adéquat de l'Objet concerné.
Si l'objet en question ne fournit pas de constructeur correspondant au nombre et types de paramètres fournis, le compilateur se charge de nous adresser un message d'erreur… ouf… nous somme sauvés !

Cela nous sera très utile lorsque nous allons créer nos classes plus tard.

Une classe renferme les informations et les fonctionnalités liées au traitement de l'objet qu'elle présente.
Cela peut comprendre des données représentant les attributs de l'objet ou pouvant être nécessaires à ses traitements…
Une classe propose, généralement, un set de fonctions qui fournissent des informations sur les attributs de l'objet, qui affectent les valeurs de certains de ces attributs et/ou qui font des calculs spécifiques à l'objet.

Les données déclarées dans l'espace (fichier) d'une classe sont par défaut visible seulement à l'intérieur de la classe. Cette limitation de visibilité permet à la classe de contrôler la cohérence de ses attributs selon les critères interne de l'objet. (Permet, par exemple, à un objet date de contrôler que la date ne soit jamais le 31 février).

Les données d'une classe peuvent avoir l'attribut 'Public', 'Protected' ou 'Friend'.

Une variable de classe ayant l'attribut 'Public' sera directement accessible par toutes les fonctions (dans le même projet ou dans les projets qui référenceraient celui-ci) lors de la manipulation d'une instance de l'objet représenté par la classe. Une variable 'Public' d'une classe est un 'champ' (field) de la classe et sa valeur pourra être directement modifiée par l'utilisateur.

Une variable de classe ayant l'attribut 'Protected' sera accessible seulement par les fonctions membres de la classe ou d'une classe dérivée de la classe (nous verrons la dérivation des classes plus loin).
Une variable de classe ayant l'attribut 'Friend' sera accessible par toutes les fonctions dans le même projet lors de la manipulation d'une instance de l'objet de la classe.

Toute variable n'ayant pas d'attribut lors de sa déclaration, est considérée comme ayant l'attribut 'Private' et sera accessible seulement par les fonctions membres de la classe.

Nous allons déclarer une classe qui représentera un tag html.
Notre classe de tag contiendra quatre informations :
  • Le nom du tag html – chaîne de caractères (par exemple : TD, P, SPAN… etc.)
  • Le style du tag – chaîne de caractère ;
  • Les autres informations du tag ;
  • Et le texte associé au tag.

Public Class html_tag	'// notre classe est publique
				'// son nom est : html_tag
	'-------------------------------
	' attributs de la classe
	'-------------------------------
	Protected	m_html_tag 	As String		'// ex: td, span, p...
	Protected	m_html_style	As String	'// style html du tag
	Protected	m_info			As String	'// autres infos
	Protected	m_text			As String	'// texte associé au tag

	'-------------------------------
	' ici.. on écrira, par la suite,
	' les propriétés et les fonctions 
	' de la classe
	'-------------------------------
End Class

Comme vous avez remarqué, les attributs du tag sont 'Protected' : accessible seulement dans la classe et dans les classes qui pourraient en dériver.
Nous allons maintenant, dans la zone réservée ci-dessus, proposer des propriétés qui permettront à l'utilisateur de notre classe d'obtenir et de mettre à jour les valeurs de ces attributs :
	'-----------------------------------------------
	' propriété: obtient ou met à jour le tag html
	'-----------------------------------------------
	Property tag() As String
		Get
			Return (m_html_tag)
		End Get

		Set(ByVal Value As String)
			m_html_tag = Value
		End Set
	End Property


	'-----------------------------------------------
	' propriété: obtient ou met à jour le style html
	'-----------------------------------------------
	Property style() As String
		Get
			Return (m_html_style)
		End Get

		Set(ByVal Value As String)
			m_html_style = Value
		End Set
	End Property

	'-----------------------------------------------
	' propriété: obtient ou met à jour les 'autres infos'
	'-----------------------------------------------
	Property info() As String
		Get
			Return (m_info)
		End Get

		Set(ByVal Value As String)
			m_info = Value
		End Set
	End Property

	'-----------------------------------------------
	' propriété: obtient ou met à jour le texte associé
	'-----------------------------------------------
	Property text() As String
		Get
			Return (m_text)
		End Get

		Set(ByVal Value As String)
			m_text = Value
		End Set
	End Property

Il faut maintenant proposer une fonction qui écrit le tag avec son texte associé.
La fonction 'écrira' dans un objet 'HttpResponse'… c'est-à-dire au moment du transfert des données vers le navigateur du poste client, l'objet HttpResponse sera un paramètre fournit par l'appelant et accessible par référence (ByRef) pour que la fonction puisse y écrire les informations :
	'-----------------------------------------------
	' méthode: écrire le tag et le texte associé
	'-----------------------------------------------
	Sub output(ByRef response As HttpResponse)
		response.Write("<" & m_html_tag)	'// ouvre le tag

		'// écrire les attribus (s'il en existe)
		If Len(m_info) > 0 Then
			response.Write(" " & m_info)
		End If

		'// écrire le style html (s'il en existe)
		If Len(m_html_style) >0 Then
			response.Write( " Style=""" & m_html_style & """)
		End If

		response.Write(">")		'// fermer le tag

		'// écrire le texte associé (s'il en existe)
		If Len(m_text) >0 Then
			response.Write( m_text)
		End If

		'// écrire fin de tag (ex : </span>)
		response.Write( "</" & m_html_tag &">")
	End Sub

Tout cela est très bien, notre classe est presque parfaite… c'est un nouveau type de données que l'on peut utiliser dans une déclaration comme celle-ci :
Dim mon_tag	As html_tag

Pour utiliser les options de cette classe, il nous faudra créer une instance de la classe : c'est-à-dire utiliser la méthode New.
Pour créer une instance d'une classe, celle-ci doit avoir une (ou plusieurs) méthodes New : les constructeurs de la classe.
De même, lors de la libération de la mémoire utiliser par une instance, une classe peut proposer au système une méthode 'Finalize' : destructeur de la classe, qui s'occupera des opérations de nettoyage éventuelles (notamment pour libérer la mémoire occupée par des données internes à la classe…) avant de libérer la mémoire de l'instance en cours de la classe.

Nous allons maintenant, toujours dans la zone que nous avons réservée pour les fonctions et propriétés, proposer deux versions de la méthode New :
La première sera sans paramètres (et sera donc la méthode par défaut pour créer une instance de notre classe)… elle initialisera le tag html à <P>
	Sub New()
		m_html_tag = "P"
	End Sub

Notre deuxième version de New aura, comme paramètre, le nom du tag html souhaité :
	Sub New(ByVal str_html_tag As String)
		m_html_tag = str_html_tag
	End Sub

Avec ces deux versions de New, il nous sera possible d'écrire :
Dim	mon_tag	As New html_tag()	'// créer un tag par défaut (<P>)

Ou d'écrire:
Dim	mon_tag	As New html_tag("span")	'// créer un tag <span>

Le fait de pouvoir écrire plusieurs versions (avec des paramètres différents et/ou type de données renvoyé différent) d'une même méthode est appelé 'overloading' – superposition.
Nous pouvons, par exemple, écrire une nouvelle version de notre méthode New qui prendra comme paramètre une autre instance d'objet html_tag. La méthode se chargera de dupliquer les informations de l'objet fourni dans la nouvelle instance :
	Sub New(ByVal autre_tag As html_tag)
		m_html_tag 	= autre_tag.m_html_tag
		m_html_style	= autre_tag.m_html_style
		m_text			= autre_tag.m_text
	End Sub

La superposition (overloading) est applicable sur les méthodes des classes et également sur toutes les fonctions des modules.
On peut, par exemple, écrire dans un module deux versions d'une fonction max :

	Function max(ByVal long1 As Long, ByVal long2 As Long) As Long
		If long1 > long2 Then
			Return long1
		Else
			Return long2
		End If
	End Sub
	Function max(ByVal int1 As Integer, ByVal int2 As Integer) As Integer
		If int1 > int2 Then
			Return int1
		Else
			Return int2
		End If
	End Sub

Le compilateur se charge de :
  • À la création des fonctions superposées : de vérifier que les fonctions superposées (overloaded) sont réellement différentes et adresse une erreur s'elles sont identiques (ayants des paramètres de types identiques et ayants un type de retour identique) ;
  • Au moment de l'appel à une fonction superposée : déterminer la version adéquate de la fonction à appeler selon les paramètres fournis, et adresse une erreur s'il ne trouve pas de fonction correspondant au contexte de ces paramètres.

Notre sous-classe <td>, appelons la 'html_tag_td', est une 'html_tag' avec quelques propriétés et méthodes supplémentaires.
Nous allons, dans une nouvelle classe, 'hériter' les propriétés et méthodes de html_tag, en ajoutant les informations spécifiques au tag <td> :

Public Class html_tag_td	'// notre classe est publique
					'// son nom est : html_tag_td
	Inherits	html_tag	'// nous héritons html_tag

	'-------------------------------
	' attributs spécifiques à la classe
	'-------------------------------
	Protected m_largeur 		As String	'// ex: "30%"

	'-------------------------------
	' ici les propriétés et méthodes 
	' de la classe
	'-------------------------------
End Class

Hériter html_tag, permet à toutes les fonctions de la nouvelle classe (html_tag_td) d'accéder aux variables et de faire appel aux fonctions et méthodes qui sont déclarées dans la classe de base html_tag.

Comme pour toutes les classes, nous allons définir une méthode New qui se chargera de créer une nouvelle instance de notre html_tag_td :

	Sub New(optional ByVal largeur As String ="15%")
		MyBase.New("td")			'// appeler html_tag pour un tag "td"
		m_largeur = largeur		'// largeur, par défaut "15%"
	End Sub

Écrivons maintenant les propriétés spécifiques à la classe :
	'-----------------------------------------------
	' propriété: obtient ou met à jour la largeur
	'-----------------------------------------------
	Property largeur() As String
		Get
			Return (m_largeur)
		End Get

		Set(ByVal Value As String)
			m_largeur = Value
		End Set
	End Property

Vous avez probablement remarqué que nous avons un petit problème : comment allons nous traiter la méthode output définie dans la classe de base ?
La méthode output de html_tag écrit le tag, son style, son texte associé puis fin de tag… elle ne tient pas compte de la nouvelle information que nous avons : la largeur !

ASP.NET fournit une solution (héritée encore du C++ !) : outrepasser la méthode définie dans la classe de base…
Outrepasser une méthode c'est : définir une méthode ayant le même nom et les mêmes paramètres pour effectuer le même traitement mais de manière adaptée à la situation de notre nouvelle classe.

Mais, pour outrepasser une méthode dans une classe, il faut que la méthode de la classe de base soit déjà déclarée comme permettant d'être outrepassée (overridable) !

Nous allons donc modifier la déclaration de la méthode output de la classe html_tag avant d'écrire celle de remplacement :
	'-----------------------------------------------
	' méthode: écrire le tag et le texte associé
	' Overridable = peut être outrepassée
	'-----------------------------------------------
	Overridable Sub output(ByRef response As HttpResponse)

On peut maintenant écrire notre nouvelle méthode qui tiendra compte de l'élément largeur :
	'-----------------------------------------------
	' méthode: écrire le tag td (et le texte associé)
	' overrides = outrepasse celle de la classe html_tag
	'-----------------------------------------------
	Overrides Sub output(ByRef response As HttpResponse)
		response.Write("<" & m_html_tag)	'// ouvre le tag

		'// écrire les attribus (s'il en existe)
		If Len(m_info) > 0 Then
			response.Write(" " & m_info)
		End If

		'// écrire le style html (s'il en existe)
		If Len(m_html_style) >0 Then
			response.Write( " Style=""" & m_html_style & """")
		End If

		'// écrire la largeur (s'il en existe)
		If Len(m_largeur) >0 Then
			response.Write( " width=""" & m_largeur & """")
		End If

		response.Write(">")	'// fermer le tag

		'// écrire le texte associé (s'il en existe)
		If Len(m_text) >0 Then
			response.Write( m_text)
		End If

		'// écrire fin de tag (ex : </span>)
		response.Write( "</" & m_html_tag &">")
	End Sub

Le code que nous venons d'écrire pour la méthode output est un peu redondant avec celui de la classe de base !
Pour traiter ce problème, nous allons séparer la méthode de la classe de base (html_tag) en trois méthodes : une qui écrit l'en-tête du tag, la deuxième pour écrire le texte associé au tag, et la dernière pour écrire la fin du tag… la méthode output (qui restera overridable) fera appel à ces trois méthodes dans l'ordre.

	'-----------------------------------------------
	' méthode: écrire l'en-tête du tag
	'-----------------------------------------------
	Sub output_head(ByRef response As HttpResponse)
		response.Write( vbCrLf & "<" & m_html_tag)	'// ouvre le tag

		'// écrire les autres infos (s'il en existe)
		If Len(m_info) > 0 Then
			response.Write(" " & m_info)
		End If

		'// écrire le style html (s'il en existe)
		If Len(m_html_style) > 0 Then
			response.Write( " Style=""" & m_html_style & """")
		End If
	End Sub

	'-----------------------------------------------
	' méthode: écrire le texte du tag
	'-----------------------------------------------
	Sub output_text(ByRef response As HttpResponse)
		If Len(m_text) > 0 Then
			response.Write( m_text)
		End If
	End Sub

	'-----------------------------------------------
	' méthode: écrire la fin de tag
	'-----------------------------------------------
	Sub output_foot(ByRef response As HttpResponse)
		response.Write( "</" & m_html_tag &">")
	End Sub

	'-----------------------------------------------
	' méthode: écrire l'ensemble des infos du tag
	' Overridable = peut être outrepassée
	'-----------------------------------------------
	Overridable Sub output(ByRef response As HttpResponse)
		output_head(response)
		response.Write(">")		'// fermer le tag

		'// écrire le style html (s'il en existe)
		output_text( response)
		output_foot( response)
	End Sub

De cette manière, la méthode output de notre nouvelle classe html_tag_td sera plus concise :
	'-----------------------------------------------
	' méthode: écrire le tag td (et le texte associé)
	' overrides = outrepasse celle de la classe html_tag
	'-----------------------------------------------
	Overrides Sub output(ByRef response As HttpResponse)
		output_head(response)

		'// écrire la largeur (s'il en existe)
		If Len(m_largeur) >0 Then
			response.Write( " width=""" & m_largeur & """")
		End If

		response.Write(">")		'// fermer le tag

		output_text( response)
		output_foot( response)
	End Sub

Les formulaires web sont les pages .aspx, similaires aux pages .asp dans les sens qu'elles peuvent être associées à du code exécuté sur le serveur pour produire une partie ou la totalité du contenu 'affichable' de la page au moment de la transmission de celle-ci vers le navigateur du poste client.
Avec ASP, le code associé à la page .asp était composé d'un amalgame de morceaux de code importés dans la page grâce à la fameuse instruction <!--#include file="xx"-->, ou insérés directement dans la page, soit par l'utilisation des miraculeux signes <% et %>, entre lesquels on pouvait écrire toute sorte de chose qu'un langage de programmation peut permettre !... soit, de manière plus académique, en utilisant <script runat="server" language="xx">

Avec les web forms, ASP.NET propose une architecture plus cohérente :
  • Par la séparation du code associé à une page de son aspect visuel ;
  • Par un modèle évènementiel de traitement du code (par contraste au modèle linéaire de ASP qui consistait à traiter séquentiellement les lignes de la page (html ou code !) du haut vers le bas)

Un formulaire web est en réalité une classe qui peut produire des éléments visuels en utilisant comme support, la trame graphique de base de la page .aspx physique.
Le code de la classe d'un web form, qui peut être écrit dans l'un des langages proposés par la plateforme .NET (VB, C# ou JScript.Net), est compilé faisant partie de l'application et sera exécuté sur le serveur. Il est stocké dans un fichier (.aspx.vb lorsqu'il s'agit de code VB) indépendamment physiquement du fichier 'support visuel' .aspx.

Étant exécuté sur le serveur, le code d'une classe web form bénéficie de toutes les options de la plateforme .NET et peut avoir accès aux objets proposées dans la bibliothèque de classe '.NET Framework Class Library'

Nous allons examiner un exemple pratique de la migration d'une section de code qui traite les onglets.
Le code dessine les onglets et se charge de traiter les événements associés aux changements de l'onglet actif.

Tout d'abord, nos onglets sont composés de :
  • Une table contenant deux lignes ;
  • Chaque cellule de la première ligne affiche un onglet ;
  • La deuxième ligne est composée d'une seule cellule pour 'souligner' les onglets ;
  • La table est suivie par une série de tables (une table pour chaque onglet), composée chacune d'une seule cellule qui contient un <iframe> qui affiche le contenu (page) associé à l'onglet correspondant ;


Figure 1 : schéma de la table des onglets

Un espace (table + iframe) est associé à chaque onglet pour afficher son contenu.
Les tables associées aux onglets (nommons les : table1, table2 et table3) sont cachées ou affichées selon l'onglet sélectionné :
  • Lorsque l'onglet 1 est actif : table1 est affichée, table2 et table3 sont cachées ;
  • Lorsque l'onglet 2 est actif : table2 est affichée, table1 et table3 sont cachées ;

Lorsque l'onglet 1 est actif, les éléments auraient l'apparence suivante :


Figure 2 : apparence avec onglet 1 actif

Si l'utilisateur clique l'onglet 2, les éléments auraient l'apparence :

Figure 3 : apparence avec 'onglet 2' actif

En activant l'onglet 3 :

Figure 4 : apparence avec 'onglet 3' actif

Ce traitement est effectué sur le poste client par une procédure qui répond à l'événement OnClick de l'onglet.


Figure 5 : exemple d'onglets dans le navigateur

Matériellement, dans le fichier html transmis au navigateur du poste client, les onglets de la Figure 5 ci-dessus ressemblent à ceci :

<table id="idTabs" name="idTabs"  class="tab_table" 
				width:"100%;" cellspacing="0" cellpadding="0" >
<tbody>
	<!—---------------les onglets ---------------->
	<tr>
		<td id="onglet0" name="onglet0" class="nTabSelected" 
				width="7%" height="22" title="dossiers du site" nowrap  
				onclick="ontabClick(0,'nTab','nTabSelected')">Dossiers</td>
		<td id="onglet1" name="onglet1" class="nTab" 
				width="7%" height="22" title="pages du site" nowrap 
				onclick="ontabClick(1,'nTab','nTabSelected')">Pages</td>
		<td id="onglet2" name="onglet2" class="nTab" 
				width="7%" height="22" title="images du site" nowrap  
				onclick="ontabClick(2,'nTab','nTabSelected')">Images</td>
		<td id="onglet3" name="onglet3" class="nTab" 
				width="7%" height="22" title="menu de gestion du site" nowrap 
				onclick="ontabClick(3,'nTab','nTabSelected')">Menus SM</td>
	</tr>
	<!—--------------- pied des onglets ---------------->
	<tr height="0">
		<td class="nTabBottom" colSpan="4" style="width:100%;">&nbsp;</td>
	</tr>
</tbody>
</table>

<!—-------------------------------------------------------------->
<!—--------------- contenus associés aux onglets ---------------->
<!—-------------------------------------------------------------->
	<!—--------------- contenu associé à l'onglet 0 ---------------->
	<table id="txt_Content0" name="txt_Content0" 
		style=" visibility:visible; display:block;" width="100%" height="95%" 
		valign="top" 	cellspacing=0 cellpadding=0 border=0 >
	<tr>
		<td width="100%" height="100%" nowrap>
			<iframe id="nav_frame0" name="nav_frame0" style="LEFT:0px; TOP:0px;
				width:100%; height:100%; 
				visibility:visible; display:block;" 
				width="100%" height="100%" 
				src="pages/folders.htm" 
				scrolling="no"  marginheight="0" marginwidth="0" 
				framespacing="0" ></iframe>
		</td>
	</tr>
	</table>

	<!—--------------- contenu associé à l'onglet 1 ---------------->
	<table id="txt_Content1" name="txt_Content1" 
		style=" visibility:hidden; display:none;" width="100%" height="95%" 
		valign="top"  cellspacing=0 cellpadding=0 border=0 >
	<tr>
		<td width="100%" height="100%" nowrap>
			<iframe id="nav_frame1" name="nav_frame1" style="LEFT:0px; TOP:0px; 
				width:100%; height:100%; 
				visibility:hidden; display:none;" 
				width="100%" height="100%" 
				src="pages/pages.htm" 
				scrolling="no"  marginheight="0" marginwidth="0" 
				framespacing="0" ></iframe>
		</td>
	</tr>
	</table>

	<!—--------------- contenu associé à l'onglet 2 ---------------->
	<table id="txt_Content2" name="txt_Content2" 
		style=" visibility:hidden; display:none;" width="100%" height="95%" 
		valign="top"  cellspacing=0 cellpadding=0 border=0 >
	<tr>
		<td width="100%" height="100%" nowrap>
			<iframe id="nav_frame2" name="nav_frame2" style="LEFT:0px; TOP:0px; 
				width:100%; height:100%; 
				visibility:hidden; display:none;" 
				width="100%" height="100%" 
				src="pages/images.htm" 
				scrolling="no"  marginheight="0" marginwidth="0" 
				framespacing="0" ></iframe>
		</td>
	</tr>
	</table>

	... 
	...

Lorsque l'utilisateur clique sur la cellule d'un onglet, la procédure ontabClick est appelée.

Cette procédure, toujours dans le fichier html du navigateur, ressemble à ceci :
<script language="javascript">
<!--

var	last_ndx		=0;


function ontabClick( ndxTab, class_tab, class_tab_selected )
{
	// si l'élément est déjà actif: fin
	if( last_ndx ==ndxTab)
		return;

	// déclarer les variables
	var	last_tab, cur_tab,
			last_frame, cur_frame;

	// objets à activer, ex : "onglet2", "txt_Content2", "nav_frame2"
	cur_tab		=document.all("onglet" + parseInt( ndxTab));
	cur_table	=document.all("txt_Content" + parseInt( ndxTab));	
	cur_frame	=document.all("nav_frame" + parseInt( ndxTab));

	// objets à désactiver, ex : "onglet0", "txt_Content0", "nav_frame0"
	last_tab	=document.all("onglet" + parseInt( last_ndx));	
	last_table	=document.all("txt_Content" + parseInt( last_ndx));
	last_frame	=document.all("nav_frame" + parseInt( last_ndx));
	
	cur_tab.className		=class_tab_selected; // modifier l'apparrence
	last_tab.className		=class_tab;		 // des onglets actif / inactif

	// cacher la table et le iframe actuellement actifs
	if( last_frame !=null)
	{
		last_frame.style.display		="none";
		last_frame.style.visibility	="hidden";
	}
	if( last_table !=null)
	{
		last_table.style.display		="none";
		last_table.style.visibility	="hidden";
	}

	// afficher la table et le iframe de l'onglet à activer
	if( cur_frame !=null)
	{
		cur_frame.style.display		="block";
		cur_frame.style.visibility		="visible";
	}
	if( cur_table !=null)
	{
		cur_table.style.display		="block";
		cur_table.style.visibility		="visible";
	}
	
	// sauvegarder l'index du dernier onglet actif
	last_ndx	=ndxTab;
}
//-->
</script>


Figure 6 : après activation de l'onglet 'Images' dans le navigateur
Nous allons nous intéresser à nos classes html_tag et html_tag_td (voir Exemple de déclaration d'une classe de tag (étiquette) html plus haut)
Un onglet est en effet une cellule de table avec quelques informations complémentaires :
  • Son index (ordre dans la série d'onglets) ;
  • Ses attributs html (classe de la feuille de styles : actif / inactif) ;
  • Le document (ou page) à afficher.

Nous allons déclarer une classe html_onglet dérivée de la classe html_tag_td :
Public Class html_onglet
	Inherits	html_tag_td		'// nous héritons html_tag_td

	'-------------------------------
	' attributs de la classe
	'-------------------------------
	Protected	m_index			As Integer	'// ordre
	Protected	m_css_class		As String	'// style inactif
	Protected	m_css_class_selected	As String	'// style actif
	Protected	m_doc_src			As String	'// page du contenu à afficher
	'-------------------------------
	' ici.. on écrira les propriétés 
	' et les méthodes de la classe
	'-------------------------------
End Class

Nous allons ajouter deux versions de la méthode New
Une première version avec seulement l'index de l'onglet :
Sub New(ByVal index As Integer)
	MyBase.New()
	m_index			= index
	m_css_class		= "nTab"
	m_css_class_selected	= "nTabSelected"
	m_text			= "&nbsp;"	'// caption =par défaut, un espace
End Sub

La deuxième version de New, avec plus de paramètres dont certains avec des valeurs par défaut :
Sub New(ByVal index As Integer, ByVal str_caption As String, _
		ByVal str_doc_src	As String, _
		Optional ByVal str_css_class As String ="nTab", _
		Optional ByVal str_css_class_selected As String ="nTabSelected")

	MyBase.New()
	m_index			= index
	m_doc_src			= str_doc_src
	m_css_class		= str_css_class
	m_css_class_selected	= str_css_class_selected
	m_text			= str_caption
End Sub

On ajoute aussi les propriétés qui permetteront aux gens d'utiliser notre classe :
	'// obtenir ou modifier l'index
	Property index() As Integer
		Get
			Return (m_index)
		End Get
		Set(ByVal intVal As Integer)
			m_index = intVal
		End Set
	End Property

	'// obtenir ou modifier le document source
	Property doc_src() As String
		Get
			Return (m_doc_src)
		End Get
		Set(ByVal Val As String)
			m_doc_src = Val
		End Set
	End Property

	'// obtenir ou modifier le style normal
	Property css_class() As String
		Get
			Return (m_css_class)
		End Get
		Set(ByVal Val As String)
			m_css_class = Val
		End Set
	End Property

	'// obtenir ou modifier le style sélectionné
	Property css_class_selected() As String
		Get
			Return (m_css_class_selected)
		End Get
		Set(ByVal Val As String)
			m_css_class_selected = Val
		End Set
	End Property

Note : d'autres propriétés, comme la largeur de l'onglet, sont déjà gérées par notre classe de base html_tag_td.

On réécrit la méthode output de notre classe de base, car nous avons besoin de préciser l'index de l'onglet, sa classe de la feuille de style et de redériger le traitement de l'événement OnClick :

	'-----------------------------------------------
	' méthode: écrire l'onglet
	'-----------------------------------------------
	Overrides Sub output(ByRef response As HttpResponse)
		output_head(response)

		'// écrire le id et nom de l'onglet
		response.Write( " id=""onglet" & m_index & """")
		response.Write( " name=""onglet" & m_index & """")

		'// écrire la classe de la feuille de styles
		If m_index =0 Then
			response.Write( " class=""" & m_css_class_selected & """")
		Else
			response.Write( " class=""" & m_css_class & """)
		End If

		'// redériger l'événement OnClick de l'onglet
		response.Write( " onclick="'ontabClick(" _
				& m_index			& "," _
				& """" & m_css_class	& """," _
				& """" & m_css_class_selected & """);'")

		'// écrire la largeur (s'il en existe)
		If Len(m_largeur) >0 Then
			response.Write( " width=""" & m_largeur & """")
		End If

		response.Write(">")		'// fermer le tag

		output_text( response)
		output_foot( response)
	End Sub

Il faut aussi qu'un onglet 'sache' écrire, à l'emplacement souhaité, une table contenant le <iframe> qui affichera le document source qui lui est associé.
Nous allons ajouter une méthode qui permet de faire cela :
	'-----------------------------------------------
	' méthode: écrire la table et le <iframe> 
	' du document associé à l'onglet
	'-----------------------------------------------
	Sub output_doc_frame(ByRef response As HttpResponse)
		Dim tag_table 		As New html_tag("table")
		Dim tag_iframe 		As New html_tag("iframe")

		tag_table.info = " id=""txt_Content" & m_index & """ " _
				& " name=""txt_Content" & m_index & """ " _
				& " width=""100%"" height=""100%"" " _
				& " valign=""top"" cellspacing=""0"" _
				& " cellpadding=""0"" border=""0"" "

		tag_iframe.info = " id=""nav_frame" & m_index & """ " _
				& " name=""nav_frame" & m_index & """ " _
				& " width=""100%"" height=""100%"" " _
				& " src=""" & m_doc_src & """ " _
				& " scrolling=""auto"" " _
				& " marginheight=""0"" " _
				& " marginwidth=""0"" " _
				& " framespacing=""0"" "

		'// le premier onglet est par défaut visible
		If m_index = 0 Then
			tag_table.style = "visibility:visible; display:block;"
			tag_iframe.style = "LEFT:0px; TOP:0px; " _
					& " width:100%; height:100%; " _
					& " visibility:visible; display:block;"
		Else
		'// les autres onglets sont invisibles
			tag_table.style = "visibility:hidden; display:none;"
			tag_iframe.style = "LEFT:0px; TOP:0px; " _
					& " width:100%; height:100%; " _
					& " visibility:hidden; display:none;"
		End If

		tag_table.output_head(response)
		response.Write(">")
		response.Write(vbCrLf & "<tr>" _
					& vbCrLf & "<td width=""100%"" height=""100%"" >")
		tag_iframe.output(response)
		response.Write(vbCrLf & "</td>" & vbCrLf & "</tr>" & vbCrLf)
		tag_table.output_foot(response)
		response.Write(vbCrLf)
	End Sub


Un 'set' d'onglets est une liste (collection) d'onglets (html_onglet) :
Nous déclarons la classe :
Public Class onglets
	'-------------------------------
	' attributs de la classe
	'-------------------------------
	Private		m_onglets	As Collection	'// liste de html_onglet
	'-------------------------------
	' ici.. on écrira les propriétés 
	' et les méthodes de la classe
	'-------------------------------
End Class

Lors de la création d'une instance de la nouvelle classe onglets, nous devons créer une instance (réserver de la mémoire) pour notre collection. Cela veut naturellement dire : que lorsque l'instance de la classe est libérée, nous devons libérer la mémoire occupée par cette collection. Nous allons ajouter les deux méthodes New et Finalize à notre classe onglets :
Sub New()
	m_onglets = New Collection()
End Sub


Protected Overrides Sub Finalize()
	MyBase.Finalize()

	'// libérer les onglets de la liste
	n_onglets = m_onglets.Count()
	For ndx = 1 To n_onglets
		onglet = m_onglets(1)
		m_onglets.Remove(1)
		onglet = Nothing
	Next

	'// libérer la liste
	m_onglets = Nothing
End Sub

Il faut que notre classe onglets puisse permettre l'ajout d'un nouvel onglet:
	'-----------------------------------------------
	' méthode: ajouter un onglet 
	'-----------------------------------------------
	Sub add_tab( ByVal index As Integer, ByVal caption As String, _
				ByVal doc_src As String)
		Dim	new_tab	As New html_onglet( index, caption, doc_src)

		M_onglets.Add(new_tab)
	End Sub

Il serait aussi assez utile de pouvoir accéder aux onglets de la collection avec une notation simple, comme par exemple : mes_ongles(1)mes_onglets(2)… etc.
Nous allons écrire une propriété qui renvoie l'onglet de la collection à l'index souhaité.
Pour que cette écriture puisse être possible, nous allons déclarer cette propriété comme étant la propriété par défaut de notre classe :

	'-----------------------------------------------
	' propriété par défaut: accéder à l'onglet(ndx)
	'-----------------------------------------------
	Default ReadOnly Property Item(ByVal ndx As Integer) As html_onglet
		Get
			Return (m_onglets.Item(ndx))
		End Get
	End Property


D'autres propriétés utiles :
	'-----------------------------------------------
	' propriété: nombre dd'onglets de la collection
	'-----------------------------------------------
	ReadOnly Property count() As Integer
		Get
			Return (m_isoTabs.Count())
		End Get
	End Property

Nous allons aussi proposer une méthode qui écrira la procédure JavaScript qui gère l'événement OnClick sur les onglets :
'-----------------------------------------------------
'* méthode privée : écrire le code JavaScript de la procédure OnClick	
'-----------------------------------------------------
Private Sub output_toggle_tab_code(ByRef response As HttpResponse)
		If m_onglets.Count() <= 1 Then
			Exit Sub
		End If

	response.Write( vbCrLf & "<script language=""javascript"">" _
	& vbCrLf & "<!--" _
	& vbCrLf)

	response.Write( _
	  vbCrLf & "var	last_ndx		=0;" _
	& vbCrLf & "" _
	& vbCrLf & "" _
	& vbCrLf & "function ontabClick( ndxTab, class_tab, class_tab_selected )" _
	& vbCrLf & "{" _
	& vbCrLf & "	if( last_ndx ==ndxTab)" _
	& vbCrLf & "		return;" _
	& vbCrLf & "" _
	& vbCrLf & "" _
	& vbCrLf & "	ndxTab = parseInt(ndxTab);" _
	& vbCrLf & "" _
	& vbCrLf & "	var		ndx, ndx0, bcolor," _
	& vbCrLf & "				last_tab, cur_tab," _
	& vbCrLf & "				last_frame, cur_frame;" _
	& vbCrLf & "		" _
	& vbCrLf & "	ndx		=parseInt( ndxTab);" _
	& vbCrLf & "	ndx0	=parseInt( last_ndx);" _
	& vbCrLf & "		" _
	& vbCrLf & "	cur_tab		=document.all(""onglet"" + ndx);" _
	& vbCrLf & "	last_tab	=document.all(""onglet"" + ndx0);" _
	& vbCrLf & "	cur_table	=document.all(""txt_Content"" + ndx);" _
	& vbCrLf & "	last_table	=document.all(""txt_Content"" + ndx0);" _
	& vbCrLf & "	cur_frame	=document.all(""nav_frame"" + ndx);" _
	& vbCrLf & "	last_frame	=document.all(""nav_frame"" + ndx0);" _
	& vbCrLf & "	" _
	& vbCrLf & "	cur_tab.className		=class_tab_selected;" _
	& vbCrLf & "	last_tab.className		=class_tab;" _
	& vbCrLf & "" _
	& vbCrLf & "	if( last_frame !=null)" _
	& vbCrLf & "	{" _
	& vbCrLf & "		last_frame.style.display    =""none"";" _
	& vbCrLf & "		last_frame.style.visibility    =""hidden"";" _
	& vbCrLf & "	}" _
	& vbCrLf & "	if( last_table !=null)" _
	& vbCrLf & "	{" _
	& vbCrLf & "		last_table.style.display    =""none"";" _
	& vbCrLf & "		last_table.style.visibility    =""hidden"";" _
	& vbCrLf & "	}" _
	& vbCrLf & "	if( cur_frame !=null)" _
	& vbCrLf & "	{" _
	& vbCrLf & "		cur_frame.style.display     =""block"";" _
	& vbCrLf & "		cur_frame.style.visibility    =""visible"";" _
	& vbCrLf & "	}" _
	& vbCrLf & "	if( cur_table !=null)" _
	& vbCrLf & "	{" _
	& vbCrLf & "		cur_table.style.display     =""block"";" _
	& vbCrLf & "		cur_table.style.visibility    =""visible"";" _
	& vbCrLf & "	}" _
	& vbCrLf & "	//	sauvegarder l'inedx de l'onglet sélectioinné " _
	& vbCrLf & "	last_ndx	=ndxTab;" _
	& vbCrLf & "}")

	response.Write(vbCrLf & "//-->" _
	& vbCrLf & "</script>" _
	& vbCrLf)
End Sub

Il faut maintenant écrire la méthode output qui écrira effectivement les onglets dans l'objet httpResponse :
	'-----------------------------------------------------
	'* méthode: écrire les onglets de la collection
	'-----------------------------------------------------
	Sub output (ByRef response As HttpResponse)
		Dim n_tabs 	As Integer
		Dim ndx 		As Integer
		Dim cur_tab 	As html_onglet
		Dim tag_table 	As New html_tag("table")
		Dim tag_td 	As New html_tag("td")

		'// écrire le script Java pour l'événement OnClick
		output_toggle_tab_code(response)

		'// écrire l'en-tête de la table contenant les onglets
		tag_table.info = "id=""idTabs"" " _
			& " width =""100%;"" " _
			& " cellspacing=""0"" cellpadding=""0"""

		response.Write(vbCrLf & "<!" & "-- les onglets -->")
		tag_table.output_head(response)
		response.Write(">" & vbCrLf & "	<tr>" )

		'// écrire les onglets
		n_tabs = m_onglets.Count()

		For ndx = 1 To n_tabs
			cur_tab = m_onglets.Item(ndx)
			cur_tab.output(response)
		Next

		'// écrire la fin de la table des onglets
		response.Write(vbCrLf & "</tr>" _
			& vbCrLf & "<tr>")
		tag_td.info = " class=""nTabBottom"" colSpan=""" & n_tabs & """ " _
			& " width=""100%;"""
		tag_td.output_head(response)

		response.Write(">&nbsp;")
		tag_td.output_foot(response)

		response.Write(vbCrLf & "</tr>" & vbCrLf)
		tag_table.output_foot(response)

		'// écrire les iframes de chaque onglet
		For ndx = 1 To n_tabs
			cur_tab = m_onglets.Item( ndx)
			cur_tab.output_doc_frame( response)
		Next
	End Sub


Figure 7 : Présentation des éléments de deux onglets
Le formulaire de test
Il nous faut maintenant créer la page principale de test : test_onglets.aspx.
C'est un formulaire web .NET (Web Form) dont le corps ressemblera à ceci :

	<body MS_POSITIONING="FlowLayout">
	<%affiche_les_onglets()%>
	</body>

affiche_les_onglets sera une méthode de la classe de notre page de test (test_onglets.aspx)
La méthode aura pour rôle de créer un objet de la classe onglets, ajouter 3 onglets ayant comme contenu la page contenu_onglet.asp ouverte avec le numéro de l'onglet comme paramètre.
La méthode affichera ensuite les onglets par appel à la méthode output de la classe onglets.

Pour ajouter cette méthode, nous allons ouvrir la page de code associée au formulaire web test_onglets.aspx : dans VStudio.NET, cliquer le bouton droit sur la page formulaire et prendre Afficher le code.
	'---------------------------------------------------
	' tester les classes et traitement des onglets
	'---------------------------------------------------
	Sub affiche_les_onglets()
		Dim m_onglets 	As New onglets()

		m_onglets.add_tab( 0, "Onglet 1", "contenu_onglet.asp?tab=1")
		m_onglets.add_tab( 1, "Onglet 2", "contenu_onglet.asp?tab=2")
		m_onglets.add_tab( 2, "Onglet 3", "contenu_onglet.asp?tab=3")
		m_onglets.output(Response)

		m_onglets = Nothing
	End Sub

Il suffit maintenant de compiler le projet, puis ouvrir le formulaire web test_onglets.aspx.

Figure 8 : sortie test_onglets.aspx - onglet 1 actif


Figure 9: sortie test_onglets.aspx - onglet 2 actif

Non… ce n'est pas fini… tout ceci reste à optimiser…
C'est le début du long chemin habituel… bon courage !