Ověřování vrstvou služby (C#)
Zjistěte, jak přesunout logiku ověřování z akcí kontroleru do samostatné vrstvy služby. V tomto kurzu Stephen Walther vysvětluje, jak můžete zachovat ostré oddělení obav tím, že izolujete vrstvu služby od vrstvy kontroleru.
Cílem tohoto kurzu je popsat jednu metodu ověřování v aplikaci ASP.NET MVC. V tomto kurzu se naučíte, jak přesunout logiku ověřování z kontrolerů do samostatné vrstvy služby.
Oddělení problémů
Při vytváření aplikace ASP.NET MVC byste neměli umístit logiku databáze do akcí kontroleru. Kombinace logiky databáze a kontroleru ztěžuje údržbu vaší aplikace v průběhu času. Doporučujeme umístit veškerou logiku databáze do samostatné vrstvy úložiště.
Například Výpis 1 obsahuje jednoduché úložiště s názvem ProductRepository. Úložiště produktu obsahuje všechny kódy pro přístup k datům pro aplikaci. Výpis obsahuje také rozhraní IProductRepository, které implementuje úložiště produktů.
Výpis 1 – Modely\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();
}
}
Kontroler ve výpisu 2 používá vrstvu úložiště v akcích Index() i Create(). Všimněte si, že tento kontroler neobsahuje žádnou logiku databáze. Vytvoření vrstvy úložiště vám umožní udržovat čisté oddělení problémů. Kontrolery zodpovídají za logiku řízení toku aplikace a úložiště zodpovídá za logiku přístupu k datům.
Výpis 2 – Controllers\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");
}
}
}
Vytvoření vrstvy služby
Logika řízení toku aplikace tedy patří do kontroleru a logika přístupu k datům patří do úložiště. Kam v takovém případě vložíte logiku ověřování? Jednou z možností je umístit logiku ověřování do vrstvy služby.
Vrstva služby je další vrstva v aplikaci ASP.NET MVC, která zprostředkuje komunikaci mezi kontrolerem a vrstvou úložiště. Vrstva služby obsahuje obchodní logiku. Obsahuje zejména logiku ověřování.
Například vrstva služby produktu v seznamu 3 má metodu CreateProduct(). Metoda CreateProduct() volá metodu ValidateProduct() k ověření nového produktu před předáním produktu do úložiště produktu.
Výpis 3 – Modely\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();
}
}
Kontroler produktu byl v seznamu 4 aktualizován tak, aby místo vrstvy úložiště používal vrstvu služby. Vrstva kontroleru komunikuje s vrstvou služby. Vrstva služby komunikuje s vrstvou úložiště. Každá vrstva má samostatnou odpovědnost.
Výpis 4 – Controllers\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");
}
}
}
Všimněte si, že produktová služba je vytvořena v konstruktoru kontroleru produktu. Při vytvoření produktové služby se do služby předá slovník stavu modelu. Produktová služba používá stav modelu k předání ověřovacích chybových zpráv zpět kontroleru.
Oddělení vrstvy služby
Nepodařilo se nám izolovat vrstvy kontroleru a služby v jednom ohledu. Vrstva kontroleru a služby komunikují prostřednictvím stavu modelu. Jinými slovy, vrstva služby je závislá na konkrétní funkci architektury ASP.NET MVC.
Chceme co nejvíce izolovat vrstvu služby od vrstvy kontroleru. Teoreticky bychom měli být schopni používat vrstvu služby s libovolným typem aplikace, a ne jen s aplikací ASP.NET MVC. V budoucnu bychom například mohli chtít pro naši aplikaci vytvořit front-end WPF. Měli bychom najít způsob, jak z naší vrstvy služby odebrat závislost na ASP.NET stavu modelu MVC.
Ve výpisu 5 se vrstva služby aktualizovala tak, že už nepoužívá stav modelu. Místo toho používá jakoukoli třídu, která implementuje rozhraní IValidationDictionary.
Výpis 5 – Models\ProductService.cs (oddělené)
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();
}
}
Rozhraní IValidationDictionary je definováno v seznamu 6. Toto jednoduché rozhraní má jednu metodu a jednu vlastnost.
Výpis 6 – Models\IValidationDictionary.cs
namespace MvcApplication1.Models
{
public interface IValidationDictionary
{
void AddError(string key, string errorMessage);
bool IsValid { get; }
}
}
Třída v výpisu 7 s názvem ModelStateWrapper třída, implementuje rozhraní IValidationDictionary. Instanci třídy ModelStateWrapper můžete vytvořit předáním slovníku stavu modelu konstruktoru.
Výpis 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
}
}
Aktualizovaný kontroler v seznamu 8 používá ModelStateWrapper při vytváření vrstvy služby ve svém konstruktoru.
Výpis 8 – 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(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");
}
}
}
Pomocí rozhraní IValidationDictionary a třídy ModelStateWrapper nám umožňuje zcela izolovat vrstvu služby od vrstvy kontroleru. Vrstva služby už není závislá na stavu modelu. Do vrstvy služby můžete předat libovolnou třídu, která implementuje rozhraní IValidationDictionary. Například aplikace WPF může implementovat IValidationDictionary rozhraní s jednoduchou kolekcí třídy.
Souhrn
Cílem tohoto kurzu bylo probrat jeden přístup k ověřování v aplikaci ASP.NET MVC. V tomto kurzu jste zjistili, jak přesunout veškerou logiku ověřování z kontrolerů do samostatné vrstvy služby. Také jste se dozvěděli, jak izolovat vrstvu služby od vrstvy kontroleru vytvořením třídy ModelStateWrapper.