Sdílet prostřednictvím


Ověřování vrstvou služby (VB)

Stephen Walther

Zjistěte, jak přesunout logiku ověřování z akcí kontroleru do samostatné vrstvy služby. V tomto kurzu Stephen Walther vysvětluje, jak můžete zachovat ostré oddělení problémů tím, že izolujete vrstvu služeb od vrstvy kontroleru.

Cílem tohoto kurzu je popsat jednu metodu ověřování v aplikaci ASP.NET MVC. V tomto kurzu se naučíte přesunout logiku ověřování z kontrolerů do samostatné vrstvy služby.

Oddělení obav

Když vytváříte aplikaci ASP.NET MVC, neměli byste logiku databáze umisťovat do akcí kontroleru. Kombinování logiky databáze a kontroleru ztěžuje údržbu vaší aplikace v průběhu času. Doporučujeme umístit veškerou logiku databáze do samostatné vrstvy úložiště.

Například výpis 1 obsahuje jednoduché úložiště s názvem ProductRepository. Úložiště produktů obsahuje veškerý kód pro přístup k datům aplikace. Výpis obsahuje také rozhraní IProductRepository, které implementuje úložiště produktů.

Výpis 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 ve výpisu 2 používá vrstvu úložiště v akcích Index() i Create(). Všimněte si, že tento kontroler neobsahuje žádnou logiku databáze. Vytvoření vrstvy úložiště vám umožní udržovat čisté oddělení oblastí zájmu. Kontrolery zodpovídají za logiku řízení toku aplikace a úložiště zodpovídá za logiku přístupu k datům.

Výpis 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

Vytvoření vrstvy služby

Logika řízení toku aplikace tedy patří do kontroleru a logika přístupu k datům patří do úložiště. Kam v takovém případě umístíte logiku ověřování? Jednou z možností je umístit logiku ověřování do vrstvy služby.

Vrstva služby je další vrstva v aplikaci ASP.NET MVC, která zprostředkováává komunikaci mezi kontrolerem a vrstvou úložiště. Vrstva služby obsahuje obchodní logiku. Konkrétně obsahuje logiku ověřování.

Například vrstva služeb produktů ve výpisu 3 má metodu CreateProduct(). Metoda CreateProduct() volá metodu ValidateProduct() k ověření nového produktu před předáním produktu do úložiště produktů.

Výpis 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 byl ve výpisu 4 aktualizován tak, aby místo vrstvy úložiště používal vrstvu služby. Vrstva kontroleru komunikuje s vrstvou služby. Vrstva služby komunikuje s vrstvou úložiště. Každá vrstva má samostatnou odpovědnost.

Výpis 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

Všimněte si, že služba produktu je vytvořená v konstruktoru kontroleru produktů. Po vytvoření služby produktu se službě předá slovník stavu modelu. Produktová služba používá stav modelu k předání chybových zpráv ověření zpět kontroleru.

Oddělení vrstvy služby

Nepodařilo se nám izolovat vrstvy kontroleru a služby v jednom ohledu. Vrstva kontroleru a služby komunikují prostřednictvím stavu modelu. Jinými slovy, vrstva služby závisí na konkrétní funkci architektury ASP.NET MVC.

Chceme co nejvíce izolovat vrstvu služby od vrstvy kontroleru. Teoreticky bychom měli být schopni použít vrstvu služby s libovolným typem aplikace, a ne jen s aplikací ASP.NET MVC. V budoucnu například můžeme chtít pro naši aplikaci vytvořit front-end WPF. Měli bychom najít způsob, jak odebrat závislost na ASP.NET stavu modelu MVC z naší vrstvy služby.

Ve výpisu 5 se vrstva služby aktualizovala tak, že už nepoužívá stav modelu. Místo toho používá jakoukoli třídu, která implementuje rozhraní IValidationDictionary.

Výpis 5 – Models\ProductService.vb (oddělený)

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

Rozhraní IValidationDictionary je definováno ve výpisu 6. Toto jednoduché rozhraní má jednu metodu a jednu vlastnost.

Výpis 6 – Models\IValidationDictionary.cs

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

Třída ve výpisu 7 s názvem ModelStateWrapper třída implementuje rozhraní IValidationDictionary. Instanci třídy ModelStateWrapper můžete vytvořit předáním slovníku stavu modelu konstruktoru.

Výpis 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

Aktualizovaný kontroler ve výpisu 8 používá ModelStateWrapper při vytváření vrstvy služby ve svém konstruktoru.

Výpis 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

Použití rozhraní IValidationDictionary a ModelStateWrapper třídy nám umožňuje zcela izolovat vrstvu služby od vrstvy kontroleru. Vrstva služby už není závislá na stavu modelu. Do vrstvy služby můžete předat jakoukoli třídu, která implementuje rozhraní IValidationDictionary. Například aplikace WPF může implementovat IValidationDictionary rozhraní s jednoduchou kolekcí třídy.

Souhrn

Cílem tohoto kurzu bylo prodiskutovat jeden přístup k ověřování v aplikaci ASP.NET MVC. V tomto kurzu jste zjistili, jak přesunout veškerou logiku ověřování z kontrolerů do samostatné vrstvy služby. Také jste zjistili, jak izolovat vrstvu služby od vrstvy kontroleru vytvořením třídy ModelStateWrapper.