使用服务层进行验证 (VB)

作者 :Stephen Walther

了解如何将验证逻辑从控制器操作移动到单独的服务层中。 在本教程中,Stephen Walther 介绍了如何通过将服务层与控制器层隔离来保持关注点的清晰分离。

本教程的目的是介绍在 ASP.NET MVC 应用程序中执行验证的一种方法。 本教程介绍如何将验证逻辑从控制器移出并移动到单独的服务层中。

分离关注点

生成 ASP.NET MVC 应用程序时,不应将数据库逻辑置于控制器操作中。 混合使用数据库和控制器逻辑会使应用程序在一段时间内更难维护。 建议将所有数据库逻辑放在单独的存储库层中。

例如,列表 1 包含名为 ProductRepository 的简单存储库。 产品存储库包含应用程序的所有数据访问代码。 该列表还包括产品存储库实现的 IProductRepository 接口。

列表 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

清单 2 中的控制器在其 Index () 和 Create () 操作中使用存储库层。 请注意,此控制器不包含任何数据库逻辑。 创建存储库层可以保持关注点的干净分离。 控制器负责应用程序流控制逻辑,存储库负责数据访问逻辑。

清单 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

创建服务层

因此,应用程序流控制逻辑属于控制器,数据访问逻辑属于存储库。 在这种情况下,将验证逻辑放在何处? 一个选项是将验证逻辑放在 服务层中。

服务层是 ASP.NET MVC 应用程序中的附加层,用于调解控制器层和存储库层之间的通信。 服务层包含业务逻辑。 具体而言,它包含验证逻辑。

例如,清单 3 中的产品服务层具有 CreateProduct () 方法。 CreateProduct () 方法调用 ValidateProduct () 方法来验证新产品,然后再将产品传递到产品存储库。

清单 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

产品控制器已在清单 4 中更新为使用服务层而不是存储库层。 控制器层与服务层通信。 服务层与存储库层通信。 每个层都有单独的责任。

清单 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

请注意,产品服务是在产品控制器构造函数中创建的。 创建产品服务时,模型状态字典将传递给服务。 产品服务使用模型状态将验证错误消息传递回控制器。

分离服务层

我们未能在一个方面隔离控制器和服务层。 控制器和服务层通过模型状态进行通信。 换句话说,服务层依赖于 ASP.NET MVC 框架的特定功能。

我们希望尽可能将服务层与控制器层隔离开来。 从理论上讲,我们应该能够将服务层用于任何类型的应用程序,而不仅仅是 ASP.NET MVC 应用程序。 例如,将来,我们可能需要为应用程序生成 WPF 前端。 我们应该找到一种方法,从服务层中删除对 ASP.NET MVC 模型状态的依赖关系。

在清单 5 中,服务层已更新,使其不再使用模型状态。 相反,它使用实现 IValidationDictionary 接口的任何类。

清单 5 - Models\ProductService.vb (分离)

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

IValidationDictionary 接口在清单 6 中定义。 此简单接口具有单个方法和单个属性。

清单 6 - Models\IValidationDictionary.cs

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

清单 7 中的类(名为 ModelStateWrapper 类)实现 IValidationDictionary 接口。 可以通过将模型状态字典传递给构造函数来实例化 ModelStateWrapper 类。

清单 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

最后,清单 8 中更新的控制器在其构造函数中创建服务层时使用 ModelStateWrapper。

清单 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

使用 IValidationDictionary 接口和 ModelStateWrapper 类可将服务层与控制器层完全隔离。 服务层不再依赖于模型状态。 可以将实现 IValidationDictionary 接口的任何类传递到服务层。 例如,WPF 应用程序可以使用简单的集合类实现 IValidationDictionary 接口。

总结

本教程的目的是讨论在 ASP.NET MVC 应用程序中执行验证的一种方法。 在本教程中,你已了解如何将所有验证逻辑从控制器移出并移动到单独的服务层。 你还了解了如何通过创建 ModelStateWrapper 类将服务层与控制器层隔离开来。