Compartir a través de


Validación con un nivel de servicio (VB)

por Stephen Walther

Aprenda a mover la lógica de validación fuera de las acciones del controlador y a introducirla en una capa de servicio distinta. En este tutorial, Stephen Walther explica cómo puede separar claramente sus problemas aislando la capa de servicio de la capa de controlador.

El objetivo de este tutorial es describir un método de realizar la validación en una aplicación ASP.NET MVC. En este tutorial, aprenderá a mover la lógica de validación fuera de los controladores y a introducirla en una capa de servicio distinta.

Separación de responsabilidades

Al compilar una aplicación ASP.NET MVC, no debe colocar la lógica de la base de datos dentro de las acciones del controlador. La combinación de lógica de la base de datos y del controlador hace que la aplicación sea más difícil de mantener con el tiempo. La recomendación es colocar toda la lógica de base de datos en una capa de repositorio distinta.

Por ejemplo, la lista 1 contiene un repositorio simple denominado ProductRepository. El repositorio de productos contiene todo el código de acceso a los datos de la aplicación. La lista también incluye la interfaz IProductRepository que implementa el repositorio de productos.

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

El controlador de la lista 2 usa la capa de repositorio en sus acciones Index() y Create(). Observe que este controlador no contiene ninguna lógica de base de datos. La creación de una capa de repositorio le permite mantener una separación limpia de los problemas. Los controladores son responsables de la lógica de control del flujo de la aplicación, mientras que el repositorio es responsable de la lógica de acceso a los datos.

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

Creación de una capa de servicio

Por lo tanto, la lógica de control del flujo de aplicación pertenece a un controlador y la lógica de acceso a los datos pertenece a un repositorio. En ese caso, ¿dónde se coloca la lógica de validación? Una opción es colocarla en una capa de servicio.

Una capa de servicio es una capa adicional en una aplicación ASP.NET MVC que media la comunicación entre un controlador y una capa de repositorio. La capa de servicio contiene lógica de negocios. En concreto, contiene lógica de validación.

Por ejemplo, la capa de servicio de producto de la lista 3 tiene un método CreateProduct(). El método CreateProduct() llama al método ValidateProduct() para validar un nuevo producto antes de pasarlo al repositorio de productos.

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

El controlador Product se ha actualizado en la lista 4 para usar la capa de servicio en lugar de la capa de repositorio. La capa de controlador se comunica con la capa de servicio. La capa de servicio se comunica con la capa de repositorio. Cada capa tiene una responsabilidad distinta.

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

Observe que el servicio de producto se crea en el constructor del controlador de producto. Cuando se crea el servicio de producto, el diccionario de estado del modelo se pasa al servicio. El servicio de producto usa el estado del modelo para volver a pasar los mensajes de error de validación al controlador.

Desacoplamiento de la capa de servicio

No hemos podido aislar las capas de controlador y servicio en un solo aspecto. Las capas de controlador y servicio se comunican mediante el estado del modelo. En otras palabras, la capa de servicio tiene una dependencia de una característica determinada del marco ASP.NET MVC.

Queremos aislar lo más posible la capa de servicio de la capa de controlador. En teoría, deberíamos poder usar la capa de servicio con cualquier tipo de aplicación, no solo con aplicaciones ASP.NET MVC. Por ejemplo, en el futuro, quizás queramos compilar un front-end de WPF para nuestra aplicación. Deberíamos encontrar una manera de quitar la dependencia del estado del modelo de ASP.NET MVC de nuestra capa de servicio.

En la lista 5, la capa de servicio se ha actualizado y ya no usa el estado del modelo. En su lugar, usa cualquier clase que implemente la interfaz IValidationDictionary.

Lista 5: Models\ProductService.vb (desacoplado)

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

La interfaz IValidationDictionary se define en la lista 6. Esta interfaz simple tiene un único método y una sola propiedad.

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

La clase de la lista 7, llamada clase ModelStateWrapper, implementa la interfaz IValidationDictionary. Puede crear una instancia de la clase ModelStateWrapper pasando un diccionario de estados de modelo al constructor.

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

Por último, el controlador actualizado de la lista 8 usa ModelStateWrapper al crear la capa de servicio en su constructor.

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

El uso de la interfaz IValidationDictionary y la clase ModelStateWrapper nos permite aislar completamente la capa de servicio de la capa de controlador. La capa de servicio ya no depende del estado del modelo. Puede pasar cualquier clase que implemente la interfaz IValidationDictionary a la capa de servicio. Por ejemplo, una aplicación WPF podría implementar la interfaz IValidationDictionary con una clase de colección simple.

Resumen

El objetivo de este tutorial era analizar un enfoque para realizar la validación en una aplicación ASP.NET MVC. En este tutorial, ha aprendido a mover toda la lógica de validación fuera de los controladores e introducirla en una capa de servicio distinta. También ha aprendido a aislar la capa de servicio de la capa de controlador mediante la creación de una clase ModelStateWrapper.