Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC: How can I get my business rule validation to bubble up to the presentation layer?

Tags:

c#

asp.net-mvc

For each of my business entities I have a corresponding view model.

I have a generic CRUD controller that works like this:

    [HttpPost]
    public virtual ActionResult Create(TViewModel model, int? id)
    {
        // Validate input
        if (!ModelState.IsValid)
            return Json(Failure(createView, model.SelectLists(repository)));

        // Prepare Model
        var entity = new TModel();

        // Add to repository
        UpdateModel(entity);
        repository.Add(entity);
        repository.Save();
        return Json(CreateSuccess(entity));
    }

I use data annotations on my view model properties and this works great for simple input validation.

Now I have a case where I want to make sure a duplicate record isn't created by accident.

My first instinct is to put this logic in the repository's Add method. This implementation would be easy, but how do I get the repository to add a model state error and return some useful information to the user? I feel like there has to be a solution out there, but I haven't had much luck searching.

Thanks for any help!

like image 776
Jeff Camera Avatar asked Jan 28 '11 01:01

Jeff Camera


3 Answers

I would use exceptions.

  • Throw your custom application exception at the Add method, if an enity is doubled.
  • Wrap the Add method in a try block to catch this specific exception at the Create method.
  • Add a model state error based on the exception data at the catch block

    try
    {
        repository.Add(entity);
    }
    catch(MyRepositoryException ex)
    {
        ViewData.ModelState.AddModelError(ex.Key, ex.Value.ToString(), ex.Message)
    }
    
    if (!ModelState.IsValid)
                return Json(Failure(createView, model.SelectLists(repository)));
    
like image 117
Luke Avatar answered Oct 04 '22 03:10

Luke


An alternative to your approach would be to use the idea of a ModelStateWrapper implementing IValidationDictionary. It basically decouples the modelState, but still lets your repository/service interact with errors dictionary. This way error handling is all done via an interface and there's no need to reference any MVC-specific data object.

There's a good writeup on it here: http://www.asp.net/mvc/tutorials/validating-with-a-service-layer-cs, but the basic idea is:

1) Pass an instance of your ModelStateWrapper to your repository during the controller's initialization:

public MyController() 
{
    repository = new MyRepository(new ModelStateWrapper(this.ModelState));
}

2) Add errors to it inside your repository:

_validatonDictionary.AddError("Name", "Name is required.");

3) Handle errors like you normally would in your controller:

if (!repository.Save())
    return View();
like image 37
TheRightChoyce Avatar answered Oct 04 '22 03:10

TheRightChoyce


I hate answering my own questions, but I think I stumbled across the answer I was looking for while searching for something else:

http://nerddinnerbook.s3.amazonaws.com/Part3.htm

Looks like it was time for a back-to-basics review! I should have thought to go back and review my first tutorial as there was no way I was absorbing everything when I was first starting out.

Part 3 of the tutorial talks about implementing domain model validation that returns errors with property name and error message strings that are to be added to the controller's ModelState, which allows for this kind of validation:

if (ModelState.IsValid) {

    try {
        dinner.HostedBy = "SomeUser";

        dinnerRepository.Add(dinner);
        dinnerRepository.Save();

        return RedirectToAction("Details", new{id=dinner.DinnerID});
    }
    catch {
        ModelState.AddModelErrors(dinner.GetRuleViolations());
    }
}

I don't know if I like the idea of raising exceptions for business rule violations, but the basic pattern will work well for my project. Hope this helps someone else!

like image 35
Jeff Camera Avatar answered Oct 04 '22 02:10

Jeff Camera