Walidacja z użyciem warstwy usług (VB)

Autor: Stephen Walther

Dowiedz się, jak przenieść logikę weryfikacji z akcji kontrolera i do oddzielnej warstwy usługi. W tym samouczku Stephen Walther wyjaśnia, jak można zachować ostrą separację problemów przez odizolowanie warstwy usługi od warstwy kontrolera.

Celem tego samouczka jest opisanie jednej metody weryfikacji w aplikacji ASP.NET MVC. Z tego samouczka dowiesz się, jak przenieść logikę weryfikacji z kontrolerów i do oddzielnej warstwy usługi.

Oddzielanie obaw

Podczas tworzenia aplikacji ASP.NET MVC nie należy umieszczać logiki bazy danych wewnątrz akcji kontrolera. Mieszanie logiki bazy danych i kontrolera sprawia, że aplikacja jest trudniejsza do utrzymania w czasie. Zaleceniem jest umieszczenie całej logiki bazy danych w oddzielnej warstwie repozytorium.

Na przykład lista 1 zawiera proste repozytorium o nazwie ProductRepository. Repozytorium produktów zawiera cały kod dostępu do danych dla aplikacji. Lista zawiera również interfejs IProductRepository implementujący repozytorium produktów.

Lista 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

Kontroler w liście 2 używa warstwy repozytorium w akcjach Index() i Create(). Zwróć uwagę, że ten kontroler nie zawiera żadnej logiki bazy danych. Tworzenie warstwy repozytorium umożliwia zachowanie czystej separacji problemów. Kontrolery są odpowiedzialne za logikę sterowania przepływem aplikacji, a repozytorium jest odpowiedzialne za logikę dostępu do danych.

Lista 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

Tworzenie warstwy usługi

Dlatego logika sterowania przepływem aplikacji należy do kontrolera, a logika dostępu do danych należy do repozytorium. W takim przypadku, gdzie umieścisz logikę weryfikacji? Jedną z opcji jest umieszczenie logiki walidacji w warstwie usługi.

Warstwa usługi to dodatkowa warstwa w aplikacji ASP.NET MVC, która mediatuje komunikację między kontrolerem a warstwą repozytorium. Warstwa usługi zawiera logikę biznesową. W szczególności zawiera logikę walidacji.

Na przykład warstwa usługi produktu w pozycji Listing 3 ma metodę CreateProduct(). Metoda CreateProduct() wywołuje metodę ValidateProduct(), aby zweryfikować nowy produkt przed przekazaniem produktu do repozytorium produktów.

Lista 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

Kontroler produktu został zaktualizowany na liście 4, aby użyć warstwy usługi zamiast warstwy repozytorium. Warstwa kontrolera komunikuje się z warstwą usługi. Warstwa usługi komunikuje się z warstwą repozytorium. Każda warstwa ma osobną odpowiedzialność.

Lista 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

Zwróć uwagę, że usługa produktu jest tworzona w konstruktorze kontrolera produktu. Po utworzeniu usługi produktu słownik stanu modelu jest przekazywany do usługi. Usługa produktu używa stanu modelu do przekazywania komunikatów o błędach weryfikacji z powrotem do kontrolera.

Oddzielenie warstwy usługi

Nie udało nam się odizolować warstw kontrolera i usługi w jednym zakresie. Warstwy kontrolera i usługi komunikują się za pośrednictwem stanu modelu. Innymi słowy warstwa usługi ma zależność od określonej funkcji platformy ASP.NET MVC.

Chcemy jak najwięcej odizolować warstwę usługi od warstwy kontrolera. Teoretycznie powinniśmy mieć możliwość używania warstwy usługi z dowolnym typem aplikacji, a nie tylko aplikacją ASP.NET MVC. Na przykład w przyszłości możemy chcieć utworzyć fronton WPF dla naszej aplikacji. Powinniśmy znaleźć sposób usunięcia zależności od ASP.NET stanu modelu MVC z naszej warstwy usługi.

Na liście 5 warstwa usługi została zaktualizowana, aby nie używała już stanu modelu. Zamiast tego używa dowolnej klasy, która implementuje interfejs IValidationDictionary.

Lista 5 — Models\ProductService.vb (oddzielone)

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

Interfejs IValidationDictionary jest zdefiniowany w liście 6. Ten prosty interfejs ma jedną metodę i jedną właściwość.

Lista 6 — Models\IValidationDictionary.cs

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

Klasa w liście 7 o nazwie ModelStateWrapper klasy implementuje interfejs IValidationDictionary. Wystąpienie klasy ModelStateWrapper można utworzyć, przekazując słownik stanu modelu do konstruktora.

Lista 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

Na koniec zaktualizowany kontroler na liście 8 używa klasy ModelStateWrapper podczas tworzenia warstwy usługi w konstruktorze.

Lista 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

Użycie interfejsu IValidationDictionary i klasy ModelStateWrapper umożliwia całkowite odizolowanie warstwy usługi od warstwy kontrolera. Warstwa usługi nie jest już zależna od stanu modelu. Możesz przekazać dowolną klasę, która implementuje interfejs IValidationDictionary w warstwie usługi. Na przykład aplikacja WPF może zaimplementować interfejs IValidationDictionary z prostą klasą kolekcji.

Podsumowanie

Celem tego samouczka było omówienie jednego podejścia do przeprowadzania walidacji w aplikacji MVC ASP.NET. W tym samouczku przedstawiono sposób przenoszenia całej logiki weryfikacji z kontrolerów i do oddzielnej warstwy usługi. Przedstawiono również sposób izolowania warstwy usługi od warstwy kontrolera przez utworzenie klasy ModelStateWrapper.