Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
von Stephen Walther
Erfahren Sie, wie Sie Ihre Validierungslogik aus Ihren Controlleraktionen in eine separate Dienstebene verschieben. In diesem Tutorial erläutert Stephen Walther, wie Sie eine scharfe Trennung der Bedenken beibehalten können, indem Sie Ihre Dienstebene von Ihrer Controllerebene isolieren.
Das Ziel dieses Tutorials besteht darin, eine Methode zum Durchführen der Validierung in einer ASP.NET MVC-Anwendung zu beschreiben. In diesem Tutorial erfahren Sie, wie Sie Ihre Validierungslogik aus Ihren Controllern in eine separate Dienstebene verschieben.
Trennen von Bedenken
Wenn Sie eine ASP.NET MVC-Anwendung erstellen, sollten Sie Ihre Datenbanklogik nicht in Ihren Controlleraktionen platzieren. Das Mischen ihrer Datenbank- und Controllerlogik erschwert die Verwaltung Ihrer Anwendung im Laufe der Zeit. Es wird empfohlen, die gesamte Datenbanklogik in einer separaten Repositoryebene zu platzieren.
Beispielsweise enthält Listing 1 ein einfaches Repository namens ProductRepository. Das Produktrepository enthält den gesamten Datenzugriffscode für die Anwendung. Die Auflistung enthält auch die IProductRepository-Schnittstelle, die das Produktrepository implementiert.
Listing 1 –- Models\ProductRepository.cs
using System.Collections.Generic;
using System.Linq;
namespace MvcApplication1.Models
{
public class ProductRepository : MvcApplication1.Models.IProductRepository
{
private ProductDBEntities _entities = new ProductDBEntities();
public IEnumerable<Product> ListProducts()
{
return _entities.ProductSet.ToList();
}
public bool CreateProduct(Product productToCreate)
{
try
{
_entities.AddToProductSet(productToCreate);
_entities.SaveChanges();
return true;
}
catch
{
return false;
}
}
}
public interface IProductRepository
{
bool CreateProduct(Product productToCreate);
IEnumerable<Product> ListProducts();
}
}
Der Controller in Listing 2 verwendet die Repositoryebene sowohl in den Aktionen Index() als auch create(). Beachten Sie, dass dieser Controller keine Datenbanklogik enthält. Durch das Erstellen einer Repositoryebene können Sie eine sauber Trennung von Anliegen beibehalten. Controller sind für die Logik der Anwendungsflusssteuerung verantwortlich, und das Repository ist für die Datenzugriffslogik verantwortlich.
Eintrag 2: Controller\ProductController.cs
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class ProductController : Controller
{
private IProductRepository _repository;
public ProductController():
this(new ProductRepository()) {}
public ProductController(IProductRepository repository)
{
_repository = repository;
}
public ActionResult Index()
{
return View(_repository.ListProducts());
}
//
// GET: /Product/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Product/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude="Id")] Product productToCreate)
{
_repository.CreateProduct(productToCreate);
return RedirectToAction("Index");
}
}
}
Erstellen einer Dienstebene
Daher gehört die Logik der Anwendungsflusssteuerung in einen Controller und die Datenzugriffslogik in ein Repository. Wo setzen Sie in diesem Fall Ihre Validierungslogik? Eine Möglichkeit besteht darin, Ihre Validierungslogik in einer Dienstebene zu platzieren.
Eine Dienstschicht ist eine zusätzliche Ebene in einer ASP.NET MVC-Anwendung, die die Kommunikation zwischen einem Controller und einer Repositoryebene vermittelt. Die Dienstebene enthält Geschäftslogik. Insbesondere enthält sie Validierungslogik.
Beispielsweise verfügt die Produktdienstebene in Listing 3 über eine CreateProduct()-Methode. Die CreateProduct()-Methode ruft die ValidateProduct()-Methode auf, um ein neues Produkt zu überprüfen, bevor das Produkt an das Produktrepository übergeben wird.
Eintrag 3: Models\ProductService.cs
using System.Collections.Generic;
using System.Web.Mvc;
namespace MvcApplication1.Models
{
public class ProductService : IProductService
{
private ModelStateDictionary _modelState;
private IProductRepository _repository;
public ProductService(ModelStateDictionary modelState, IProductRepository repository)
{
_modelState = modelState;
_repository = repository;
}
protected bool ValidateProduct(Product productToValidate)
{
if (productToValidate.Name.Trim().Length == 0)
_modelState.AddModelError("Name", "Name is required.");
if (productToValidate.Description.Trim().Length == 0)
_modelState.AddModelError("Description", "Description is required.");
if (productToValidate.UnitsInStock < 0)
_modelState.AddModelError("UnitsInStock", "Units in stock cannot be less than zero.");
return _modelState.IsValid;
}
public IEnumerable<Product> ListProducts()
{
return _repository.ListProducts();
}
public bool CreateProduct(Product productToCreate)
{
// Validation logic
if (!ValidateProduct(productToCreate))
return false;
// Database logic
try
{
_repository.CreateProduct(productToCreate);
}
catch
{
return false;
}
return true;
}
}
public interface IProductService
{
bool CreateProduct(Product productToCreate);
IEnumerable<Product> ListProducts();
}
}
Der Produktcontroller wurde in Listing 4 aktualisiert, um die Dienstebene anstelle der Repositoryebene zu verwenden. Die Controllerebene spricht mit der Dienstebene. Die Dienstebene spricht mit der Repositoryebene. Jede Ebene hat eine separate Verantwortung.
Eintrag 4: Controller\ProductController.cs
Listing 4 – Controllers\ProductController.cs
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class ProductController : Controller
{
private IProductService _service;
public ProductController()
{
_service = new ProductService(this.ModelState, new ProductRepository());
}
public ProductController(IProductService service)
{
_service = service;
}
public ActionResult Index()
{
return View(_service.ListProducts());
}
//
// GET: /Product/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Product/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Product productToCreate)
{
if (!_service.CreateProduct(productToCreate))
return View();
return RedirectToAction("Index");
}
}
}
Beachten Sie, dass der Produktdienst im Produktcontrollerkonstruktor erstellt wird. Wenn der Produktdienst erstellt wird, wird das Modellstatuswörterbuch an den Dienst übergeben. Der Produktdienst verwendet den Modellzustand, um Validierungsfehlermeldungen an den Controller zurückzugeben.
Entkoppeln der Dienstebene
Wir konnten den Controller und die Dienstebene nicht in einer Hinsicht isolieren. Controller- und Dienstebenen kommunizieren über den Modellzustand. Anders ausgedrückt: Die Dienstebene ist von einem bestimmten Feature des ASP.NET MVC-Frameworks abhängig.
Wir möchten die Dienstebene so weit wie möglich von unserer Controllerebene isolieren. Theoretisch sollten wir die Dienstebene mit jedem Anwendungstyp und nicht nur mit einer ASP.NET MVC-Anwendung verwenden können. In Zukunft möchten wir beispielsweise ein WPF-Front-End für unsere Anwendung erstellen. Wir sollten eine Möglichkeit finden, die Abhängigkeit von ASP.NET MVC-Modellzustand aus unserer Dienstebene zu entfernen.
In Listing 5 wurde die Dienstebene aktualisiert, sodass sie nicht mehr den Modellzustand verwendet. Stattdessen wird eine beliebige Klasse verwendet, die die IValidationDictionary-Schnittstelle implementiert.
Eintrag 5: Models\ProductService.cs (entkoppelt)
using System.Collections.Generic;
namespace MvcApplication1.Models
{
public class ProductService : IProductService
{
private IValidationDictionary _validatonDictionary;
private IProductRepository _repository;
public ProductService(IValidationDictionary validationDictionary, IProductRepository repository)
{
_validatonDictionary = validationDictionary;
_repository = repository;
}
protected bool ValidateProduct(Product productToValidate)
{
if (productToValidate.Name.Trim().Length == 0)
_validatonDictionary.AddError("Name", "Name is required.");
if (productToValidate.Description.Trim().Length == 0)
_validatonDictionary.AddError("Description", "Description is required.");
if (productToValidate.UnitsInStock < 0)
_validatonDictionary.AddError("UnitsInStock", "Units in stock cannot be less than zero.");
return _validatonDictionary.IsValid;
}
public IEnumerable<Product> ListProducts()
{
return _repository.ListProducts();
}
public bool CreateProduct(Product productToCreate)
{
// Validation logic
if (!ValidateProduct(productToCreate))
return false;
// Database logic
try
{
_repository.CreateProduct(productToCreate);
}
catch
{
return false;
}
return true;
}
}
public interface IProductService
{
bool CreateProduct(Product productToCreate);
IEnumerable<Product> ListProducts();
}
}
Die IValidationDictionary-Schnittstelle ist in Listing 6 definiert. Diese einfache Schnittstelle verfügt über eine einzelne Methode und eine einzelne Eigenschaft.
Eintrag 6: Models\IValidationDictionary.cs
namespace MvcApplication1.Models
{
public interface IValidationDictionary
{
void AddError(string key, string errorMessage);
bool IsValid { get; }
}
}
Die Klasse in Listing 7 mit dem Namen ModelStateWrapper-Klasse implementiert die IValidationDictionary-Schnittstelle. Sie können die ModelStateWrapper-Klasse instanziieren, indem Sie ein Modellstatuswörterbuch an den Konstruktor übergeben.
Eintrag 7: Models\ModelStateWrapper.cs
using System.Web.Mvc;
namespace MvcApplication1.Models
{
public class ModelStateWrapper : IValidationDictionary
{
private ModelStateDictionary _modelState;
public ModelStateWrapper(ModelStateDictionary modelState)
{
_modelState = modelState;
}
#region IValidationDictionary Members
public void AddError(string key, string errorMessage)
{
_modelState.AddModelError(key, errorMessage);
}
public bool IsValid
{
get { return _modelState.IsValid; }
}
#endregion
}
}
Schließlich verwendet der aktualisierte Controller in Listing 8 den ModelStateWrapper, wenn die Dienstebene in ihrem Konstruktor erstellt wird.
Eintrag 8: Controller\ProductController.cs
using System.Web.Mvc;
using MvcApplication1.Models;
namespace MvcApplication1.Controllers
{
public class ProductController : Controller
{
private IProductService _service;
public ProductController()
{
_service = new ProductService(new ModelStateWrapper(this.ModelState), new ProductRepository());
}
public ProductController(IProductService service)
{
_service = service;
}
public ActionResult Index()
{
return View(_service.ListProducts());
}
//
// GET: /Product/Create
public ActionResult Create()
{
return View();
}
//
// POST: /Product/Create
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Create([Bind(Exclude = "Id")] Product productToCreate)
{
if (!_service.CreateProduct(productToCreate))
return View();
return RedirectToAction("Index");
}
}
}
Die Verwendung der IValidationDictionary-Schnittstelle und der ModelStateWrapper-Klasse ermöglicht es uns, unsere Dienstebene vollständig von unserer Controllerebene zu isolieren. Die Dienstebene ist nicht mehr vom Modellzustand abhängig. Sie können jede Klasse, die die IValidationDictionary-Schnittstelle implementiert, an die Dienstschicht übergeben. Beispielsweise kann eine WPF-Anwendung die IValidationDictionary-Schnittstelle mit einer einfachen Auflistungsklasse implementieren.
Zusammenfassung
Das Ziel dieses Tutorials bestand darin, einen Ansatz zur Durchführung der Validierung in einer ASP.NET MVC-Anwendung zu diskutieren. In diesem Tutorial haben Sie gelernt, wie Sie ihre gesamte Validierungslogik aus Ihren Controllern in eine separate Dienstebene verschieben. Sie haben auch gelernt, wie Sie Ihre Dienstebene von Ihrer Controllerebene isolieren, indem Sie eine ModelStateWrapper-Klasse erstellen.