L'application "NorthwindMVC" en ASP.NET MVC 2, NHibernate & Spring IoC

L’application WEB "NorthwindMVC" permet de consulter, d’effectuer différentes opérations CRUD sur la base de données Northwind de Microsoft.

Elle utilise les frameworks Nhibernate et Spring IoC et est basée sur une architecture ASP.NET MVC 2.

Cet article a pour but de présenter une solution possible, susceptible d'amélioration, et des éléments de réflexion à l'intégration de ces différentes technologies au sein d'une application WEB.

Télécharger la solution : ici



 

  

  

  

  

  

 


I) Présentation de la solution

La solution se compose de deux projets :


NorthwindMVC.core

Config : contient les fichiers de configuration Spring IoC ("embedded resources")
Business : contient les interfaces et les implémentations de la couche Business (ou Service)
Data : contient les interfaces et les implémentations DAO de la couche Data, interagissant avec la base de données.
DomainObjects : contient les objets de domaines de la couche modèle et les fichiers .hbm ("embedded resources") du mapping ORM NHibernate.
Utils : contient les classes utilitaires de la solution
Validators : contient les validators pour la couche modèle
ModelRessources.resx : fichier externalisant les messages d’erreurs de validation pour la couche modèle

NorthwindMVC.ui

Config : contient les fichiers de configuration Spring IoC ("embedded resources")
App_GlobalResources : contient les ressources de messages d’erreurs de validation de form, messages globales des pages web.
Content : contient le style Site.css (répertoire crée par défaut dans une solution ASP.NET MVC)
Controllers : contient les controlleurs ASP MVC.
Models : contient les classes VIEWDATA de la couche modèle
Scripts : contient les fichiers script pour la mise en place d’AJAX, de JQuery, de validation client des forms ...
Utils : contient les fichiers utilitaires côté UI/Views.
Views : contient les Vues ASP MVC.

II) La base de données Northwind

Le script de création de la base de données Northwind sous Microsoft SQL Server se trouve: ici.

Note :

Une modification a été apportée pour le bon comportement de l’application :
une des vue permet de supprimer une catégorie de produits, le comportement choisi et adopté, est que certains produits puissent se retrouver sans categorie (colonne categoryID de la table Products à NULL).

Ayant rencontré des exceptions SQL, une solution possible a été d'ajouter les lignes suivantes à la fin du fichier script :

ALTER TABLE [Products]   DROP   CONSTRAINT [FK_Products_Categories]

ALTER TABLE [Products]  WITH NOCHECK ADD  CONSTRAINT [FK_Products_Categories] FOREIGN KEY([CategoryID])
REFERENCES [Categories] ([CategoryID]) ON DELETE SET NULL


GO

Diagramme du schéma de la base de données

(Le diagramme comprend les tables essentielles uniquement)



III) La couche Modèle

Elle contient les objets de domaine, les entités NHibernate, qui sont transversaux à toute l’application.
Nous allons nous intéresser plus particulièrement à la validation des formulaires et le mapping ORM Nhibernate.

a) La validation côté serveur.


ASP MVC 2 offre la possibilité de valider côté serveur les formulaires grâce aux « Data Annotations », référencées dans System.ComponentModel.DataAnnotations.

Ce sont des attributs que l’on place au dessus de chacune des propriétés à vérifier d’un objet du Modèle :
- taille minimiale/maximale en caractères
- champs requis ou non ..

public class Category : DomainObject<int>
{
    [Required(ErrorMessageResourceName = "Category_CategoryName",
        ErrorMessageResourceType = typeof(ModelRessources))]
    [StringLength(15,
        ErrorMessageResourceName = "Category_CategoryName",
        ErrorMessageResourceType = typeof(ModelRessources))]
    public virtual string CategoryName { get; set; }

    [Required(ErrorMessageResourceName = "Category_Description",
        ErrorMessageResourceType = typeof(ModelRessources))]
    public virtual string Description { get; set; }

    ...
}

Il est possible d’écrire nos propres attributs et de les utiliser (CF les classes EmailAttribute, PhoneAttribute, PriceAttribute, QtyAttribute dans le dossier Validators du projet NorthwindMVC.core pour plus de détails).

Lorsque l’on valide un formulaire d’édition ou de création .. et que des propriétés de l’objet sont incorrectes, des messages d’erreurs sont affichés.

On peut externaliser les messages d’erreurs dans le fichier ModelRessources.resx, afin de rendre plus souple leur configuration et d'améliorer la lisibilité des classes du Modèle.


Il existe de nombreuses possibilités quant à la validation des objets de domaines.

Par exemple, si vous souhaitez intégrer NHValidator dans une application ASP.NET MVC, notamment parce qu’il permet une externalisation des attributs de validation et des messages d’erreurs dans du XML, je vous conseille d’aller voir ce post.

