Freigeben über


Überprüfen mit einer Dienstschicht (VB)

von Stephen Walther

Erfahren Sie, wie Sie Ihre Validierungslogik aus Ihren Controlleraktionen heraus in eine separate Dienstebene verschieben. In diesem Tutorial erläutert Stephen Walther, wie Sie eine scharfe Trennung der Bedenken beibehalten können, indem Sie Ihre Dienstebene von Ihrer Controllerebene isolieren.

Das Ziel dieses Tutorials besteht darin, eine Methode zum Durchführen der Validierung in einer ASP.NET MVC-Anwendung zu beschreiben. In diesem Tutorial erfahren Sie, wie Sie Ihre Validierungslogik aus Ihren Controllern in eine separate Dienstebene verschieben.

Trennen von Bedenken

Wenn Sie eine ASP.NET MVC-Anwendung erstellen, sollten Sie die Datenbanklogik nicht in Ihren Controlleraktionen platzieren. Das Mischen Ihrer Datenbank- und Controllerlogik erschwert die Verwaltung Ihrer Anwendung im Laufe der Zeit. Es wird empfohlen, die gesamte Datenbanklogik in einer separaten Repositoryebene zu platzieren.

Beispielsweise enthält Listing 1 ein einfaches Repository namens ProductRepository. Das Produktrepository enthält den gesamten Datenzugriffscode für die Anwendung. Die Auflistung enthält auch die IProductRepository-Schnittstelle, die vom Produktrepository implementiert wird.

Auflistung 1: Models\ProductRepository.vb

Public Class ProductRepository
Implements IProductRepository

    Private _entities As New ProductDBEntities()


Public Function ListProducts() As IEnumerable(Of Product) Implements IProductRepository.ListProducts
    Return _entities.ProductSet.ToList()
End Function


Public Function CreateProduct(ByVal productToCreate As Product) As Boolean Implements IProductRepository.CreateProduct
    Try
        _entities.AddToProductSet(productToCreate)
        _entities.SaveChanges()
        Return True
    Catch
        Return False
    End Try
End Function

End Class

Public Interface IProductRepository
Function CreateProduct(ByVal productToCreate As Product) As Boolean
Function ListProducts() As IEnumerable(Of Product)
End Interface

Der Controller in Listing 2 verwendet die Repositoryebene sowohl in den Aktionen Index() als auch create(). Beachten Sie, dass dieser Controller keine Datenbanklogik enthält. Durch das Erstellen einer Repositoryebene können Sie eine sauber Trennung von Bedenken beibehalten. Controller sind für die Logik zur Steuerung des Anwendungsflusses verantwortlich, und das Repository ist für die Datenzugriffslogik verantwortlich.

Auflistung 2: Controllers\ProductController.vb

Public Class ProductController
Inherits Controller

    Private _repository As IProductRepository

Public Sub New()
    Me.New(New ProductRepository())
End Sub


Public Sub New(ByVal repository As IProductRepository)
    _repository = repository
End Sub


Public Function Index() As ActionResult
    Return View(_repository.ListProducts())
End Function


'
' GET: /Product/Create

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

'
' POST: /Product/Create

<AcceptVerbs(HttpVerbs.Post)> _
Public Function Create(<Bind(Exclude:="Id")> ByVal productToCreate As Product) As ActionResult
    _repository.CreateProduct(productToCreate)
    Return RedirectToAction("Index")
End Function

End Class

Erstellen einer Dienstebene

Die Logik zur Steuerung des Anwendungsflusses gehört also in einen Controller, und die Datenzugriffslogik gehört in ein Repository. Wo legen Sie in diesem Fall Ihre Validierungslogik ein? Eine Möglichkeit besteht darin, Ihre Validierungslogik auf einer Dienstebene zu platzieren.

Eine Dienstebene ist eine zusätzliche Ebene in einer ASP.NET MVC-Anwendung, die die Kommunikation zwischen einem Controller und einer Repositoryebene vermittelt. Die Dienstebene enthält Geschäftslogik. Insbesondere enthält sie Validierungslogik.

Beispielsweise verfügt die Produktdienstebene in Listing 3 über eine CreateProduct()-Methode. Die CreateProduct()-Methode ruft die ValidateProduct()-Methode auf, um ein neues Produkt zu überprüfen, bevor das Produkt an das Produktrepository übergeben wird.

Auflistung 3: Models\ProductService.vb

Public Class ProductService
Implements IProductService

Private _modelState As ModelStateDictionary
Private _repository As IProductRepository

Public Sub New(ByVal modelState As ModelStateDictionary, ByVal repository As IProductRepository)
    _modelState = modelState
    _repository = repository
End Sub

Protected Function ValidateProduct(ByVal productToValidate As Product) As Boolean
    If productToValidate.Name.Trim().Length = 0 Then
        _modelState.AddModelError("Name", "Name is required.")
    End If
    If productToValidate.Description.Trim().Length = 0 Then
        _modelState.AddModelError("Description", "Description is required.")
    End If
    If productToValidate.UnitsInStock

Der Produktcontroller wurde in Listing 4 aktualisiert, um die Dienstebene anstelle der Repositoryebene zu verwenden. Die Controllerebene kommuniziert mit der Dienstebene. Die Dienstebene kommuniziert mit der Repositoryebene. Jede Ebene hat eine eigene Verantwortung.

Auflistung 4: Controllers\ProductController.vb

Public Class ProductController
Inherits Controller

Private _service As IProductService

Public Sub New()
    _service = New ProductService(Me.ModelState, New ProductRepository())
End Sub

Public Sub New(ByVal service As IProductService)
    _service = service
