Freigeben über


Iteration 4 – Loses Koppeln der Anwendung (VB)

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 Abhängigkeitsinjektionsmuster verwendet.

Erstellen einer Kontaktverwaltung ASP.NET MVC-Anwendung (VB)

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 Liste von Personen speichern.

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

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

  • Iteration #2: Lassen Sie die Anwendung schön aussehen. In dieser Iteration verbessern wir das Erscheinungsbild der Anwendung, indem wir die Standardansicht ASP.NET MVC master Seite und cascading Stylesheet ändern.

  • Iteration #3: Hinzufügen der Formularüberprüfung. In der dritten Iteration fügen wir die 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 #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 Abhängigkeitsinjektionsmuster verwendet.

  • Iteration #5: Erstellen von Komponententests. In der fünften Iteration erleichtern wir die Wartung und Änderung unserer Anwendung durch Hinzufügen von Komponententests. 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 Unterstützung für Ajax 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 widerstandsfähiger für Änderungen.

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

Hinweis

(SRP): Eine Klasse sollte nie mehr als einen Grund für eine Änderung haben. Das Mischen von Controller, Validierung und Datenbanklogik ist ein massiver Verstoß gegen das Prinzip der einheitlichen Verantwortung.

Es gibt mehrere Gründe, warum Sie Ihre Anwendung ä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, die Implementierung der Datenzugriffsebene zu ändern. Im Moment verwendet die Contact Manager-Anwendung das Microsoft Entity Framework, um auf die Datenbank zuzugreifen. Möglicherweise können Sie jedoch zu einer neuen oder alternativen Datenzugriffstechnologie wie ADO.NET Data Services oder NHibernate migrieren. 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 sich nicht direkt auf den Datenzugriff bezieht.

Wenn eine Anwendung hingegen lose gekoppelt ist, können Sie Änderungen an einem Teil einer Anwendung vornehmen, ohne andere Teile einer Anwendung zu berühren. Sie können beispielsweise 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

Beim Umgestalten wird eine Anwendung so umgeschrieben, dass keine vorhandene Funktionalität verloren geht.

Verwenden des Repository-Softwareentwurfsmusters

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

Um das Repositorymuster zu implementieren, müssen wir die folgenden beiden 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().

Eintrag 1 : Models\IContactManagerRepository.vb

Public Interface IContactManagerRepository
Function CreateContact(ByVal contactToCreate As Contact) As Contact
Sub DeleteContact(ByVal contactToDelete As Contact)
Function EditContact(ByVal contactToUpdate As Contact) As Contact
Function GetContact(ByVal id As Integer) As Contact
Function ListContacts() As IEnumerable(Of Contact)
End Interface

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.

Eintrag 2: Models\EntityContactManagerRepository.vb

Public Class EntityContactManagerRepository
Implements IContactManagerRepository

Private _entities As New ContactManagerDBEntities()

Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerRepository.GetContact
    Return (From c In _entities.ContactSet _
            Where c.Id = id _
            Select c).FirstOrDefault()
End Function


Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerRepository.ListContacts
    Return _entities.ContactSet.ToList()
End Function


Public Function CreateContact(ByVal contactToCreate As Contact) As Contact Implements IContactManagerRepository.CreateContact
    _entities.AddToContactSet(contactToCreate)
    _entities.SaveChanges()
    Return contactToCreate
End Function


Public Function EditContact(ByVal contactToEdit As Contact) As Contact Implements IContactManagerRepository.EditContact
    Dim originalContact = GetContact(contactToEdit.Id)
    _entities.ApplyPropertyChanges(originalContact.EntityKey.EntitySetName, contactToEdit)
    _entities.SaveChanges()
    Return contactToEdit
End Function


Public Sub DeleteContact(ByVal contactToDelete As Contact) Implements IContactManagerRepository.DeleteContact
    Dim originalContact = GetContact(contactToDelete.Id)
    _entities.DeleteObject(originalContact)
    _entities.SaveChanges()
End Sub