Libre à vous de choisir.

b) La validation coté client.


Il est possible de réduire le nombre d'aller-retours serveur en mettant en place la validation côté client dans nos formulaires d’édition.

Pour cela, dans la master page :
NorthwindMVC.ui\Views\Shared\Site.Master (*), il suffit d'insérer les lignes de scripts AJAX :

<head runat="server">
  ..
  <script src="../../Scripts/MicrosoftAjax.js" type="text/javascript"></script>
  <script src="../../Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
head>

 et dans les formulaires d’édition utilisant cette master page :

<% Html.EnableClientValidation(); %>

(*) Il est possible d’inclure les fichiers scripts, au cas par cas, dans chacun des formulaires d’édition.

c) Le mapping ORM Nhibernate de la couche modèle.
 
J'ai pu découvrir différents outils permettant de gagner du temps dans la phase de mapping ORM.

Visual NHibernate (version bêta), développé par Slyce software, permet de générer les entités Nhibernate (nos objets de domaine) à partir d'un schéma de la base de données. Il auto-génère également les fichiers de mapping .hbm.xml (mais également les fichiers .nvh.xml de NHValidator).

Une autre alternative à la génération de fichier hbm.xml à partir d'un shéma de base de données est d'utiliser Db2hbm.

Libre à vous de choisir !

IV) La couche DAO

Pour la mise en place de la couche Data, j’ai choisi de reprendre l’architecture proposé par
Billy McCafferty : ici.

La couche Data qui a été retenue utilise un DAO Factory qui crée des DAOs génériques implémentant des operations CRUD Nhibernate sur la base de données.

J’ai repris notamment la classe NHibernateSessionModule.cs (implémentant l'interface IHttpModule) pour la gestion du pattern Open Session in View.

Ce pattern garantit l'ouverture d'une session NHibernate à chaque requête de page web et sa fermeture seulement une fois la page obtenue.
Ceci permet d’éviter les erreurs (lancement d'exceptions) au niveau des collections et d'entités NHibernate chargées tardivement (d'autant plus que le lazy loading est à true par défaut), dues à une fermeture anticipée de la session.

Elément de configuration à rajouter au niveau du Web.config :

<httpModules>
  ...
  <add name="NHibernateSessionModule" type="NorthwindMVC.core.Utils.NHibernateSessionModule"/>
</httpModules>

Un article sur l'utilité, la création et la configuration des HTTP Modules ici

V) La couche Business

C’est la couche contenant les classes de services. Elles manipulent un ou plusieurs DAOs pour effectuer des traitements sur la base de données

Dans notre situation, chaque classe implémente l'interface IService.
Pour cela, elle hérite de la classe abstraite AbstractService.


VI ) La couche WEB : UI et Contrôleur ASP MVC

Elle est constituée des vues et des contrôleurs associés.

a) Les vues

Les vues servent à afficher les résultats d'opérations effectuées sur la base de données.

Il est recommandé de ne pas effectuer des traitements complexes dans ces vues (éviter d’écrire des tests conditionnels if .. then else .., tout ce qui se rapporte au rendu conditionnel …etc)

On peut s’aider entre autres de méthodes d’extensions de la classe htmlHelper.
Le fichier NorthwindMVC.ui\Utils\HtmlHelperExtension.cs contient deux méthodes utiles à nos vues.

htmlHelper.IF
htmlHelper.IFElse

Avec ces méthodes, on pourra vérifier une condition, par exemple :
- si une propriété d’un objet de la couche modèle existe
- si un string est vide ou non
- si une propriété liste contient des éléments
- ..etc

et effectuer une opération selon le cas  : afficher la propriété si elle existe, afficher tet ou tel texte ..etc

La ligne ci-dessous vérifie si la categorie d'un produit existe et affiche le nom de la catégorie si c'est le cas, sinon "None" :

<%= Html.IFElse((Model.Category != null), action => action.Encode(Model.Category.CategoryName), action => action.Encode("None"))%>


b) Les contrôleurs

Ils effectuent des traitements CRUD sur la base Northwind en faisant appel aux classes de la couche Business.

Un contrôleur a pour but d’effectuer un traitement et d’en rendre le résultat en appelant la vue correspondante. Rien d’autre.

Si vos traitements dans le contrôleur deviennent trop complexes, pensez à les effectuer plutôt dans la couche service.

VII ) Configuration de Spring IoC

Le framework Spring IoC a été utilisé pour la mise en place de l’IoC.
L'IoC permet ainsi de déclarer les interfaces (et non les implémentations utilisées) entre les couches UI – Service – Data de l’application.

