Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
von Microsoft
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:
- Erstellen einer Schnittstelle
- 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.