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.