Développement d une application de gestion de contacts avec ASP.NET MVC (C#)



Documents pareils
Construire une application marketing Facebook sur la plateforme Windows Azure

Programme Compte bancaire (code)

OpenPaaS Le réseau social d'entreprise

Alfstore workflow framework Spécification technique

Rafraichissement conditionné d'une page en.net

Quelques patterns pour la persistance des objets avec DAO DAO. Principe de base. Utilité des DTOs. Le modèle de conception DTO (Data Transfer Object)

Création d objet imbriqué sous PowerShell.

Cette application développée en C# va récupérer un certain nombre d informations en ligne fournies par la ville de Paris :

Le MSMQ. Version 1.0. Pierre-Franck Chauvet

Serveur d'application Client HTML/JS. Apache Thrift Bootcamp

Programmer en JAVA. par Tama

Plateforme PAYZEN. Définition de Web-services

Modélisation PHP Orientée Objet pour les Projets Modèle MVC (Modèle Vue Contrôleur) Mini Framework

Projet de programmation (IK3) : TP n 1 Correction

Prénom : Matricule : Sigle et titre du cours Groupe Trimestre INF1101 Algorithmes et structures de données Tous H2004. Loc Jeudi 29/4/2004

Bases de données Oracle Virtual Private Database (VPD) pour la gestion des utilisateurs d applications

SYNC FRAMEWORK AVEC SQLITE POUR APPLICATIONS WINDOWS STORE (WINRT) ET WINDOWS PHONE 8

Programmation en Java IUT GEII (MC-II1) 1

Développement d'applications Web HTML5 L'art et la manière avec Visual Studio 2015 et TFS

Sql Server 2000, 2005, 2008 R2 o T-SQL o Création d'un modèle de données o Full text search o Administration Oracle 9i, Oracle 11g

Paginer les données côté serveur, mettre en cache côté client

Exceptions. 1 Entrées/sorties. Objectif. Manipuler les exceptions ;

Notions fondamentales du langage C# Version 1.0

Formation, Audit, Conseil, Développement, UX WinRT Silverlight WPF Android Windows Phone

JADE : Java Agent DEvelopment framework. Laboratoire IBISC & Départ. GEII Université & IUT d Evry nadia.abchiche@ibisc.univ-evry.

Introduction par l'exemple à Entity Framework 5 Code First

Notes de cours : bases de données distribuées et repliquées

A DESTINATION DES SERVICES TIERS. Editeurs d applications et ressources pédagogiques connectées à l ENT

Composants Logiciels. Le modèle de composant de CORBA. Plan

Rapport de stage. Titre : Aide à la conception d interfaces et déploiement d un site d administration de tables SQL Stagiaire : Julien LE GALL

Corrigé des exercices sur les références

Notre processus d embauche

MapReduce. Malo Jaffré, Pablo Rauzy. 16 avril 2010 ENS. Malo Jaffré, Pablo Rauzy (ENS) MapReduce 16 avril / 15

Devenez un véritable développeur web en 3 mois!

PHP. Bertrand Estellon. 26 avril Aix-Marseille Université. Bertrand Estellon (AMU) PHP 26 avril / 214

HTML5. Développement d applications Web. Visual Studio 2015 et TFS. L art et la manière. avec. Philippe DIDIERGEORGES

Développement d'une application Web avec ASP.NET MVC

TD Objets distribués n 3 : Windows XP et Visual Studio.NET. Introduction à.net Remoting

Définition des Webservices Ordre de paiement par . Version 1.0

Flex. Lire les données de manière contrôlée. Programmation Flex 4 Aurélien VANNIEUWENHUYZE

.NET - Classe de Log

Une introduction à Java

La persistance des données dans les applications : DAO, JPA, Hibernate... COMPIL 2010 francois.jannin@inp-toulouse.fr 1

Connaître la version de SharePoint installée

Convers3 Documentation version Par Eric DAVID : vtopo@free.fr

Supervision et infrastructure - Accès aux applications JAVA. Document FAQ. Page: 1 / 9 Dernière mise à jour: 15/04/12 16:14

WebParts. Version 1.0

Modélisation et Gestion des bases de données avec mysql workbench

Programmation Par Objets

Mise en œuvre des serveurs d application

INTRODUCTION. Bienvenue dans la TCN FRENCH TEAM, nous allons ensemble démarrer une belle aventure qui peut devenir lucrative pour tous.

Java Licence Professionnelle CISII, Cours 2 : Classes et Objets

TP JEE Développement Web en Java. Dans ce TP nous commencerons la programmation JEE par le premier niveau d une application JEE : l application web.

TP n 2 Concepts de la programmation Objets Master 1 mention IL, semestre 2 Le type Abstrait Pile

Tp 1 correction. Structures de données (IF2)

Auto-évaluation Programmation en Java

TP1 : Initiation à Java et Eclipse

Direction des Systèmes d'information

1. Installation d'un serveur d'application JBoss:

Création d une application JEE

Les frameworks au coeur des applications web

Urbanisation des systèmes d information

Programmation MacOSX / ios

Les BASES de DONNEES dans WampServer

Le hub d entreprise est une application de déploiement des applications mais aussi un outil de communication

Renommer un contrôleur de Domaine Active Directory Sous Windows Server 2008 R2

1-Introduction 2. 2-Installation de JBPM 3. 2-JBPM en action.7

BTS SIO option SISR Lycée Godefroy de Bouillon Clermont-Ferrand

Langage et Concepts de ProgrammationOrientée-Objet 1 / 40

Introduction à la programmation concurrente

Messagerie asynchrone et Services Web

La programmation orientée objet Gestion de Connexions HTTP Manipulation de fichiers Transmission des données PHP/MySQL. Le langage PHP (2)

Un ordonnanceur stupide

Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère

2. Comprendre les définitions de classes

PHP et mysql. Code: php_mysql. Olivier Clavel - Daniel K. Schneider - Patrick Jermann - Vivian Synteta Version: 0.9 (modifié le 13/3/01 par VS)

Utilisation de JAVA coté Application serveur couplé avec Oracle Forms Hafed Benteftifa Novembre 2008

Création d un service web avec NetBeans 5.5 et SJAS 9

Présentation du langage et premières fonctions

Formulaire de candidature pour les bourses de mobilité internationale niveau Master/ Application Form for International Master Scholarship Programme

Développement d un logiciel de messagerie instantanée avec Dotnet (version simplifiée)

Network Identity Manager mit SN-Gina Outlook Web Access

Création et Gestion des tables

Hébergement et configuration de services WCF. Version 1.0

Android et le Cloud Computing

Bien aborder un projet SharePoint 2013

Firewall ou Routeur avec IP statique

Licence Bio Informatique Année Premiers pas. Exercice 1 Hello World parce qu il faut bien commencer par quelque chose...

REMBO Version 2.0. Mathrice 2004 DESCRIPTION MISE EN OEUVRE CONCLUSION.

ORACLE 10G DISTRIBUTION ET REPLICATION. Distribution de données avec Oracle. G. Mopolo-Moké prof. Associé UNSA 2009/ 2010

PHP 4 PARTIE : BASE DE DONNEES

CONCOURS DE L AGRÉGATION INTERNE «ÉCONOMIE ET GESTION» SESSION 2014

ASP.NET 2.0, C#, Spring.Net et NHibernate

Installation de SCCM 2012 (v2)

La base de données dans ArtemiS SUITE

Une introduction à la technologie EJB (2/3)

Point sur les solutions de développement d apps pour les périphériques mobiles

Série TX3 SYSTÈMES D'ACCÈS PAR TÉLÉPHONE. Démarrage rapide du Configurateur. Version 2.1 Mircom Copyright 2014 LT-973

Chapitre 10. Les interfaces Comparable et Comparator 1

Transcription:

Développement d une application de gestion de contacts avec ASP.NET MVC (C#) Etape 4 Rendre l application faiblement couplée Dans cette 4ème étape, nous allons mettre en œuvre plusieurs modèles de développement logiciel (Design Patterns) pour que l application devienne plus facile à maintenir et à modifier. Point de départ Au départ, on commence avec la classe du contrôleur Contact qui fait tout, de la validation des données saisies à leur lecture et à leur mise à jour dans la base de données. Listing 1 Controllers\ContactController.cs using System.Linq; using System.Text.RegularExpressions; using System.Web.Mvc; using ContactManager.Models; namespace ContactManager.Controllers public class ContactController : Controller private ContactManagerDBEntities _entities = new ContactManagerDBEntities(); protected void ValidateContact(Contact contacttovalidate) if (contacttovalidate.firstname.trim().length == 0) ModelState.AddModelError("FirstName", "First name is required."); if (contacttovalidate.lastname.trim().length == 0) ModelState.AddModelError("LastName", "Last name is required."); if (contacttovalidate.phone.length > 0 &&!Regex.IsMatch(contactToValidate.Phone, @"((\(\d3\)?) (\d3-))?\d3-\d4")) ModelState.AddModelError("Phone", "Invalid phone number."); if (contacttovalidate.email.length > 0 &&!Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]2,4$")) ModelState.AddModelError("Email", "Invalid email address."); public ActionResult Index() return View(_entities.ContactSet.ToList()); public ActionResult Create() public ActionResult Create([Bind(Exclude = "Id")] Contact contacttocreate)

// Validation logic ValidateContact(contactToCreate); if (!ModelState.IsValid) // Database logic _entities.addtocontactset(contacttocreate); _entities.savechanges(); public ActionResult Edit(int id) var contacttoedit = (from c in _entities.contactset where c.id == id select c).firstordefault(); return View(contactToEdit); public ActionResult Edit(Contact contacttoedit) ValidateContact(contactToEdit); if (!ModelState.IsValid) var originalcontact = (from c in _entities.contactset where c.id == contacttoedit.id select c).firstordefault(); _entities.applypropertychanges(originalcontact.entitykey.entitysetname, contacttoedit); _entities.savechanges(); public ActionResult Delete(int id) var contacttodelete = (from c in _entities.contactset where c.id == id select c).firstordefault(); return View(contactToDelete);

public ActionResult Delete(Contact contacttodelete) var originalcontact = (from c in _entities.contactset where c.id == contacttodelete.id select c).firstordefault(); _entities.deleteobject(originalcontact); _entities.savechanges(); Utilisation du modèle «Repository» On va suivre le modèle de développement «Repository» qui consiste à isoler le code chargé de l accès aux données du reste de l application. Pour cela, on commence par créer une interface qui décrit les différentes méthodes dont on a besoin pour gérer les données, telles qu on les trouve actuellement dans le contrôleur : Dans l action Index, on récupère la liste de tous les contacts, Dans l action Create, il y a du code pour créer un contact, Dans l action Edit, on a du code pour retrouver un contact et pour modifier ce contact, Dans l action Delete, on doit aussi retrouver un contact et supprimer ce contact. Listing 2 Models\IContactManagerRepository.cs using System; using System.Collections.Generic; namespace ContactManager.Models public interface IContactManagerRepository Contact CreateContact(Contact contacttocreate); void DeleteContact(Contact contacttodelete); Contact EditContact(Contact contacttoedit); Contact GetContact(int id); IEnumerable<Contact> ListContacts();

Dans cette interface, on décrit donc les 5 méthodes qui nous permettent d accéder aux contacts : CreateContact(), DeleteContact(), EditContact(), GetContact() et ListContacts(). On crée ensuite une classe qui implémente cette interface IContactManagerRepository. Comme la couche d accès aux données est confiée à Microsoft Entity Framework, on va nommer cette classe EntityContactManagerRepository. Listing 3 Models\EntityContactManagerRepository.cs using System.Collections.Generic; using System.Linq; namespace ContactManager.Models public class EntityContactManagerRepository : ContactManager.Models.IContactManagerRepository private ContactManagerDBEntities _entities = new ContactManagerDBEntities(); public Contact GetContact(int id) return (from c in _entities.contactset where c.id == id select c).firstordefault(); public IEnumerable<Contact> ListContacts() return _entities.contactset.tolist(); public Contact CreateContact(Contact contacttocreate) _entities.addtocontactset(contacttocreate); _entities.savechanges(); return contacttocreate; public Contact EditContact(Contact contacttoedit) var originalcontact = GetContact(contactToEdit.Id); _entities.applypropertychanges(originalcontact.entitykey.entitysetname, contacttoedit); _entities.savechanges(); return contacttoedit; public void DeleteContact(Contact contacttodelete) var originalcontact = GetContact(contactToDelete.Id); _entities.deleteobject(originalcontact); _entities.savechanges();

Utilisation du modèle «Injection de Dépendance» Maintenant que l accès aux données est géré par notre classe Repository, il faut faire évoluer le code du contrôleur pour utiliser cette classe Repository. Pour cela, nous employons une technique appelée «l injection de dépendance» : la classe contrôleur ne vas pas instancier la classe Repository dont elle a besoin, mais «injecter» un objet Repository Listing 4 Controllers\ContactController.cs using System.Text.RegularExpressions; using System.Web.Mvc; using ContactManager.Models; namespace ContactManager.Controllers public class ContactController : Controller private IContactManagerRepository _repository; public ContactController() : this(new EntityContactManagerRepository()) public ContactController(IContactManagerRepository repository) _repository = repository; protected void ValidateContact(Contact contacttovalidate) if (contacttovalidate.firstname.trim().length == 0) ModelState.AddModelError("FirstName", "First name is required."); if (contacttovalidate.lastname.trim().length == 0) ModelState.AddModelError("LastName", "Last name is required."); if (contacttovalidate.phone.length > 0 &&!Regex.IsMatch(contactToValidate.Phone, @"((\(\d3\)?) (\d3-))?\d3-\d4")) ModelState.AddModelError("Phone", "Invalid phone number."); if (contacttovalidate.email.length > 0 &&!Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]2,4$")) ModelState.AddModelError("Email", "Invalid email address."); public ActionResult Index() return View(_repository.ListContacts()); public ActionResult Create() public ActionResult Create([Bind(Exclude = "Id")] Contact contacttocreate) // Validation logic ValidateContact(contactToCreate); if (!ModelState.IsValid)

// Database logic _repository.createcontact(contacttocreate); public ActionResult Edit(int id) return View(_repository.GetContact(id)); public ActionResult Edit(Contact contacttoedit) // Validation logic ValidateContact(contactToEdit); if (!ModelState.IsValid) // Database logic _repository.editcontact(contacttoedit); public ActionResult Delete(int id) return View(_repository.GetContact(id)); public ActionResult Delete(Contact contacttodelete) _repository.deletecontact(contacttodelete);

Par rapport à avant, on peut remarquer que notre contrôleur contient maintenant deux constructeurs. Le premier constructeur n attend pas de paramètre. Il se contente d instancier un objet EntityContactManagerRepository et de faire passer ( «injecter») celui-ci au second constructeur : public ContactController() : this(new EntityContactManagerRepository()) Le second constructeur attend comme paramètre une interface IContactManagerRepository : public ContactController(IContactManagerRepository repository) _repository = repository; Grace à cette technique (appelée «Constructeur d injection de dépendance»), seul le premier constructeur utilise la «vraie» classe EntityContactManagerRepository alors que tout le reste du code du contrôleur travaille uniquement avec l interface IContactManagerRepository (l application est faiblement couplée). Avantages 1. Si un jour on passe de EntityFramework à NHibernate ou Subsonic, on n aura qu une ligne à modifier dans tout le contrôleur. Concrètement, ça veut dire qu on peut démarrer un projet même si on ne sait pas encore trop quel framework d accès aux données on va utiliser. Et quand on sera fixé sur le framework qui nous convient le mieux, le fait de faire un changement en cours de route devrait avoir un impact minime (l application est plus robuste aux changements). 2. L autre intérêt, c est qu on peut utiliser le second constructeur en lui passant une «fausse» implémentation de IContactManagerRepository, ce qui sera extrêmement pratique pour réaliser des tests unitaires (le contrôleur est testable). Utilisation d une couche de «Service» Le contrôleur ne contient plus aucun code pour accéder aux données. Par contre, on y trouve encore du code lié à la validation de ces données (dans la procédure ValidateContact()). Pour les mêmes bonnes raisons qui nous ont fait sortir la gestion des données du contrôleur, on va créer une «couche de service» entre le contrôleur et la couche de «repository». Cette couche de service va contenir la couche métier et toute la couche de validation. Listing 5 Models\ContactManagerService.cs using System.Collections.Generic; using System.Text.RegularExpressions; using System.Web.Mvc; namespace ContactManager.Models

public class ContactManagerService : IContactManagerService private ModelStateDictionary _validationdictionary; private IContactManagerRepository _repository; public ContactManagerService(ModelStateDictionary validationdictionary) : this(validationdictionary, new EntityContactManagerRepository()) public ContactManagerService(ModelStateDictionary validationdictionary, IContactManagerRepository repository) _validationdictionary = validationdictionary; _repository = repository; public bool ValidateContact(Contact contacttovalidate) if (contacttovalidate.firstname.trim().length == 0) _validationdictionary.addmodelerror("firstname", "First name is required."); if (contacttovalidate.lastname.trim().length == 0) _validationdictionary.addmodelerror("lastname", "Last name is required."); if (contacttovalidate.phone.length > 0 &&!Regex.IsMatch(contactToValidate.Phone, @"((\(\d3\)?) (\d3-))?\d3-\d4")) _validationdictionary.addmodelerror("phone", "Invalid phone number."); if (contacttovalidate.email.length > 0 &&!Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]2,4$")) _validationdictionary.addmodelerror("email", "Invalid email address."); return _validationdictionary.isvalid; #region IContactManagerService Members public bool CreateContact(Contact contacttocreate) // Validation logic if (!ValidateContact(contactToCreate)) return false; // Database logic _repository.createcontact(contacttocreate); return false; return true; public bool EditContact(Contact contacttoedit) // Validation logic if (!ValidateContact(contactToEdit)) return false; // Database logic _repository.editcontact(contacttoedit);

return false; return true; public bool DeleteContact(Contact contacttodelete) _repository.deletecontact(contacttodelete); return false; return true; public Contact GetContact(int id) return _repository.getcontact(id); public IEnumerable<Contact> ListContacts() return _repository.listcontacts(); #endregion Le constructeur de la classe ContactManagerService attend un paramètre de type ModelStateDictionary qui va servir pour échanger des informations entre le contrôleur et le service. Par ailleurs, tout comme dans le cas du Repository, la classe ContactManagerService va en fait implémenter l interface IContactManagerService. Toute l application de «Gestion de Contacts» travaillera en priorité avec l interface IContactManagerService pour que l application soit faiblement couplée. Listing 6 Models\IContactManagerService.cs using System.Collections.Generic; namespace ContactManager.Models public interface IContactManagerService bool CreateContact(Contact contacttocreate); bool DeleteContact(Contact contacttodelete); bool EditContact(Contact contacttoedit); Contact GetContact(int id); IEnumerable ListContacts();

Il nous reste donc à modifier une nouvelle fois le code source du contrôleur pour que d une part il ne contienne plus de code lié à la validation des données et que d autre part il n ai plus de lien direct avec le Repository mais laisse le Service interagir avec celui-ci. Listing 7 Controllers\ContactController.cs using System.Web.Mvc; using ContactManager.Models; namespace ContactManager.Controllers public class ContactController : Controller private IContactManagerService _service; public ContactController() _service = new ContactManagerService(this.ModelState); public ContactController(IContactManagerService service) _service = service; public ActionResult Index() return View(_service.ListContacts()); public ActionResult Create() public ActionResult Create([Bind(Exclude = "Id")] Contact contacttocreate) if (_service.createcontact(contacttocreate)) public ActionResult Edit(int id) return View(_service.GetContact(id)); public ActionResult Edit(Contact contacttoedit) if (_service.editcontact(contacttoedit)) public ActionResult Delete(int id) return View(_service.GetContact(id));

public ActionResult Delete(Contact contacttodelete) if (_service.deletecontact(contacttodelete)) L application suit désormais le modèle SRP (Single Responsibility Principe), c'est-à-dire que chaque «morceau» du code est chargé de faire une seule chose : Le contrôleur n a plus qu un rôle de contrôle du flux de l application. La couche de validation a été déportée dans une couche «Service». Toute la couche d accès aux données à été déportée dans la couche «Repository». Utilisation du modèle «Décoration» Notre objectif d avoir une classe par responsabilité est donc bien atteint. Il nous reste malgré tout un petit souci : pour faire passer les messages d erreurs de la couche service au contrôleur, on utilise des objets qui dépendent de ASP.NET MVC (un objet ModelStateDictionary en l occurrence). Cela posera problème le jour où on aura besoin de mettre à jour des contacts depuis autre chose qu une application ASP.NET MVC (si on voulait faire une moulinette en mode console par exemple). Il faut donc trouver un moyen pour casser cette dépendance. Pour cela, on utilise le modèle de conception «Décoration». Cela consiste simplement à encapsuler une classe existante dans une nouvelle classe, ce qui dans le cas présent nous permet alors d implémenter cette nouvelle classe en tant qu interface. On va donc encapsuler la classe ModelStateDictionary dans une classe ModelStateWrapper qui implémente l interface IValidationDictionary. Listing 8 Models\Validation\ModelStateWrapper.cs using System.Web.Mvc; namespace ContactManager.Models.Validation public class ModelStateWrapper : IValidationDictionary private ModelStateDictionary _modelstate; public ModelStateWrapper(ModelStateDictionary modelstate) _modelstate = modelstate; public void AddError(string key, string errormessage)

_modelstate.addmodelerror(key, errormessage); public bool IsValid get return _modelstate.isvalid; Listing 9 Models\Validation\IValidationDictionary.cs namespace ContactManager.Models.Validation public interface IValidationDictionary void AddError(string key, string errormessage); bool IsValid get; Il faut ensuite modifier le code de la classe ContactManagerService pour ne plus utiliser d objet ModelStateDictionary mais travailler avec l interface IValidationDictionary à la place. Listing 10 Models\ContactManagerService.cs using System.Collections.Generic; using System.Text.RegularExpressions; using System.Web.Mvc; using ContactManager.Models.Validation; namespace ContactManager.Models public class ContactManagerService : IContactManagerService private IValidationDictionary _validationdictionary; private IContactManagerRepository _repository; public ContactManagerService(IValidationDictionary validationdictionary) : this(validationdictionary, new EntityContactManagerRepository()) public ContactManagerService(IValidationDictionary validationdictionary, IContactManagerRepository repository) _validationdictionary = validationdictionary; _repository = repository; public bool ValidateContact(Contact contacttovalidate) if (contacttovalidate.firstname.trim().length == 0) _validationdictionary.adderror("firstname", "First name is required."); if (contacttovalidate.lastname.trim().length == 0) _validationdictionary.adderror("lastname", "Last name is required."); if (contacttovalidate.phone.length > 0 &&!Regex.IsMatch(contactToValidate.Phone, @"((\(\d3\)?) (\d3-))?\d3-\d4")) _validationdictionary.adderror("phone", "Invalid phone number.");

if (contacttovalidate.email.length > 0 &&!Regex.IsMatch(contactToValidate.Email, @"^[\w-\.]+@([\w-]+\.)+[\w-]2,4$")) _validationdictionary.adderror("email", "Invalid email address."); return _validationdictionary.isvalid; etc... Par conséquent, lorsque la classe contrôleur créé la couche de service, elle ne doit plus utiliser directement un objet ModelStateDictionary mais l encapsuler dans une classe ModelStateWrapper : _service = new ContactManagerService(this.ModelState); _service = new ContactManagerService(new ModelStateWrapper(this.ModelState)); Et il n y a pas d autres modifications à apporter : le framework ASP.NET MVC continuera à utiliser l objet ModelState pour afficher les erreurs via les helpers Html.ValidationXxxxxx. Récapitulatif Le but de cette étape était de faire du «refactoring» (refonte) sur l application pour la rendre plus facilement modifiable et maintenable. On est partit d un contrôleur qui s occupait de tout et on l a réorganisé morceau par morceau. Dans un premier temps nous avons implémenté le modèle de développement «Repository» et migré ainsi le code d accès aux données dans une classe séparée. Ensuite nous avons isolé la partie validation et logique dans une couche de «Service» : La couche contrôleur interagit avec la couche de service La couche de service interagit avec la couche «Repository» Parallèlement, on a utilisé le modèle de programmation «Injection de dépendance» qui nous permet de manipuler des interfaces plutôt que des classes. Et pour finir, nous avons modifié la couche de service pour tirer avantage du modèle de «Décoration» afin d isoler ModelState de la couche de service.