Partager via


Validation avec une couche de service (VB)

par Stephen Walther

Découvrez comment déplacer votre logique de validation en dehors des actions de votre contrôleur et dans une couche de service distincte. Dans ce tutoriel, Stephen Walther explique comment maintenir une séparation nette des problèmes en isolant votre couche de service de votre couche de contrôleur.

L’objectif de ce tutoriel est de décrire une méthode d’exécution de la validation dans une application MVC ASP.NET. Dans ce tutoriel, vous allez apprendre à déplacer votre logique de validation en dehors de vos contrôleurs et dans une couche de service distincte.

Séparation des préoccupations

Lorsque vous générez une application MVC ASP.NET, vous ne devez pas placer votre logique de base de données à l’intérieur de vos actions de contrôleur. La combinaison de votre base de données et de la logique du contrôleur rend votre application plus difficile à gérer au fil du temps. Il est recommandé de placer toute votre logique de base de données dans une couche de dépôt distincte.

Par exemple, listing 1 contient un dépôt simple nommé ProductRepository. Le référentiel de produits contient tout le code d’accès aux données de l’application. La liste inclut également l’interface IProductRepository que le référentiel de produits implémente.

Listing 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

Le contrôleur de la liste 2 utilise la couche de dépôt dans ses actions Index() et Create(). Notez que ce contrôleur ne contient aucune logique de base de données. La création d’une couche de dépôt vous permet de maintenir une propre séparation des préoccupations. Les contrôleurs sont responsables de la logique de contrôle de flux d’application et le référentiel est responsable de la logique d’accès aux données.

Listing 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

Création d’une couche de service

Ainsi, la logique de contrôle de flux d’application appartient à un contrôleur et la logique d’accès aux données appartient à un dépôt. Dans ce cas, où placez-vous votre logique de validation ? Une option consiste à placer votre logique de validation dans une couche de service.

Une couche de service est une couche supplémentaire dans une application MVC ASP.NET qui sert de médiateur de communication entre un contrôleur et une couche de dépôt. La couche de service contient une logique métier. En particulier, il contient une logique de validation.

Par exemple, la couche de service produit dans listing 3 a une méthode CreateProduct(). La méthode CreateProduct() appelle la méthode ValidateProduct() pour valider un nouveau produit avant de le transmettre au dépôt de produit.

Listing 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

Le contrôleur de produit a été mis à jour dans la liste 4 pour utiliser la couche de service au lieu de la couche de dépôt. La couche contrôleur communique avec la couche de service. La couche de service communique avec la couche du référentiel. Chaque couche a une responsabilité distincte.

Listing 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

Notez que le service produit est créé dans le constructeur du contrôleur de produit. Lorsque le service produit est créé, le dictionnaire d’état du modèle est passé au service. Le service produit utilise l’état du modèle pour transmettre les messages d’erreur de validation au contrôleur.

Découplage de la couche de service

Nous n’avons pas réussi à isoler les couches de contrôleur et de service à un seul égard. Les couches de contrôleur et de service communiquent via l’état du modèle. En d’autres termes, la couche de service dépend d’une fonctionnalité particulière de l’infrastructure MVC ASP.NET.

Nous voulons isoler autant que possible la couche de service de notre couche de contrôleur. En théorie, nous devrions être en mesure d’utiliser la couche de service avec n’importe quel type d’application et pas seulement une application MVC ASP.NET. Par exemple, à l’avenir, nous pourrions créer un serveur frontal WPF pour notre application. Nous devons trouver un moyen de supprimer la dépendance à ASP.NET’état du modèle MVC de notre couche de service.

Dans la liste 5, la couche de service a été mise à jour afin qu’elle n’utilise plus l’état du modèle. Au lieu de cela, il utilise n’importe quelle classe qui implémente l’interface IValidationDictionary.

Listing 5 - Models\ProductService.vb (découplé)

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

L’interface IValidationDictionary est définie dans listing 6. Cette interface simple a une seule méthode et une seule propriété.

Listing 6 - Models\IValidationDictionary.cs

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

La classe de listing 7, nommée classe ModelStateWrapper, implémente l’interface IValidationDictionary. Vous pouvez instancier la classe ModelStateWrapper en passant un dictionnaire d’état de modèle au constructeur.

Listing 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

Enfin, le contrôleur mis à jour dans listing 8 utilise modelStateWrapper lors de la création de la couche de service dans son constructeur.

Listing 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

L’utilisation de l’interface IValidationDictionary et de la classe ModelStateWrapper nous permet d’isoler complètement notre couche de service de notre couche de contrôleur. La couche de service ne dépend plus de l’état du modèle. Vous pouvez passer n’importe quelle classe qui implémente l’interface IValidationDictionary à la couche de service. Par exemple, une application WPF peut implémenter l’interface IValidationDictionary avec une classe de collection simple.

Résumé

L’objectif de ce tutoriel était de discuter d’une approche pour effectuer la validation dans une application MVC ASP.NET. Dans ce tutoriel, vous avez appris à déplacer toute votre logique de validation à partir de vos contrôleurs et dans une couche de service distincte. Vous avez également appris à isoler votre couche de service de votre couche de contrôleur en créant une classe ModelStateWrapper.