Click here to Skip to main content
15,902,114 members
Articles / Fluent

How To Master Complex Scenarios Using Fluent Validation

Rate me:
Please Sign up or sign in to vote.
5.00/5 (2 votes)
23 Mar 2017CPOL4 min read 14.7K   1   1
Part 3 of a series on configuring and using Fluent Validation and Autofac

This is part 3 of a series on configuring and using Fluent Validation and Autofac. Check out the other parts here:

Feedback is an important part of a good user experience. It is a normal, everyday part of using a website. Whenever we fill in a form, we expect to see some. Success messages. Error messages if we fill in the form wrong or we miss some stuff out. Most validation rules are simple. An empty field. An invalid email address. Not always, though. How do we deal with more complex scenarios?

In the last article, we looked at the simple scenarios, which map nicely to their Data Annotations equivalents. Now, we'll examine more complex scenarios. If you were using Data Annotations, you might think about adding this logic to your controller actions. With Fluent Validation, you can keep it all in the one place. This also means you can test it all together, as we'll see in the next article.

So far, we've seen methods like NotNull, NotEmpty, Length and EmailAddress. The two we'll focus on here are Must and Custom. Must works at the property level, much like these others. It’s a lot more flexible though. Let's look at an example. In our example, we've got a form and need to validate a URL. Users must enter a fully qualified URL or it’s not valid.

Now, we could use a regular expression here. I like to avoid regular expressions though, if I can. They're complicated. Using them means, at best, adding comments explaining what they do. At worst, there are no comments either. This means you either understand them or you don't. If you don't, you may end up glossing over them and hoping they never go wrong! I was a Perl developer many moons ago, so I've seen my fair share of them.

If we're not using a regular expression, where does that leave us? Well, we could try to create a URL from the input string, using Uri.TryCreate. Let’s put it all together and see how it looks. We’re building on the validation rules we set up last time. If you haven’t seen the previous article, take a look here: How To Easily Set Up Fluent Validation With Autofac

We Must Get Down To Business

Cheap puns aside, let’s add a property to our RegisterFluentViewModel:

C#
public string BlogUrl { get; set; }

Now, we’ll add a rule to the RegisterFluentViewModelValidator:

C#
RuleFor(m => m.BlogUrl)
    .NotEmpty()
    .WithMessage("Please enter a URL")
    .Must(BeAValidUrl)
    .WithMessage("Invalid URL. Please re-enter")
C#
private static bool BeAValidUrl(string arg)
{
    Uri result;
    return Uri.TryCreate(arg, UriKind.Absolute, out result);
}

View the original article.

Must accepts a Func that returns a bool. If the Func returns false, validation fails for that rule. Must has an overload that accepts the entire model as well as the property you’re validating. Let’s see that in action. Here's an example from a project I worked on recently. The form has a textbox for comments and a checkbox. If the checkbox is unchecked, the textbox can be empty. Otherwise, you have to enter at least 50 chars of text.

C#
RuleFor(m => m.Comments)
    .Must(BeValidComments)
    .WithMessage("Please enter at least 50 characters of comments. 
                  If you have nothing to say, please check the checkbox.");
C#
private static bool BeValidComments(RegisterFluentViewModel model, string comments)
{
    if (model.NoComment) return true;
    return comments != null && comments.Length >= 50;
}

Some Customs Are More Complex Than Others

Custom is slightly different, in that it tends to be used for tricky scenarios. If you were pulling data back from the database to compare against, you’d be better off using a custom validation rule. We’re just going to rewrite the same rule above using Custom. We’ll use different ViewModel properties, though:

C#
public string OtherComments { get; set; }
public bool NoOtherComment { get; set; }

Here’s the validation rule itself. Pop this into the constructor of the RegisterFluentViewModelValidator:

C#
Custom(OtherCommentsMustBeValid);
C#
private static ValidationFailure OtherCommentsMustBeValid(RegisterFluentViewModel model)
{
    if (model.NoOtherComment) return null;
    return model.OtherComments != null && model.OtherComments.Length >= 50 ? null : 
        new ValidationFailure("OtherComments", "Please enter at least 50 characters of comments. 
                               If you have nothing to say, please check the checkbox.");
}

The only difference here is that we return a ValidationFailure object if validation fails. If it is successful, we return null. The first parameter of the ValidationFailure is the name of the property being validated. This just scratches the surface of the different kinds of rules you can write.

What are the most complicated rules you have to validate against? Let me know in the comments below!

Giving Our Custom Validation Rule Client-Side Super Powers

One problem you’ll encounter with custom validators is that they don’t support client-side validation. If you use the basic validators on the same form, you’ll have a mix of client and server-side validation. That won’t be a great user experience. How do we get around that?

We need to create a custom property validator. We’ll then be able to generate the appropriate client-side hooks. Jerrie Pelser has written a great blog post about this here: Remote Client Side Validation with FluentValidation

There’s also a top quality Stack Overflow answer from Darin Dimitrov here: unobtrusive client validation using fluentvalidation.

View the original article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead Levelnis Ltd
United Kingdom United Kingdom
Follow along my journey as I create a newsletter about launching websites. Just message me with "I'm in" and I'll add you

Comments and Discussions

 
GeneralMy vote of 5 Pin
DavisGaudry24-Mar-17 21:15
DavisGaudry24-Mar-17 21:15 
Generalnice article Pin
Member 1308104623-Mar-17 20:13
Member 1308104623-Mar-17 20:13 
GeneralMessage Closed Pin
24-Mar-17 0:00
Member 1308155724-Mar-17 0:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.