Pregunta

I am converting from the old ways of ASP.NET Web Forms to ASP.NET MVC. I have a project that I am working on that has about 40-50 tables in the database. I have decided to use Entity Framework as my data access layer. I have also decided to put a repository layer and unit of work abstraction over EF so that I am not tied to it and so that I can do unit testing. Finally, I want to make my controllers "thin" so I am looking at implementing a business "service" layer for my business logic.

The thing I am struggling with is how do I propagate Business Logic Errors from my service layer to my Presentation UI layer so that an appropriate error can be shown? Please note that I am trying to look for a solution that is NOT MVC specific as this service/business logic layer will likely be used in other things besides an MVC app (console app's, web services, etc.)

On to some code...

Lets say I have a POCO / data / domain model like so:

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)...
}

An Entity Framework fluent configuration/mapping class like so:

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...
    }
}

A unit of work encapsulating multiple repositories like so:

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)); }
    }
}

A service / business logic layer like so:

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!");
          }
    }
}

And a controller like this:

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");
        }
    }
}

First of all, can anybody tell me if I am on the right track with using View Models? I feel like I almost have three View Models (Create, Edit, View/List) per domain model.

Secondly, my EF configuration/mapping class takes care of the database constraints. Some of these constraints (e.g. Max length) are also data annotations in the View Models and can easily be displayed on the UI. But where can I show my custom business logic errors?

¿Fue útil?

Solución

First, your overall approach to MVC looks good to me :-)

Second, you most likely want to use DataAnnotation on your view models for model validation. Have a look this blog post for a good intro on using it in ASP.MVC.

In case of custom validation not suitable for data annotation you can do the following in your controller:

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);
}

Otros consejos

This is a pretty old question, but for future readers I'd like to add something.

If you're actually using a N-Tier pattern, entity validation should be in your Service layer. Not in your MVC Controller.

The right way to do it is to do basic model validations in your model class, using ValidationAttributes, but re-validate your entities in your service layer. Add a handling of custom exceptions in your controller to catch any validation error raised from the service layer, and display error messages.

If your service layer is just there to call your repositories, you're doing something wrong ;)

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top