Purpose
Generics and Lambda expressions can be harnessed (in the Fluent Paradigm) for building a validation framework. MyFluent is my implementation of such a validation framework.
The source code is available free for download and you can build and add your own Rules and extend the framework.
Implementation
The Set<T, TProperty> is the base class for RuleSet<T, TProperty> and ConditionSet<T, TProperty>.
RuleSet<T, TProperty> contains the rules that can be applied.
ConditionSet<T, TProperty> contains the RuleSets that are to be applied if the Condition is true. It also holds the Condition specified as Func<TProperty, bool> .
In the RuleBuilder<T>, RuleSets and ConditionSets are stored in a static List<Set<T, object>> sets variable.
In the Validate method of the Validator<T>, the RuleSets and ConditionSets from the sets variable are evaluated against the instance of the object to validate.
BaseValidator<TValidator, T> creates a singleton instance of the validator, which is then re-used.
The MVC Model validation (client-side) is done by generating jQuery rules by the helper (MyFluentClientValidation). The rules hook into the jQuery validation plug-in.
The following built-in Rules are available:
* To be used in server-side validation for creating custom validations.
** To be used in MVC Model validation for creating custom validations.
The following class diagram explains the framework:
Usage
Server side validation
Suppose we had to validate an object of class ObjectToValidate which contains a complex object Employee.
First, add a reference to MyFluent.dll. Then create a validator (SampleValidator) and put the Rules in the constructor.
- using MyFluent;
-
- public class SampleValidator : BaseValidator<SampleValidator, ObjectToValidate>
- {
- public SampleValidator()
- {
- RuleFor(obj => obj.Employee)
- .NotNull("Employee cannot be null");
-
- If(obj => obj.Employee.Language, language => language == Language.Bengali)
- .ConditionRuleFor(obj => obj.Employee.Name)
- .NotNullOrEmpty("Name not null or empty")
- .IsAlpha(@"\u0985-\u09E3\s", "Employee name not Bengali letters with spaces")
- .Matches(@"^[\u0985-\u09E3\s]+$", "Employee name not Bengali letters with spaces")
- .ConditionRuleFor(obj => obj.Employee.Message)
- .IsAlpha(@"\u0985-\u09E3\s", "Employee message not Bengali letters with spaces");
-
- If(obj => obj.Employee.Language, language => language == Language.English)
- .ConditionRuleFor(obj => obj.Employee.Name)
- .NotNullOrEmpty("Name not null or empty")
- .IsAlpha(@"a-zA-Z\s", "Employee name not English letters with spaces")
- .Matches("^[a-zA-Z ]+", "Employee name not English letters with spaces")
- .ConditionRuleFor(obj => obj.Employee.Message)
- .IsAlpha(@"a-zA-Z\s", "Employee message not English letters with spaces");
-
- RuleFor(obj => obj.Employee.JobTypes)
- .NotNull("Employee Job types cannot be null")
- .Required(jobTypes => jobTypes.Count > 0, "Employee requires at least one Job type");
-
- RuleFor(obj => obj.Employee.CrediCardNo)
- .CreditCard("Invalid credit card no");
-
- RuleFor(obj => obj.Employee.Email)
- .Email("Invalid email address");
- }
- }
After we have created the validator we can do the validation as shown below:
- static void Main(string[] args)
- {
- ObjectToValidate objToValidate = new ObjectToValidate();
-
- objToValidate.Employee = new Employee
- {
- Language = TestMyFluent.Language.English,
-
- Name = "Shantanu",
-
-
- CrediCardNo = "5213291336202060",
- Email = "shantanu@@hotmail.com",
-
- Message = "Hello World",
-
- JobTypes = new List<JobType> {}
- };
-
- ValidationResult result = SampleValidator.Validate(objToValidate);
- }
ASP .NET MVC Model validation - client side
MyFluent provides an HtmlHelper to generate jQuery rules (based on Rules specified on the Model in the validator) and wire up the form submit.
Add references to MyFluent.dll and MyFluent.Mvc.dll to get the helper and the MvcBaseValidator<TValidator, T>.
E.g.:
Model
Create the validator for the Model.
- using MyFluent.Mvc;
-
- public class RegisterValidator : MvcBaseValidator<RegisterValidator, MvcApplication1.Models.RegisterModel>
- {
- public RegisterValidator()
- {
- RuleFor(m => m.UserName)
- .NotNullOrEmpty("User name cannot be null or empty")
- .Matches(@"^[a-zA-Z0-9]+$", "User name should only contain letters and numbers");
-
- RuleFor(m => m.Email)
- .NotNullOrEmpty("Email cannot be null or empty")
- .Email("Not a valid email address");
-
- RuleFor(m => m.Password)
- .NotNullOrEmpty("Password cannot be null or empty")
- .JQueryRequired("UserNameNotInPassword", "User name cannot be a part of the password");
-
- RuleFor(m => m.ConfirmPassword)
- .NotNullOrEmpty("Confirm password cannot be null or empty")
- .JQueryRequired("PasswordAndConfirmAreSame", "Password and confirm are not the same");
- }
- }
Then in the view (Register.cshtml),
- @model MvcApplication1.Models.RegisterModel
- @using MvcApplication1.Validators;
-
- @{
- ViewBag.Title = "Register";
- }
-
- <h2>Create a New Account</h2>
- <p>
- Use the following form to create a new account.
- </p>
- @*Include jQuery and jQuery validation plug-in*@
-
- <script src="@Url.Content("~/Scripts/jquery-1.4.4.js")" type="text/javascript"></script>
- <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
-
- <script type="text/javascript">
- function UserNameNotInPassword(password) {
- var userName = $("#UserName").val();
-
- return password.indexOf(userName) == -1;
- }
-
- function PasswordAndConfirmAreSame(confirmPassword) {
- var password = $("#Password").val();
-
- return password == confirmPassword;
- }
- </script>
-
- @*Call the helper*@
- @Html.MyFluentClientValidation(RegisterValidator.GetValidator())
-
- @using (Html.BeginForm())
- {
- @Html.ValidationSummary(true, "Account creation was unsuccessful. Please correct the errors and try again.")
- <div>
- <fieldset>
- <legend>Account Information</legend>
-
- <div class="editor-label">
- @Html.LabelFor(m => m.UserName)
- </div>
- <div class="editor-field">
- @Html.TextBoxFor(m => m.UserName)
- @Html.ValidationMessageFor(m => m.UserName)
- </div>
-
- <div class="editor-label">
- @Html.LabelFor(m => m.Email)
- </div>
- <div class="editor-field">
- @Html.TextBoxFor(m => m.Email)
- @Html.ValidationMessageFor(m => m.Email)
- </div>
-
- <div class="editor-label">
- @Html.LabelFor(m => m.Password)
- </div>
- <div class="editor-field">
- @Html.PasswordFor(m => m.Password)
- @Html.ValidationMessageFor(m => m.Password)
- </div>
-
- <div class="editor-label">
- @Html.LabelFor(m => m.ConfirmPassword)
- </div>
- <div class="editor-field">
- @Html.PasswordFor(m => m.ConfirmPassword)
- @Html.ValidationMessageFor(m => m.ConfirmPassword)
- </div>
-
- <p>
- <input type="submit" value="Register" />
- </p>
- </fieldset>
- </div>
- }
You can do custom validations using the JQueryRequired rule where you specify the function which will be called as a part of validation of the property. Eg. UserNameNotInPassword is a custom validation function which will be called whenever the Password validation occurs. This function must return true or false.
Conditional rules are not available in MVC Model validation.
You can attach the default submit handler which automatically triggers the jQuery validate method. Or you can create your own custom handler.
Screen shot: