简体   繁体   中英

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

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!

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

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!

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM