共用方式為


驗證與服務層 (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 類別,將服務層與控制器層區隔。