End Sub


Public Function Index() As ActionResult
    Return View(_service.ListProducts())
End Function


'
' GET: /Product/Create

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

'
' POST: /Product/Create

<AcceptVerbs(HttpVerbs.Post)> _
Public Function Create(<Bind(Exclude := "Id")> ByVal productToCreate As Product) As ActionResult
    If Not _service.CreateProduct(productToCreate) Then
        Return View()
    End If
    Return RedirectToAction("Index")
End Function

End Class

Beachten Sie, dass der Produktdienst im Produktcontrollerkonstruktor erstellt wird. Wenn der Produktdienst erstellt wird, wird das Modellzustandsverzeichnis an den Dienst übergeben. Der Produktdienst verwendet den Modellzustand, um Validierungsfehlermeldungen an den Controller zurückzugeben.

Entkoppeln der Dienstebene

Es ist nicht gelungen, die Controller- und Dienstebenen in einer Hinsicht zu isolieren. Die Controller- und Dienstebenen kommunizieren über den Modellzustand. Anders ausgedrückt: Die Dienstebene ist von einem bestimmten Feature des ASP.NET MVC-Frameworks abhängig.

Wir möchten die Dienstebene so weit wie möglich von unserer Controllerebene isolieren. Theoretisch sollten wir in der Lage sein, die Dienstebene mit jeder Art von Anwendung und nicht nur mit einer ASP.NET MVC-Anwendung zu verwenden. In Zukunft können wir beispielsweise ein WPF-Front-End für unsere Anwendung erstellen. Wir sollten eine Möglichkeit finden, die Abhängigkeit von ASP.NET MVC-Modellzustand aus unserer Dienstebene zu entfernen.

In Auflistung 5 wurde die Dienstebene aktualisiert, sodass sie den Modellzustand nicht mehr verwendet. Stattdessen werden alle Klassen verwendet, die die IValidationDictionary-Schnittstelle implementiert.

Auflistung 5: Models\ProductService.vb (entkoppelt)

Public Class ProductService
Implements IProductService

Private _validatonDictionary As IValidationDictionary
Private _repository As IProductRepository

Public Sub New(ByVal validationDictionary As IValidationDictionary, ByVal repository As IProductRepository)
    _validatonDictionary = validationDictionary
    _repository = repository
End Sub

Protected Function ValidateProduct(ByVal productToValidate As Product) As Boolean
    If productToValidate.Name.Trim().Length = 0 Then
        _validatonDictionary.AddError("Name", "Name is required.")
    End If
    If productToValidate.Description.Trim().Length = 0 Then
        _validatonDictionary.AddError("Description", "Description is required.")
    End If
    If productToValidate.UnitsInStock

Die IValidationDictionary-Schnittstelle ist in Listing 6 definiert. Diese einfache Schnittstelle verfügt über eine einzelne Methode und eine einzelne Eigenschaft.

Auflistung 6: Models\IValidationDictionary.cs

Public Interface IValidationDictionary
Sub AddError(ByVal key As String, ByVal errorMessage As String)
ReadOnly Property IsValid() As Boolean
End Interface

Die Klasse in Listing 7 mit dem Namen ModelStateWrapper-Klasse implementiert die IValidationDictionary-Schnittstelle. Sie können die ModelStateWrapper-Klasse instanziieren, indem Sie ein Modellzustandsverzeichnis an den Konstruktor übergeben.

Auflistung 7: Models\ModelStateWrapper.vb

Public Class ModelStateWrapper
Implements IValidationDictionary

Private _modelState As ModelStateDictionary

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

#Region "IValidationDictionary Members"

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 Region

End Class

Schließlich verwendet der aktualisierte Controller in Listing 8 den ModelStateWrapper, wenn die Dienstebene in ihrem Konstruktor erstellt wird.

Auflistung 8 : Controllers\ProductController.vb

Public Class ProductController
Inherits Controller

Private _service As IProductService

Public Sub New()
    _service = New ProductService(New ModelStateWrapper(Me.ModelState), New ProductRepository())
End Sub

Public Sub New(ByVal service As IProductService)
    _service = service
End Sub


Public Function Index() As ActionResult
    Return View(_service.ListProducts())
End Function


'
' GET: /Product/Create

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

'
' POST: /Product/Create

<AcceptVerbs(HttpVerbs.Post)> _
Public Function Create(<Bind(Exclude := "Id")> ByVal productToCreate As Product) As ActionResult
    If Not _service.CreateProduct(productToCreate) Then
        Return View()
    End If
    Return RedirectToAction("Index")
End Function

End Class

Mithilfe der IValidationDictionary-Schnittstelle und der ModelStateWrapper-Klasse können wir die Dienstebene vollständig von der Controllerebene isolieren. Die Dienstebene ist nicht mehr vom Modellstatus abhängig. Sie können jede Klasse, die die IValidationDictionary-Schnittstelle implementiert, an die Dienstebene übergeben. Beispielsweise kann eine WPF-Anwendung die IValidationDictionary-Schnittstelle mit einer einfachen Sammlungsklasse implementieren.

Zusammenfassung

Das Ziel dieses Tutorials war es, einen Ansatz zum Durchführen der Validierung in einer ASP.NET MVC-Anwendung zu diskutieren. In diesem Tutorial haben Sie gelernt, wie Sie Ihre gesamte Validierungslogik aus Ihren Controllern in eine separate Dienstebene verschieben. Außerdem haben Sie gelernt, wie Sie Ihre Dienstebene von Der Controllerebene isolieren, indem Sie eine ModelStateWrapper-Klasse erstellen.