使用服务层进行验证 (C#)
了解如何将验证逻辑从控制器操作移动到单独的服务层中。 在本教程中,Stephen Walther 介绍了如何通过将服务层与控制器层隔离来保持关注点的清晰分离。
本教程的目的是介绍在 ASP.NET MVC 应用程序中执行验证的一种方法。 本教程介绍如何将验证逻辑从控制器移出并移动到单独的服务层中。
分离关注点
生成 ASP.NET MVC 应用程序时,不应将数据库逻辑置于控制器操作中。 混合使用数据库和控制器逻辑会使应用程序在一段时间内更难维护。 建议将所有数据库逻辑放在单独的存储库层中。
例如,列表 1 包含名为 ProductRepository 的简单存储库。 产品存储库包含应用程序的所有数据访问代码。 该列表还包括产品存储库实现的 IProductRepository 接口。
列表 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();
}
}
清单 2 中的控制器在其 Index () 和 Create () 操作中使用存储库层。 请注意,此控制器不包含任何数据库逻辑。 创建存储库层可以保持关注点的干净分离。 控制器负责应用程序流控制逻辑,存储库负责数据访问逻辑。
清单 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");
}
}
}
创建服务层
因此,应用程序流控制逻辑属于控制器,数据访问逻辑属于存储库。 在这种情况下,将验证逻辑放在何处? 一个选项是将验证逻辑放在 服务层中。
服务层是 ASP.NET MVC 应用程序中的附加层,用于调解控制器层和存储库层之间的通信。 服务层包含业务逻辑。 具体而言,它包含验证逻辑。
例如,清单 3 中的产品服务层具有 CreateProduct () 方法。 CreateProduct () 方法调用 ValidateProduct () 方法来验证新产品,然后再将产品传递到产品存储库。
清单 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();
}
}
产品控制器已在清单 4 中更新为使用服务层而不是存储库层。 控制器层与服务层通信。 服务层与存储库层通信。 每个层都有单独的责任。
清单 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");
}
}
}
请注意,产品服务是在产品控制器构造函数中创建的。 创建产品服务时,模型状态字典将传递给服务。 产品服务使用模型状态将验证错误消息传递回控制器。
分离服务层
我们未能在一个方面隔离控制器和服务层。 控制器和服务层通过模型状态进行通信。 换句话说,服务层依赖于 ASP.NET MVC 框架的特定功能。
我们希望尽可能将服务层与控制器层隔离开来。 从理论上讲,我们应该能够将服务层用于任何类型的应用程序,而不仅仅是 ASP.NET MVC 应用程序。 例如,将来,我们可能需要为应用程序生成 WPF 前端。 我们应该找到一种方法,从服务层中删除对 ASP.NET MVC 模型状态的依赖关系。
在清单 5 中,服务层已更新,使其不再使用模型状态。 相反,它使用实现 IValidationDictionary 接口的任何类。
清单 5 - Models\ProductService.cs (分离)
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();
}
}
IValidationDictionary 接口在清单 6 中定义。 此简单接口具有单个方法和单个属性。
清单 6 - Models\IValidationDictionary.cs
namespace MvcApplication1.Models
{
public interface IValidationDictionary
{
void AddError(string key, string errorMessage);
bool IsValid { get; }
}
}
清单 7 中的类(名为 ModelStateWrapper 类)实现 IValidationDictionary 接口。 可以通过将模型状态字典传递给构造函数来实例化 ModelStateWrapper 类。
清单 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
}
}
最后,清单 8 中更新的控制器在其构造函数中创建服务层时使用 ModelStateWrapper。
列表 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");
}
}
}
使用 IValidationDictionary 接口和 ModelStateWrapper 类可将服务层与控制器层完全隔离。 服务层不再依赖于模型状态。 可以将实现 IValidationDictionary 接口的任何类传递到服务层。 例如,WPF 应用程序可以使用简单的集合类实现 IValidationDictionary 接口。
总结
本教程的目的是讨论在 ASP.NET MVC 应用程序中执行验证的一种方法。 在本教程中,你已了解如何将所有验证逻辑从控制器移出并移动到单独的服务层。 你还了解了如何通过创建 ModelStateWrapper 类将服务层与控制器层隔离开来。