Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Informazioni su come spostare la logica di convalida dalle azioni del controller e in un livello di servizio separato. In questa esercitazione, Stephen Walther spiega come mantenere una netta separazione delle problematiche isolando il livello di servizio dal livello controller.
L'obiettivo di questa esercitazione è descrivere un metodo per eseguire la convalida in un'applicazione MVC ASP.NET. In questa esercitazione si apprenderà come spostare la logica di convalida dai controller e in un livello di servizio separato.
Separazione dei problemi
Quando si compila un'applicazione MVC ASP.NET, non è consigliabile inserire la logica del database all'interno delle azioni del controller. La combinazione della logica del database e del controller rende l'applicazione più difficile da gestire nel tempo. È consigliabile inserire tutta la logica del database in un livello di repository separato.
Ad esempio, l'elenco 1 contiene un repository semplice denominato ProductRepository. Il repository del prodotto contiene tutto il codice di accesso ai dati per l'applicazione. L'elenco include anche l'interfaccia IProductRepository implementata dal repository del prodotto.
Elenco 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
Il controller nell'elenco 2 usa il livello del repository nelle azioni Index() e Create(). Si noti che questo controller non contiene alcuna logica di database. La creazione di un livello del repository consente di mantenere una separazione pulita delle problematiche. I controller sono responsabili della logica di controllo del flusso dell'applicazione e il repository è responsabile della logica di accesso ai dati.
Elenco 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
Creazione di un livello di servizio
Pertanto, la logica di controllo del flusso dell'applicazione appartiene a un controller e la logica di accesso ai dati appartiene a un repository. In tal caso, dove si inserisce la logica di convalida? Un'opzione consiste nell'inserire la logica di convalida in un livello di servizio.
Un livello di servizio è un livello aggiuntivo in un'applicazione MVC ASP.NET che media la comunicazione tra un controller e un livello di repository. Il livello del servizio contiene la logica di business. In particolare, contiene la logica di convalida.
Ad esempio, il livello di servizio del prodotto in Listing 3 ha un metodo CreateProduct(). Il metodo CreateProduct() chiama il metodo ValidateProduct() per convalidare un nuovo prodotto prima di passare il prodotto al repository del prodotto.
Elenco 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
Il controller del prodotto è stato aggiornato nell'elenco 4 per usare il livello di servizio anziché il livello del repository. Il livello controller comunica con il livello del servizio. Il livello del servizio comunica con il livello del repository. Ogni livello ha una responsabilità separata.
Elenco 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
Si noti che il servizio prodotto viene creato nel costruttore del controller del prodotto. Quando viene creato il servizio di prodotto, il dizionario dello stato del modello viene passato al servizio. Il servizio del prodotto usa lo stato del modello per passare nuovamente i messaggi di errore di convalida al controller.
Disaccoppiamento del livello di servizio
Non è stato possibile isolare il controller e i livelli di servizio in un unico rispetto. I livelli controller e di servizio comunicano tramite lo stato del modello. In altre parole, il livello del servizio ha una dipendenza da una particolare funzionalità del framework MVC ASP.NET.
Si vuole isolare il livello di servizio dal livello controller il più possibile. In teoria, dovremmo essere in grado di usare il livello di servizio con qualsiasi tipo di applicazione e non solo un'applicazione ASP.NET MVC. In futuro, ad esempio, potrebbe essere necessario creare un front-end WPF per l'applicazione. È consigliabile trovare un modo per rimuovere la dipendenza dallo stato del modello MVC ASP.NET dal livello del servizio.
Nell'elenco 5 il livello del servizio è stato aggiornato in modo che non usi più lo stato del modello. Usa invece qualsiasi classe che implementa l'interfaccia IValidationDictionary.
Elenco 5 - Models\ProductService.vb (disaccoppiato)
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
L'interfaccia IValidationDictionary è definita nell'elenco 6. Questa interfaccia semplice ha un singolo metodo e una singola proprietà.
Elenco 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 classe in Listato 7, denominata classe ModelStateWrapper, implementa l'interfaccia IValidationDictionary. È possibile creare un'istanza della classe ModelStateWrapper passando un dizionario di stato del modello al costruttore.
Elenco 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
Infine, il controller aggiornato nell'elenco 8 usa ModelStateWrapper durante la creazione del livello di servizio nel relativo costruttore.
Elenco 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
L'uso dell'interfaccia IValidationDictionary e della classe ModelStateWrapper consente di isolare completamente il livello di servizio dal livello controller. Il livello del servizio non dipende più dallo stato del modello. È possibile passare qualsiasi classe che implementa l'interfaccia IValidationDictionary al livello del servizio. Ad esempio, un'applicazione WPF potrebbe implementare l'interfaccia IValidationDictionary con una classe di raccolta semplice.
Riepilogo
L'obiettivo di questa esercitazione era illustrare un approccio all'esecuzione della convalida in un'applicazione MVC ASP.NET. In questa esercitazione si è appreso come spostare tutta la logica di convalida dai controller e in un livello di servizio separato. Si è anche appreso come isolare il livello di servizio dal livello controller creando una classe ModelStateWrapper.