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

时间:2013-08-06 13:20:34

标签: asp.net-mvc-3 entity-framework service repository-pattern business-logic-layer

我正在从ASP.NET Web Forms的旧方式转换为ASP.NET MVC。我有一个我正在研究的项目,它在数据库中有大约40-50个表。我决定使用Entity Framework作为我的数据访问层。我还决定在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");
        }
    }
}

首先,任何人都可以告诉我,如果我使用View Models,我是否走在正确的轨道上?我觉得每个域模型几乎有三个视图模型(创建,编辑,查看/列表)。

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

2 个答案:

答案 0 :(得分:0)

首先,你对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);
}

答案 1 :(得分:0)

这是一个非常古老的问题,但对于未来的读者,我想添加一些东西。

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

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

如果您的服务层就在那里调用您的存储库,那么您做错了;)