2

I'm exploring using FluentValidation as it seems to be an elegant API for validation of my ViewModels upon model binding. I'm looking for opinions on how to properly centralize validation using this library as well as from my business (service) layer and raise it up to the view without having 2 different approaches to adding modelstate errors.

I'm open to using an entirely different API but essentially looking to solve this branching validation strategy.

[Side Note: One thing I tried was to move my business method into my FluentValidation's custom RsvpViewModelValidator class and using the .Must method but it seemed wrong to hide that call in there because if I needed to actually use my Customer object they I would have to re-query it again since its out of scope]

Sample Code:

[HttpPost]
public ActionResult AcceptInvitation(RsvpViewModel model)
{
    //FluentValidation has happened on my RsvpViewModel already to check that 
    //RsvpCode is not null or whitespace
    if(ModelState.IsValid)
    {
        //now I want to see if that code matches a customer in my database.
        //returns null if not, Customer object if existing
        customer = _customerService.GetByRsvpCode(model.RsvpCode);
        if(customer == null)
        {
            //is there a better approach to this?  I don't like that I'm
            //splitting up the validation but struggling up to come up with a 
            //better way.
            ModelState.AddModelError("RsvpCode", 
                string.Format("No customer was found for rsvp code {0}", 
                              model.RsvpCode);

            return View(model);
        }

        return this.RedirectToAction(c => c.CustomerDetail());
    }

    //FluentValidation failed so should just display message about RsvpCode 
    //being required
    return View(model);
}

[HttpGet]
public ActionResult CustomerDetail()
{
     //do work.  implementation not important for this question.
}
7
  • 1
    You could extract that kind of decision to a service layer, but i think you're looking for the white elephant. Don't confuse business/process logic with validation logic; a model can be valid without it making a whole lot of sense in the database. Commented Apr 7, 2013 at 1:39
  • I agree with your statement about the differences in logic. I guess I'm wondering is...do you think the code I have above is correct or would you approach things differently? My question is kind of a wide open 'What Would You Do' in terms of getting messages back to the view for validation and business logic together. The code above is the best approach I could think to come away with. Commented Apr 7, 2013 at 1:48
  • 1
    I'm always working with N-Tier, so I'd probbaly have an invitationService.Accept() (or whatever kind of service would describe the process) method that either passed/failed, and that's where the logic would be housed, but without the service, the next best place is within the controller. personally I'd say you'd done it the best was possible given the circumstance. Commented Apr 7, 2013 at 2:05
  • Ok, I'm aiming to build an architecture that enforces good separation of concerns as well (hence _customerService). The code was based on an example problem but it i'm not confined to it as I just wrote it up for this question. So, using your example of invitationService.Accept(), would this be void and throw exceptions or do you return something like an IList<Error> out of it to populate ModelState or something else. Just picking your brain a little more. Sorry, I'm not trying to drag this out. Commented Apr 7, 2013 at 2:30
  • If I minded Q&A, I wouldn't be a frequent user here on SO, so ask away! -- To answer your question, I bounce depending on what the method does. Some return an enum status (almost like membership create), some return an int with status code, some have out parameters, and some throw exceptions. (this is across the various one-off projects I've done). personally I think having a ProjectNameException base that I can have validation exceptions spawn from is the better way (usually in the infra/core lib). Commented Apr 7, 2013 at 2:34

2 Answers 2

2

To give some closure to the question (and make it acceptable) as well as summarize the comments:

Business/process logic and validation logic are two entities. Unless the validation ties in to the database (e.g. check for unique entries) there's no reason to group validation into one location. Some are responsible in the model making sure there's nothing invalid about the information, and some handle how the validated values are used within the system. Think of it in terms of property getters/setters vs the logic used in the methods with those properties.

That being said, separating out the processes (checks, error handling, etc.--anything not relating to UI) can be done in a service layer which also tends to keep the application DRY. Then the action(s) is/are only responsible for calling and presenting and not performing the actual unit of work. (also, if various actions in your application use similar logic, the checks are all in one location instead of throw together between actions. (did I remember to check that there's an entry in the customer table?))

Also, by breaking it down in to layers, you're keeping concerns modular and testable. (Accepting an RSVP isn't dependent on an action in the UI, but now it's a method in the service, which could be called by this UI or maybe a mobile application as well).

As far as bubbling errors up, I usually have a base exception that transverses each layer then I can extend it depending on purpose. You could just as easily use Enums, Booleans, out parameters, or simply a Boolean (the Rsvp either was or wasn't accepted). It just depends on how finite a response the user needs to correct the problem, or maybe change the work-flow so the error isn't a problem or something that the user need correct.

Sign up to request clarification or add additional context in comments.

Comments

0

You can have the whole validation logic in fluent validation:

public class RsvpViewValidator : AbstractValidator<RsvpViewModel>
{
    private readonly ICustomerService _customerService = new CustomerService();
    public RsvpViewValidator()
    {
        RuleFor(x => x.RsvpCode)
            .NotEmpty()
            .Must(BeAssociatedWithCustomer)
            .WithMessage("No customer was found for rsvp code {0}", x => x.RsvpCode)
    }

    private bool BeAssociatedWithCustomer(string rsvpCode)
    {
        var customer = _customerService.GetByRsvpCode(rsvpCode);
        return (customer == null) ? false : true;
    }
}

1 Comment

This was part of my original question and an approach I've done (see "Side Note"). I agree that it technically works but it doesn't sit well with me yet from a best practice standpoint. I worry that following this pattern could leak too much business logic / process into my ViewModel validators just to get messages into ModelState therefore making it hard to decide what calls go where. I'm open to hearing your thoughts though if you would like to share more.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.