Freigeben über


Iteration 4 – Loses Koppeln der Anwendung (C#)

von Microsoft

Code herunterladen

In dieser vierten Iteration nutzen wir mehrere Softwareentwurfsmuster, um die Verwaltung und Änderung der Contact Manager-Anwendung zu vereinfachen. Beispielsweise umgestalten wir unsere Anwendung so, dass sie das Repositorymuster und das Dependency Injection-Muster verwendet.

Erstellen einer Kontaktverwaltung ASP.NET MVC-Anwendung (C#)

In dieser Reihe von Tutorials erstellen wir eine gesamte Kontaktverwaltungsanwendung von Anfang bis Ende. Mit der Contact Manager-Anwendung können Sie Kontaktinformationen – Namen, Telefonnummern und E-Mail-Adressen – für eine Personenliste speichern.

Wir erstellen die Anwendung über mehrere Iterationen. Mit jeder Iteration verbessern wir die Anwendung schrittweise. Das Ziel dieses Mehrfachiterationsansatzes besteht darin, ihnen zu ermöglichen, den Grund für jede Änderung zu verstehen.

  • Iteration Nr. 1: Erstellen Sie die Anwendung. In der ersten Iteration erstellen wir den Contact Manager auf die einfachste Weise. Wir fügen Unterstützung für grundlegende Datenbankvorgänge hinzu: Erstellen, Lesen, Aktualisieren und Löschen (CRUD).

  • Iteration Nr. 2: Sorgen Sie dafür, dass die Anwendung gut aussieht. In dieser Iteration verbessern wir das Erscheinungsbild der Anwendung, indem wir die Standardansicht ASP.NET MVC master Seite und cascading Stylesheet ändern.

  • Iteration Nr. 3: Hinzufügen der Formularvalidierung In der dritten Iteration fügen wir eine grundlegende Formularüberprüfung hinzu. Wir verhindern, dass Personen ein Formular einreichen, ohne die erforderlichen Formularfelder ausfüllen zu müssen. Außerdem überprüfen wir E-Mail-Adressen und Telefonnummern.

  • Iteration Nr. 4: Machen Sie die Anwendung lose gekoppelt. In dieser vierten Iteration nutzen wir mehrere Softwareentwurfsmuster, um die Verwaltung und Änderung der Contact Manager-Anwendung zu vereinfachen. Beispielsweise umgestalten wir unsere Anwendung so, dass sie das Repositorymuster und das Dependency Injection-Muster verwendet.

  • Iteration 5: Erstellen von Komponententests. In der fünften Iteration vereinfachen wir die Wartung und Änderung unserer Anwendung, indem wir Komponententests hinzufügen. Wir simulieren unsere Datenmodellklassen und erstellen Komponententests für unsere Controller und die Validierungslogik.

  • Iteration #6: Verwenden sie die testgesteuerte Entwicklung. In dieser sechsten Iteration fügen wir unserer Anwendung neue Funktionen hinzu, indem wir zuerst Komponententests schreiben und Code für die Komponententests schreiben. In dieser Iteration fügen wir Kontaktgruppen hinzu.

  • Iteration #7: Hinzufügen von Ajax-Funktionen. In der siebten Iteration verbessern wir die Reaktionsfähigkeit und Leistung unserer Anwendung, indem wir Ajax-Unterstützung hinzufügen.

Diese Iteration

In dieser vierten Iteration der Contact Manager-Anwendung wird die Anwendung umgestalten, um die Anwendung lockerer gekoppelt zu machen. Wenn eine Anwendung lose gekoppelt ist, können Sie den Code in einem Teil der Anwendung ändern, ohne den Code in anderen Teilen der Anwendung ändern zu müssen. Lose gekoppelte Anwendungen sind resilienter gegenüber Änderungen.

Derzeit ist die gesamte Datenzugriffs- und Validierungslogik, die von der Contact Manager-Anwendung verwendet wird, in den Controllerklassen enthalten. Das ist eine schlechte Idee. Wenn Sie einen Teil Ihrer Anwendung ändern müssen, riskieren Sie, dass Fehler in einen anderen Teil Ihrer Anwendung eingeführt werden. Wenn Sie z. B. Ihre Validierungslogik ändern, riskieren Sie neue Fehler in Ihre Datenzugriffs- oder Controllerlogik.

Hinweis

(SRP), eine Klasse sollte niemals mehr als einen Grund haben, sich zu ändern. Das Kombinieren von Controller, Validierung und Datenbanklogik stellt einen massiven Verstoß gegen das Prinzip der einheitlichen Verantwortung dar.

Es gibt mehrere Gründe, warum Sie Ihre Anwendung möglicherweise ändern müssen. Möglicherweise müssen Sie Ihrer Anwendung ein neues Feature hinzufügen, möglicherweise müssen Sie einen Fehler in Ihrer Anwendung beheben oder die Implementierung eines Features Ihrer Anwendung ändern. Anwendungen sind selten statisch. Sie neigen dazu, im Laufe der Zeit zu wachsen und zu mutieren.

Stellen Sie sich beispielsweise vor, Sie entscheiden sich dafür, die Implementierung ihrer Datenzugriffsebene zu ändern. Im Moment verwendet die Contact Manager-Anwendung das Microsoft Entity Framework für den Zugriff auf die Datenbank. Sie können sich jedoch für die Migration zu einer neuen oder alternativen Datenzugriffstechnologie wie ADO.NET Data Services oder NHibernate entscheiden. Da der Datenzugriffscode jedoch nicht vom Validierungs- und Controllercode isoliert ist, gibt es keine Möglichkeit, den Datenzugriffscode in Ihrer Anwendung zu ändern, ohne anderen Code zu ändern, der nicht direkt mit dem Datenzugriff verknüpft ist.

Wenn eine Anwendung lose gekoppelt ist, können Sie dagegen Änderungen an einem Teil einer Anwendung vornehmen, ohne andere Teile einer Anwendung zu berühren. Beispielsweise können Sie die Datenzugriffstechnologien wechseln, ohne Ihre Validierungs- oder Controllerlogik zu ändern.

In dieser Iteration nutzen wir mehrere Softwareentwurfsmuster, die es uns ermöglichen, unsere Contact Manager-Anwendung in eine lose gekoppelte Anwendung umzugestalten. Wenn wir fertig sind, wird der Kontakt-Manager nichts tun, was er zuvor noch nicht getan hat. In Zukunft können wir die Anwendung jedoch einfacher ändern.

Hinweis

Refactoring ist der Prozess, bei dem eine Anwendung so umgeschrieben wird, dass keine vorhandene Funktionalität verloren geht.

Verwenden des Repository-Softwareentwurfsmusters

Unsere erste Änderung besteht darin, ein Softwareentwurfsmuster zu nutzen, das als Repositorymuster bezeichnet wird. Wir verwenden das Repositorymuster, um unseren Datenzugriffscode vom Rest unserer Anwendung zu isolieren.

Um das Repositorymuster zu implementieren, müssen wir die folgenden zwei Schritte ausführen:

  1. Erstellen einer Schnittstelle
  2. Erstellen einer konkreten Klasse, die die -Schnittstelle implementiert

Zunächst müssen wir eine Schnittstelle erstellen, die alle Datenzugriffsmethoden beschreibt, die wir ausführen müssen. Die IContactManagerRepository-Schnittstelle ist in Listing 1 enthalten. Diese Schnittstelle beschreibt fünf Methoden: CreateContact(), DeleteContact(), EditContact(), GetContact und ListContacts().

Auflistung 1: Models\IContactManagerRepository.cs

using System;
using System.Collections.Generic;

namespace ContactManager.Models
{
    public interface IContactRepository
    {
        Contact CreateContact(Contact contactToCreate);
        void DeleteContact(Contact contactToDelete);
        Contact EditContact(Contact contactToUpdate);
        Contact GetContact(int id);
        IEnumerable<Contact> ListContacts();

    }
}

Als Nächstes müssen wir eine konkrete Klasse erstellen, die die IContactManagerRepository-Schnittstelle implementiert. Da wir das Microsoft Entity Framework für den Zugriff auf die Datenbank verwenden, erstellen wir eine neue Klasse mit dem Namen EntityContactManagerRepository. Diese Klasse ist in Listing 2 enthalten.

Auflistung 2: 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 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();
        }

    }
}

Beachten Sie, dass die EntityContactManagerRepository-Klasse die IContactManagerRepository-Schnittstelle implementiert. Die -Klasse implementiert alle fünf Methoden, die von dieser Schnittstelle beschrieben werden.

Sie fragen sich vielleicht, warum wir uns mit einer Schnittstelle mühen müssen. Warum müssen wir sowohl eine Schnittstelle als auch eine Klasse erstellen, die sie implementiert?

Mit einer Ausnahme interagiert der Rest unserer Anwendung mit der -Schnittstelle und nicht mit der konkreten Klasse. Anstatt die Methoden aufzurufen, die von der EntityContactManagerRepository-Klasse verfügbar gemacht werden, rufen wir die Methoden auf, die von der IContactManagerRepository-Schnittstelle verfügbar gemacht werden.

Auf diese Weise können wir die Schnittstelle mit einer neuen Klasse implementieren, ohne den Rest der Anwendung ändern zu müssen. Zu einem späteren Zeitpunkt möchten wir beispielsweise eine DataServicesContactManagerRepository-Klasse implementieren, die die IContactManagerRepository-Schnittstelle implementiert. Die DataServicesContactManagerRepository-Klasse kann ADO.NET Data Services verwenden, um auf eine Datenbank anstelle von Microsoft Entity Framework zuzugreifen.

