次の方法で共有


サービス層の検証 (VB)

作成者: Stephen Walther

コントローラー アクションから別のサービス レイヤーに検証ロジックを移動する方法について説明します。 このチュートリアルでは、コントローラー レイヤーからサービス レイヤーを分離することで、明確な関心の分離を維持する方法について、Stephen Walther が説明します。

このチュートリアルの目的は、ASP.NET MVC アプリケーションで検証を実行する 1 つの方法について説明することです。 このチュートリアルでは、コントローラーから別のサービス レイヤーに検証ロジックを移動する方法について説明します。

関心の分離

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

サービス レイヤーの作成

そのため、アプリケーション フロー制御ロジックはコントローラーに属し、データ アクセス ロジックはリポジトリに属します。 その場合、検証ロジックはどこに配置しますか? 1 つのオプションは、検証ロジックをサービス レイヤーに配置することです。

サービス レイヤーは、コントローラーとリポジトリ レイヤーの間の通信を仲介する、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 で定義されています。 この単純なインターフェイスには、1 つのメソッドと 1 つのプロパティがあります。

リスト 6 - Models\IValidationDictionary.cs

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

ModelStateWrapper クラスという名前のリスト 7 のクラスは、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 アプリケーションで検証を実行するための 1 つのアプローチについて説明することでした。 このチュートリアルでは、すべての検証ロジックをコントローラーから別のサービス レイヤーに移動する方法について説明しました。 また、ModelStateWrapper クラスを作成して、コントローラー レイヤーからサービス レイヤーを分離する方法についても学習しました。