End Class

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 unserer Anwendung ändern zu müssen. Beispielsweise sollten wir zu einem späteren Zeitpunkt 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 unser Anwendungscode für die IContactManagerRepository-Schnittstelle anstelle der konkreten EntityContactManagerRepository-Klasse programmiert ist, können wir konkrete Klassen wechseln, ohne den Rest unseres 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 von konkreten Klassen macht unsere Anwendung widerstandsfähiger 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 Extrahierende Schnittstelle 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 ändern, um diese Klasse zu verwenden. 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.

Eintrag 3: Controller\ContactController.vb

Public Class ContactController
    Inherits System.Web.Mvc.Controller

    Private _repository As IContactManagerRepository 

    Sub New()
        Me.New(new EntityContactManagerRepository())
    End Sub

    Sub New(repository As IContactManagerRepository)
        _repository = repository
    End Sub

    Protected Sub ValidateContact(contactToValidate As Contact)
        If contactToValidate.FirstName.Trim().Length = 0 Then
            ModelState.AddModelError("FirstName", "First name is required.")
        End If
        If contactToValidate.LastName.Trim().Length = 0 Then
            ModelState.AddModelError("LastName", "Last name is required.")
        End If
        If (contactToValidate.Phone.Length > 0 AndAlso Not Regex.IsMatch(contactToValidate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}"))
            ModelState.AddModelError("Phone", "Invalid phone number.")
        End If        
        If (contactToValidate.Email.Length > 0 AndAlso  Not Regex.IsMatch(contactToValidate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$"))
            ModelState.AddModelError("Email", "Invalid email address.")
        End If
    End Sub

    Function Index() As ActionResult
        Return View(_repository.ListContacts())
    End Function

    Function Create() As ActionResult
        Return View()
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
        ' Validation logic
        ValidateContact(contactToCreate)
        If Not ModelState.IsValid Then
            Return View()
        End If

        ' Database logic
        Try
            _repository.CreateContact(contactToCreate)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

    Function Edit(ByVal id As Integer) As ActionResult
        Return View(_repository.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Edit(ByVal contactToEdit As Contact) As ActionResult
        ' Validation logic
        ValidateContact(contactToEdit)
        If Not ModelState.IsValid Then
            Return View()
        End If

        ' Database logic
        Try
            _repository.EditContact(contactToEdit)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

    Function Delete(ByVal id As Integer) As ActionResult
        Return View(_repository.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Delete(ByVal contactToDelete As Contact) As ActionResult
        Try
            _repository.DeleteContact(contactToDelete)
            Return RedirectToAction("Index")
        Catch
            Return View()
        End Try
    End Function

End Class

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 Konstruktorabhängigkeitsinjektion.

Die einzige Stelle, an der die EntityContactManagerRepository-Klasse verwendet wird, befindet sich im ersten 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.vb

Public Class ContactManagerService
Implements IContactManagerService

Private _validationDictionary As IValidationDictionary
Private _repository As IContactManagerRepository


Public Sub New(ByVal validationDictionary As IValidationDictionary)
    Me.New(validationDictionary, New EntityContactManagerRepository())
End Sub


Public Sub New(ByVal validationDictionary As IValidationDictionary, ByVal repository As IContactManagerRepository)
    _validationDictionary = validationDictionary
    _repository = repository
End Sub


Public Function ValidateContact(ByVal contactToValidate As Contact) As Boolean
    If contactToValidate.FirstName.Trim().Length = 0 Then
        _validationDictionary.AddError("FirstName", "First name is required.")
    End If
    If contactToValidate.LastName.Trim().Length = 0 Then
        _validationDictionary.AddError("LastName", "Last name is required.")
    End If
    If contactToValidate.Phone.Length > 0 AndAlso (Not Regex.IsMatch(contactToValidate.Phone, "((\(\d{3}\) ?)|(\d{3}-))?\d{3}-\d{4}")) Then
        _validationDictionary.AddError("Phone", "Invalid phone number.")
    End If
    If contactToValidate.Email.Length > 0 AndAlso (Not Regex.IsMatch(contactToValidate.Email, "^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$")) Then
        _validationDictionary.AddError("Email", "Invalid email address.")
    End If
    Return _validationDictionary.IsValid
End Function


#Region "IContactManagerService Members"

Public Function CreateContact(ByVal contactToCreate As Contact) As Boolean Implements IContactManagerService.CreateContact
    ' Validation logic
    If Not ValidateContact(contactToCreate) Then
        Return False
    End If

    ' Database logic
    Try
        _repository.CreateContact(contactToCreate)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function EditContact(ByVal contactToEdit As Contact) As Boolean Implements IContactManagerService.EditContact
    ' Validation logic
    If Not ValidateContact(contactToEdit) Then
        Return False
    End If

    ' Database logic
    Try
        _repository.EditContact(contactToEdit)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function DeleteContact(ByVal contactToDelete As Contact) As Boolean Implements IContactManagerService.DeleteContact
    Try
        _repository.DeleteContact(contactToDelete)
    Catch
        Return False
    End Try
    Return True
End Function

Public Function GetContact(ByVal id As Integer) As Contact Implements IContactManagerService.GetContact
    Return _repository.GetContact(id)
End Function

Public Function ListContacts() As IEnumerable(Of Contact) Implements IContactManagerService.ListContacts
    Return _repository.ListContacts()
End Function

#End Region
End Class

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.vb

Public Interface IContactManagerService
Function CreateContact(ByVal contactToCreate As Contact) As Boolean
Function DeleteContact(ByVal contactToDelete As Contact) As Boolean
Function EditContact(ByVal contactToEdit As Contact) As Boolean
Function GetContact(ByVal id As Integer) As Contact
Function ListContacts() As IEnumerable(Of Contact)
End Interface

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.vb

Public Class ContactController
    Inherits System.Web.Mvc.Controller

    Private _service As IContactManagerService 

    Sub New()
        _service = new ContactManagerService(New ModelStateWrapper(ModelState))
    End Sub

    Sub New(service As IContactManagerService)
        _service = service
    End Sub

    Function Index() As ActionResult
        Return View(_service.ListContacts())
    End Function

    Function Create() As ActionResult
        Return View()
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Create(<Bind(Exclude:="Id")> ByVal contactToCreate As Contact) As ActionResult
        If _service.CreateContact(contactToCreate) Then
            Return RedirectToAction("Index")        
        End If
        Return View()
    End Function

    Function Edit(ByVal id As Integer) As ActionResult
        Return View(_service.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Edit(ByVal contactToEdit As Contact) As ActionResult
        If _service.EditContact(contactToEdit) Then
            Return RedirectToAction("Index")        
        End If
        Return View()
    End Function

    Function Delete(ByVal id As Integer) As ActionResult
        Return View(_service.GetContact(id))
    End Function

    <AcceptVerbs(HttpVerbs.Post)> _
    Function Delete(ByVal contactToDelete As Contact) As ActionResult
        If _service.DeleteContact(contactToDelete) Then
            return RedirectToAction("Index")
        End If
        Return View()
    End Function

End Class

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.vb

Public Class ModelStateWrapper
Implements IValidationDictionary

Private _modelState As ModelStateDictionary

Public Sub New(ByVal modelState As ModelStateDictionary)
    _modelState = modelState
End Sub

Public Sub AddError(ByVal key As String, ByVal errorMessage As String) Implements IValidationDictionary.AddError
    _modelState.AddModelError(key, errorMessage)
End Sub

Public ReadOnly Property IsValid() As Boolean Implements IValidationDictionary.IsValid
    Get
        Return _modelState.IsValid
    End Get
End Property

End Class

Auflistung 8: Models\Validation\IValidationDictionary.vb

Public Interface IValidationDictionary

Sub AddError(ByVal key As String, ByVal errorMessage As String)
ReadOnly Property IsValid() As Boolean

End Interface

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(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.