使用服务层进行验证 (C#)

作者 :Stephen Walther

了解如何将验证逻辑从控制器操作移动到单独的服务层中。 在本教程中,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 类将服务层与控制器层隔离开来。