Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
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.