N 层服务层验证显示表示层中的业务逻辑错误

Posted

技术标签:

【中文标题】N 层服务层验证显示表示层中的业务逻辑错误【英文标题】:N-Tier Service Layer Validation Show Business Logic Error in Presentation Layer 【发布时间】:2013-08-07 12:49:42 【问题描述】:

我正在从旧的 ASP.NET Web 窗体方式转换为 ASP.NET MVC。我有一个正在处理的项目,该项目在数据库中有大约 40-50 个表。我决定使用实体框架作为我的数据访问层。我还决定在 EF 上放置一个存储库层和工作单元抽象,这样我就不会被它束缚,这样我就可以进行单元测试。最后,我想让我的控制器“瘦”,所以我正在考虑为我的业务逻辑实现一个业务“服务”层。

我正在努力解决的问题是如何将业务逻辑错误从我的服务层传播到我的 Presentation UI 层,以便显示适当的错误?请注意,我正在尝试寻找非 MVC 特定的解决方案,因为此服务/业务逻辑层可能会用于除 MVC 应用程序(控制台应用程序、Web 服务等)之外的其他事物。

关于一些代码...

假设我有一个像这样的 POCO/数据/域模型:

public class Category

    public int Id  get; set; 
    public string Name  get; set; 
    public string Description  get; set; 
    public bool IsActive  get; set; 

    // other properties (navigation, etc)...

一个像这样的实体框架流式配置/映射类:

public class CategoryMap : EntityTypeConfiguration<Category>

    public CategoryMap()
    
        this.HasKey(c => c.Id);
        this.Property(c => c.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); // auto increment identity in our DB schema
        this.Property(c=> c.Name)
            .IsRequired() // defined as NOT NULL in DB schema so we put a constraint here
            .HasMaxLength(150); // defined as varchar(150) in DB schema so we put a constraint here
        this.Property(c=> c.Description)
            .IsRequired(); // defined as NOT NULL in DB schema so we put a constraint here

        // fluent config for related entities (navigation properties) would go here...
    

一个像这样封装多个存储库的工作单元:

public class UnitOfWork : IUnitOfWork

    private readonly MyDbContext context;
    private CategoryRepository catRepo;

    public UnitOfWork()
    
         this.context = new MyDbContext();
    

    public ICategoryRepository Categories
    
        get  return this.catRepo?? (this.catRepo= new CategoryRepository (this.context)); 
    

这样的服务/业务逻辑层:

public class CategoryService : ICategoryService

    private readonly IUnitOfWork unitOfWork;
    public CategoryService(IUnitOfWork uow) // injected by IoC
    
          this.unitOfWork = uow;
    

    public Category CreateNewCategory(Category category)
    
          if (category == null)
          
              throw new ArgumentNullException("category cannot be null");
          

          // Simple business logic here to make sure another category with this name does not already exist.
          int count = this.unitOfWork.Categories.Count(cat => cat.Name == category.Name);
          if (count > 0)
          
              // *** This is the error I want the user to see in the UI ***
              throw new Exception("Sorry - a category with that name already exists!");
          
    

还有这样的控制器:

public ManageCategoriesController : Controller

    ICategoryService catSvc;
    public ManageCategoriesController(ICategoryService svc) // injected by IoC
    
        this.catSvc = svc;
    


    [HttpPost]
    public ActionResult(CategoryCreateModel createModel) // my View Models / Create Models have Data Annotations on them
    
        if (ModelState.IsValid)
        
             // use of AutoMapper to map from View Model to domain model...
             Category cat = Mapper.Map<CategoryCreateModel , Category>(createModel);
             this.catSvc.CreateNewCategory(cat); // ***need to get potential errors from Service and display on form.***
             return this.RedirectToAction("Index");
        
    

首先,谁能告诉我我是否在正确的轨道上使用视图模型?我觉得每个域模型几乎都有三个视图模型(创建、编辑、视图/列表)。

其次,我的 EF 配置/映射类负责数据库约束。其中一些约束(例如最大长度)也是视图模型中的数据注释,可以很容易地显示在 UI 上。但是我在哪里可以显示我的自定义业务逻辑错误?

【问题讨论】:

【参考方案1】:

首先,您对 MVC 的整体方法对我来说看起来不错 :-)

其次,您很可能希望在视图模型上使用 DataAnnotation 进行模型验证。看看this blog post,了解如何在 ASP.MVC 中使用它。

如果自定义验证不适合数据注释,您可以在控制器中执行以下操作:

try

    // the following exception could be thown by some nested validation logic
    // e.g. while processing a post request
    throw new ValidationException("the error description");

catch (ValidationException exception)

    ModelState.AddModelError("", exception.Message);

【讨论】:

看起来不错。在我的服务层中,我抛出了一个“BusinessLogicException”,并在我的 UI 中捕获了它。【参考方案2】:

这是一个相当古老的问题,但对于未来的读者,我想补充一些东西。

如果您实际使用的是 N 层模式,则实体验证应该在您的服务层中。不在您的 MVC 控制器中。

正确的做法是在模型类中使用 ValidationAttributes 进行基本模型验证,但在服务层中重新验证实体。 在控制器中添加自定义异常处理,以捕获服务层引发的任何验证错误,并显示错误消息。

如果您的服务层只是用来调用您的存储库,那么您做错了什么;)

【讨论】:

以上是关于N 层服务层验证显示表示层中的业务逻辑错误的主要内容,如果未能解决你的问题,请参考以下文章

使用分层实现业务处理

使用三层架构处理业务

存储过程中的所有业务逻辑

为啥我们需要在单独的服务层中编写业务逻辑而不是在控制器本身中编写?

将依赖于 Web 引用的业务逻辑从表示层中分离出来

如何管理业务逻辑层中的unicity?