Wenn der Anwendungscode für die IContactManagerRepository-Schnittstelle anstelle der konkreten EntityContactManagerRepository-Klasse programmiert ist, können wir konkrete Klassen wechseln, ohne den Rest des Codes zu ändern. Beispielsweise können wir von der EntityContactManagerRepository-Klasse zur DataServicesContactManagerRepository-Klasse wechseln, ohne unsere Datenzugriffs- oder Validierungslogik zu ändern.

Das Programmieren mit Schnittstellen (Abstraktionen) anstelle konkreter Klassen macht unsere Anwendung resilienter gegenüber Änderungen.

Hinweis

Sie können schnell eine Schnittstelle aus einer konkreten Klasse in Visual Studio erstellen, indem Sie die Menüoption Umgestalten, Schnittstelle extrahieren auswählen. Beispielsweise können Sie zuerst die EntityContactManagerRepository-Klasse erstellen und dann die Schnittstelle extrahieren verwenden, um die IContactManagerRepository-Schnittstelle automatisch zu generieren.

Verwenden des Softwareentwurfsmusters für Abhängigkeitsinjektion

Nachdem wir nun unseren Datenzugriffscode zu einer separaten Repositoryklasse migriert haben, müssen wir unseren Kontaktcontroller so ändern, dass diese Klasse verwendet wird. Wir nutzen ein Softwareentwurfsmuster namens Dependency Injection, um die Repository-Klasse in unserem Controller zu verwenden.

Der geänderte Kontaktcontroller ist in Listing 3 enthalten.

Auflistung 3: 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, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
                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()
        {
            return View();
        } 

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
        {
            // Validation logic
            ValidateContact(contactToCreate);
            if (!ModelState.IsValid)
                return View();

            // Database logic
            try
            {
                _repository.CreateContact(contactToCreate);
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        public ActionResult Edit(int id)
        {
            return View(_repository.GetContact(id));
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(Contact contactToEdit)
        {
            // Validation logic
            ValidateContact(contactToEdit);
            if (!ModelState.IsValid)
                return View();

            // Database logic
            try
            {
                _repository.EditContact(contactToEdit);
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

        public ActionResult Delete(int id)
        {
            return View(_repository.GetContact(id));
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Delete(Contact contactToDelete)
        {
            try
            {
                _repository.DeleteContact(contactToDelete);
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

    }
}

Beachten Sie, dass der Kontaktcontroller in Listing 3 über zwei Konstruktoren verfügt. Der erste Konstruktor übergibt eine konkrete instance der IContactManagerRepository-Schnittstelle an den zweiten Konstruktor. Die Contact-Controllerklasse verwendet die Konstruktorabhängigkeitsinjektion.

Die einzige Stelle, an der die EntityContactManagerRepository-Klasse verwendet wird, ist der erste Konstruktor. Der Rest der Klasse verwendet die IContactManagerRepository-Schnittstelle anstelle der konkreten EntityContactManagerRepository-Klasse.

Dies erleichtert es, implementierungen der IContactManagerRepository-Klasse in Zukunft zu wechseln. Wenn Sie die DataServicesContactRepository-Klasse anstelle der EntityContactManagerRepository-Klasse verwenden möchten, ändern Sie einfach den ersten Konstruktor.

Die Konstruktorabhängigkeitsinjektion macht auch die Contact-Controller-Klasse sehr testbar. In Ihren Komponententests können Sie den Contact-Controller instanziieren, indem Sie eine Simuliertimplementierung der IContactManagerRepository-Klasse übergeben. Dieses Feature der Abhängigkeitsinjektion wird uns in der nächsten Iteration sehr wichtig sein, wenn wir Komponententests für die Contact Manager-Anwendung erstellen.

Hinweis

Wenn Sie die Contact-Controllerklasse vollständig von einer bestimmten Implementierung der IContactManagerRepository-Schnittstelle entkoppeln möchten, können Sie ein Framework nutzen, das Dependency Injection unterstützt, z. B. StructureMap oder das Microsoft Entity Framework (MEF). Wenn Sie ein Dependency Injection-Framework nutzen, müssen Sie niemals auf eine konkrete Klasse in Ihrem Code verweisen.

Erstellen einer Dienstebene

Möglicherweise haben Sie bemerkt, dass unsere Validierungslogik noch mit unserer Controllerlogik in der geänderten Controllerklasse in Listing 3 vermischt ist. Aus demselben Grund, aus dem es eine gute Idee ist, unsere Datenzugriffslogik zu isolieren, empfiehlt es sich, unsere Validierungslogik zu isolieren.

Um dieses Problem zu beheben, können wir eine separate Dienstebene erstellen. Die Dienstebene ist eine separate Ebene, die wir zwischen unseren Controller- und Repositoryklassen einfügen können. Die Dienstebene enthält unsere Geschäftslogik, einschließlich der gesamten Validierungslogik.

Der ContactManagerService ist in Listing 4 enthalten. Sie enthält die Validierungslogik der Contact-Controllerklasse.

Auflistung 4: 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, @"((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
                _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;
        }

        #region IContactManagerService Members

        public bool CreateContact(Contact contactToCreate)
        {
            // Validation logic
            if (!ValidateContact(contactToCreate))
                return false;

            // Database logic
            try
            {
                _repository.CreateContact(contactToCreate);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public bool EditContact(Contact contactToEdit)
        {
            // Validation logic
            if (!ValidateContact(contactToEdit))
                return false;

            // Database logic
            try
            {
                _repository.EditContact(contactToEdit);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public bool DeleteContact(Contact contactToDelete)
        {
            try
            {
                _repository.DeleteContact(contactToDelete);
            }
            catch
            {
                return false;
            }
            return true;
        }

        public Contact GetContact(int id)
        {
            return _repository.GetContact(id);
        }

        public IEnumerable<Contact> ListContacts()
        {
            return _repository.ListContacts();
        }

        #endregion
    }
}

Beachten Sie, dass der Konstruktor für den ContactManagerService ein ValidationDictionary erfordert. Die Dienstebene kommuniziert mit der Controllerebene über dieses ValidationDictionary. Im folgenden Abschnitt wird das ValidationDictionary ausführlich erläutert, wenn wir das Decorator-Muster erläutern.

Beachten Sie außerdem, dass contactManagerService die IContactManagerService-Schnittstelle implementiert. Sie sollten immer versuchen, anstelle von konkreten Klassen für Schnittstellen zu programmieren. Andere Klassen in der Contact Manager-Anwendung interagieren nicht direkt mit der ContactManagerService-Klasse. Stattdessen wird der Rest der Contact Manager-Anwendung mit einer Ausnahme für die IContactManagerService-Schnittstelle programmiert.

Die IContactManagerService-Schnittstelle ist in Listing 5 enthalten.

Auflistung 5: 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();
    }
}

Die geänderte Contact-Controllerklasse ist in Listing 6 enthalten. Beachten Sie, dass der Kontaktcontroller nicht mehr mit dem ContactManager-Repository interagiert. Stattdessen interagiert der Kontaktcontroller mit dem ContactManager-Dienst. Jede Ebene ist so weit wie möglich von anderen Ebenen isoliert.

Auflistung 6: 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(new ModelStateWrapper(this.ModelState));

        }

        public ContactController(IContactManagerService service)
        {
            _service = service;
        }
        
        public ActionResult Index()
        {
            return View(_service.ListContacts());
        }

        public ActionResult Create()
        {
            return View();
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create([Bind(Exclude = "Id")] Contact contactToCreate)
        {
            if (_service.CreateContact(contactToCreate))
                return RedirectToAction("Index");
            return View();
        }

        public ActionResult Edit(int id)
        {
            return View(_service.GetContact(id));
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(Contact contactToEdit)
        {
            if (_service.EditContact(contactToEdit))
                return RedirectToAction("Index");
            return View();
        }

        public ActionResult Delete(int id)
        {
            return View(_service.GetContact(id));
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Delete(Contact contactToDelete)
        {
            if (_service.DeleteContact(contactToDelete))
                return RedirectToAction("Index");
            return View();
        }

    }
}

Unsere Anwendung läuft nicht mehr nach dem Single Responsibility Principle (SRP). Der Kontaktcontroller in Listing 6 wurde von jeder anderen Verantwortung als der Steuerung des Ablaufs der Anwendungsausführung befreit. Die gesamte Validierungslogik wurde aus dem Kontaktcontroller entfernt und in die Dienstebene gepusht. Die gesamte Datenbanklogik wurde in die Repositoryebene gepusht.

Verwenden des Decorator-Musters

Wir möchten in der Lage sein, unsere Serviceebene vollständig von unserer Controllerebene zu entkoppeln. Im Prinzip sollten wir in der Lage sein, unsere Dienstebene in einer separaten Assembly von unserer Controllerebene zu kompilieren, ohne einen Verweis auf unsere MVC-Anwendung hinzufügen zu müssen.

Unsere Dienstebene muss jedoch in der Lage sein, Validierungsfehlermeldungen an die Controllerebene zurückzugeben. Wie können wir es der Dienstebene ermöglichen, Validierungsfehlermeldungen zu kommunizieren, ohne den Controller und die Dienstebene zu koppeln? Wir können ein Softwareentwurfsmuster mit dem Namen Decorator-Muster nutzen.

Ein Controller verwendet ein ModelStateDictionary mit dem Namen ModelState, um Validierungsfehler darzustellen. Daher könnten Sie versucht sein, ModelState von der Controllerebene an die Dienstebene zu übergeben. Durch die Verwendung von ModelState in der Dienstebene ist Ihre Dienstebene jedoch von einem Feature des ASP.NET MVC-Frameworks abhängig. Dies wäre schlecht, da Sie eines Tages die Dienstebene mit einer WPF-Anwendung anstelle einer ASP.NET MVC-Anwendung verwenden möchten. In diesem Fall möchten Sie nicht auf das ASP.NET MVC-Framework verweisen, um die ModelStateDictionary-Klasse zu verwenden.

Mit dem Decorator-Muster können Sie eine vorhandene Klasse in eine neue Klasse umschließen, um eine Schnittstelle zu implementieren. Unser Contact Manager-Projekt enthält die ModelStateWrapper-Klasse, die in Listing 7 enthalten ist. Die ModelStateWrapper-Klasse implementiert die Schnittstelle in Listing 8.

Auflistung 7: 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; }
        }
    }
}

Auflistung 8: Models\Validation\IValidationDictionary.cs

namespace ContactManager.Models.Validation
{
    public interface IValidationDictionary
    {
        void AddError(string key, string errorMessage);
        bool IsValid {get;}
    }
}

Wenn Sie sich Listing 5 genauer ansehen, sehen Sie, dass die ContactManager-Dienstebene ausschließlich die IValidationDictionary-Schnittstelle verwendet. Der ContactManager-Dienst ist nicht von der ModelStateDictionary-Klasse abhängig. Wenn der Kontaktcontroller den ContactManager-Dienst erstellt, umschließt der Controller seinen ModelState wie folgt:

_service = new ContactManagerService(new ModelStateWrapper(this.ModelState));

Zusammenfassung

In dieser Iteration haben wir der Contact Manager-Anwendung keine neuen Funktionen hinzugefügt. Das Ziel dieser Iteration war es, die Contact Manager-Anwendung so umzugestalten, dass einfacher zu verwalten und zu ändern ist.

Zuerst haben wir das Entwurfsmuster der Repositorysoftware implementiert. Wir haben den gesamten Datenzugriffscode zu einer separaten ContactManager-Repositoryklasse migriert.

Außerdem haben wir unsere Validierungslogik von unserer Controllerlogik isoliert. Wir haben eine separate Dienstebene erstellt, die den gesamten Validierungscode enthält. Die Controllerebene interagiert mit der Dienstebene, und die Dienstebene interagiert mit der Repositoryebene.

Bei der Erstellung der Dienstebene haben wir das Decorator-Muster genutzt, um ModelState von unserer Dienstebene zu isolieren. In unserer Dienstebene haben wir für die IValidationDictionary-Schnittstelle anstelle von ModelState programmiert.

Schließlich haben wir ein Softwareentwurfsmuster mit dem Namen Dependency Injection-Muster genutzt. Dieses Muster ermöglicht es uns, für Schnittstellen (Abstraktionen) anstelle von konkreten Klassen zu programmieren. Durch die Implementierung des Dependency Injection-Entwurfsmusters ist der Code auch besser testbar. In der nächsten Iteration fügen wir dem Projekt Komponententests hinzu.