D'une part, les implémentations utilisées sont déclarées dans des fichiers de configuration xml.
Dans le fichier web.config, on déclare le chemin de ces fichiers :

<spring>
  <context>
    <resource uri="assembly://NorthwindMVC.core/NorthwindMVC.core.Config/spring-config-services.xml" />
    <resource uri="assembly://NorthwindMVC.core/NorthwindMVC.core.Config/spring-config-daos.xml" />
    <resource uri="assembly://NorthwindMVC.ui/NorthwindMVC.ui.Config/spring-config-controllers.xml" />
  </context>
</spring

spring-config-controllers.xml : contient la configuration des implémentations au niveau des contrôleurs.

spring-config-services.xml : contient la configuration des implémentations au niveau de la couche Business/Service.

spring-config-daos.xml : contient la configuration des implémentations des DAOs au niveau de la couche Data.

D'autre part, on utilise la classe NorthwindMVC.core\Utils\SpringControllerFactory.cs en ajoutant la ligne suivante dans le Global.asax.cs :

protected void Application_Start()
{
    ..
    ControllerBuilder.Current.SetControllerFactory(typeof(SpringControllerFactory));
}

La classe SpringControllerFactory.cs a pour rôle de mapper tous les contrôleurs de notre application avec les implémentations définies dans le fichier spring-config-controllers.xml.
Pour cela, elle hérite de la classe System.Web.Mvc.DefaultControllerFactory et implémente l’interface System.Web.Mvc.IControllerFactory.


VIII) Autres : configurations et performances

a) Configurer Log4Net pour NHibernate

Il est pratique de visualiser les requêtes générées par NHibernate en base de données (afin de faciliter le debuggage ou d'optimiser les performances). Pour cela, il est nécessaire de configurer Log4Net (tutorial ici).

Elément de configuration au niveau du web.config:

<configSections>
  ..
  <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
  ..
</configSections>

<log4net>
  <appender name="NHibernateFileLog" type="log4net.Appender.FileAppender">
    <file value="logs/nhibernate.txt" />
    <appendToFile value="false" />
    <layout type="log4net.Layout.PatternLayout">
      <conversionPattern value="%d{HH:mm:ss.fff} [%t] %-5p %c - %m%n"  />
    </layout>
  </appender>

  <appender name="NHibernateConsoleAppender" type="log4net.Appender.ConsoleAppender, log4net">
    <layout type="log4net.Layout.PatternLayout, log4net">
      <param name="ConversionPattern" value="%d %p %m%n" />
    </layout>
  </appender>

  <logger name="NHibernate.SQL" additivity="false">
    <level value="DEBUG"/>
    <appender-ref ref="NHibernateFileLog"/>
  </logger>

  <root>
    <level value="ERROR"/>
    <appender-ref ref="NHibernateFileLog"/>
  </root>
</log4net>

On affichera ainsi les messages de journalisation en DEBUG de la classe Nhibernate.SQL, visibles dans le fichier log/nhibernate.txt (projet Northwind.ui)

Ligne à ajouter au niveau du Global.asax.cs:

protected void Application_Start()
{
    ...
    log4net.Config.XmlConfigurator.Configure();
}

Ne pas oublier de référencer la dll log4net comme cela a été fait pour le projet NorthwindMVC.ui.

b) Eléments d'optimisation des performances NHibernate

- Réfléchir avant d'utiliser le chargement précoce sur les entités ou collections (attribut lazy=false dans les fichiers de mapping ORM).
Le comportement de NHibernate par défaut est de charger ces entités ou collections tardivement (lazy=true).
Un très bon article expliquant le lazy loading et ses impacts en fonctions de la configuration : ici

- Comment configurer le cache level 2 de NHibernate : ici

- Définir le schéma de base de données utilisé par défaut par l'application dans le web.config:

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    ..
    <property name="default_schema">Northwind.dbo</property>
  </session-factory>
</hibernate-configuration>

c) Configurer l'output caching

Il peut être intéressant de configurer l'output caching des pages ASP.NET MVC.
Tutorial : Améliorer les performances de vos applications MVC avec le caching

d) Compiler les vues ASP.NET MVC.

Il est possible de faire valider les vues durant la compilation de la solution, afin d'éviter les erreurs au runtime. Toutefois, l'inconvénient est que la phase de compilation devient plus longue !
Il suffit de configurer un élément au niveau du .csproj, le How To : ici

Télécharger la solution : ici
N'oubliez pas de modifier la ligne suivante avec le nom de serveur [SERVERNAME] adéquat dans le web.config:

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>

    <property name="connection.connection_string">Data Source=[SERVERNAME];Initial Catalog=NORTHWIND;Integrated Security=True</property>

  </session-factory>
</hibernate-configuration>

0 commentaires:

Enregistrer un commentaire