Validação com uma camada de serviço (VB)

por Stephen Walther

Saiba como mover sua lógica de validação para fora das ações do controlador e para uma camada de serviço separada. Neste tutorial, Stephen Walther explica como você pode manter uma separação acentuada de preocupações isolando sua camada de serviço da camada do controlador.

O objetivo deste tutorial é descrever um método de execução de validação em um aplicativo MVC ASP.NET. Neste tutorial, você aprenderá a mover sua lógica de validação para fora dos controladores e para uma camada de serviço separada.

Separando preocupações

Ao criar um aplicativo MVC ASP.NET, você não deve colocar a lógica do banco de dados dentro das ações do controlador. Misturar o banco de dados e a lógica do controlador torna seu aplicativo mais difícil de manter ao longo do tempo. A recomendação é que você coloque toda a lógica do banco de dados em uma camada de repositório separada.

Por exemplo, a Listagem 1 contém um repositório simples chamado ProductRepository. O repositório de produtos contém todo o código de acesso a dados para o aplicativo. A listagem também inclui a interface IProductRepository que o repositório de produtos implementa.

Listagem 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

O controlador na Listagem 2 usa a camada do repositório em suas ações Index() e Create(). Observe que esse controlador não contém nenhuma lógica de banco de dados. A criação de uma camada de repositório permite que você mantenha uma separação limpo de preocupações. Os controladores são responsáveis pela lógica de controle de fluxo do aplicativo e o repositório é responsável pela lógica de acesso a dados.

Listagem 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

Criando uma camada de serviço

Portanto, a lógica de controle de fluxo do aplicativo pertence a um controlador e a lógica de acesso a dados pertence a um repositório. Nesse caso, onde você coloca sua lógica de validação? Uma opção é colocar a lógica de validação em uma camada de serviço.

Uma camada de serviço é uma camada adicional em um aplicativo MVC ASP.NET que media a comunicação entre um controlador e uma camada de repositório. A camada de serviço contém lógica de negócios. Em particular, ele contém a lógica de validação.

Por exemplo, a camada de serviço do produto na Listagem 3 tem um método CreateProduct(). O método CreateProduct() chama o método ValidateProduct() para validar um novo produto antes de passar o produto para o repositório do produto.

Listagem 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

O Controlador de produto foi atualizado na Listagem 4 para usar a camada de serviço em vez da camada do repositório. A camada do controlador se comunica com a camada de serviço. A camada de serviço se comunica com a camada do repositório. Cada camada tem uma responsabilidade separada.

Listagem 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

Observe que o serviço de produto é criado no construtor do controlador de produto. Quando o serviço de produto é criado, o dicionário de estado do modelo é passado para o serviço. O serviço de produto usa o estado do modelo para passar mensagens de erro de validação de volta para o controlador.

Desacoplando a camada de serviço

Falha ao isolar o controlador e as camadas de serviço em um aspecto. O controlador e as camadas de serviço se comunicam por meio do estado do modelo. Em outras palavras, a camada de serviço tem uma dependência de um recurso específico do ASP.NET estrutura MVC.

Queremos isolar a camada de serviço da camada do controlador o máximo possível. Em teoria, devemos ser capazes de usar a camada de serviço com qualquer tipo de aplicativo e não apenas um aplicativo MVC ASP.NET. Por exemplo, no futuro, talvez queiramos criar um front-end do WPF para nosso aplicativo. Devemos encontrar uma maneira de remover a dependência de ASP.NET estado do modelo MVC de nossa camada de serviço.

Na Listagem 5, a camada de serviço foi atualizada para que ela não use mais o estado do modelo. Em vez disso, ele usa qualquer classe que implemente a interface IValidationDictionary.

Listagem 5 – Models\ProductService.vb (separado)

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

A interface IValidationDictionary é definida na Listagem 6. Essa interface simples tem um único método e uma única propriedade.

Listagem 6 – Models\IValidationDictionary.cs

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

A classe na Listagem 7, chamada classe ModelStateWrapper, implementa a interface IValidationDictionary. Você pode instanciar a classe ModelStateWrapper passando um dicionário de estado de modelo para o construtor.

Listagem 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

Por fim, o controlador atualizado na Listagem 8 usa o ModelStateWrapper ao criar a camada de serviço em seu construtor.

Listagem 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

Usar a interface IValidationDictionary e a classe ModelStateWrapper nos permite isolar completamente nossa camada de serviço da camada do controlador. A camada de serviço não depende mais do estado do modelo. Você pode passar qualquer classe que implemente a interface IValidationDictionary para a camada de serviço. Por exemplo, um aplicativo WPF pode implementar a interface IValidationDictionary com uma classe de coleção simples.

Resumo

O objetivo deste tutorial era discutir uma abordagem para executar a validação em um aplicativo MVC ASP.NET. Neste tutorial, você aprendeu a mover toda a lógica de validação para fora dos controladores e para uma camada de serviço separada. Você também aprendeu a isolar a camada de serviço da camada do controlador criando uma classe ModelStateWrapper.