Yesterday we discussed about Unobtrusive Client and Server Side Age Validation now let’s do a not equal to validator which is basically the opposite of the Compare validator.
So where can this be useful?
If you don’t want two items to have the same value then this is your solution. Lets say you don’t want to have same answers on two different text boxes or you don’t want two same selections on dropdowns. One practical application is in Secret Question forms on sign up like the one below
Now lets start, first lets create your own custom validation, let us call it UnlikeAttribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class UnlikeAttribute : ValidationAttribute, IClientValidatable { private const string DefaultErrorMessage = "The value of {0} cannot be the same as the value of the {1}."; public string OtherProperty { get; private set; } public string OtherPropertyName { get; private set; } public UnlikeAttribute( string otherProperty, string otherPropertyName) : base(DefaultErrorMessage) { OtherProperty = otherProperty; OtherPropertyName = otherPropertyName; } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (value != null) { var otherProperty = validationContext.ObjectInstance.GetType().GetProperty(OtherProperty); var otherPropertyValue = otherProperty.GetValue(validationContext.ObjectInstance, null); if (value.Equals(otherPropertyValue)) { return new ValidationResult(FormatErrorMessage(validationContext.DisplayName)); } } return ValidationResult.Success; } public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context) { var rule = new ModelClientValidationRule() { ValidationType = "unlike", ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()), }; rule.ValidationParameters.Add("otherproperty", OtherProperty); rule.ValidationParameters.Add("otherpropertyname", OtherPropertyName); yield return rule; } public override string FormatErrorMessage(string name) { return string.Format(ErrorMessageString, name, OtherPropertyName); } }
In this class you notice you have three methods, first is IsValid. We override this method from the ValidationAttribute and this basically validates the specified value with respect to the current validation attribute, it outputs an instance of the System.ComponentModel.DataAnnotations.ValidationResult class. The next method is FormatErrorMessage which is from the same ValidationAttribute overriden class, as the name suggests this gets or sets an error message to associate with a validation control if validation. Now we have GetClientValidationRules which come from IClientValidatable where it provides a way for the ASP.NET MVC validation framework to discover at run time whether a validator has support for client validation.
Now let use this in your view model
Lets say your Secret Question Field day field is called SecretQuestion1Id and SecretQuestion2Id, we want it to be required and not to contain the same value
[DisplayName("First Secret Question")] [Required] public int SecretQuestion1Id { get; set; } [DisplayName("Second Secret Question")] [Required] [Unlike("SecretQuestion1Id", "First Secret Question")] public int SecretQuestion2Id { get; set; }
Now if you notice you have 2 parameters in
Unlike, first is SecretQuestion1Id, this is where you compare this element with. Second is just defines the name of the element this is compared with. They are then mapped to otherProperty and otherPropertyName of your UnlikeAttribute class. You will also notice we use Unlike and not UnlikeAttribute, you can use both as it auto maps with the class.
At this point your server-side validation will now work all you need to do is before doing anything check the ModelState in your controller like this
[AcceptVerbs(HttpVerbs.Post)] [Transaction] public ActionResult CreateAccount(RegisterPageViewModel viewModel) { var errorMessage = string.Empty; if (ModelState.IsValid) { var newExtendedUserDetails = activeDirectoryTasks.CreateUser( viewModel.FirstName, viewModel.LastName, viewModel.Email, viewModel.BirthDate.Value, viewModel.SecretQuestion1Id, viewModel.SecretQuestionAnswer1, viewModel.SecretQuestion2Id, viewModel.SecretQuestionAnswer2, out errorMessage ); if (errorMessage == string.Empty) { //Success viewModel = registerQuery.GetSuccessPageViewModel(viewModel); return View("Success", viewModel); } //Error on Save viewModel = registerQuery.GetErrorPageViewModel(viewModel); return View("Error", viewModel); } //Validation Error viewModel = registerQuery.GetRegisterPageViewModel(viewModel); return View("Index", viewModel); }
So if you turn off your JavaScript and run then it should throw a validation error like such.
Now lets enable your Unobtrusive Validation, first is you need to create a separate js file. I called mine custom-validators.js and I include all my custom validators there. Inside that fill would be something like this
// The unlike function $.validator.addMethod( 'unlike', function (value, element, params) { console.debug(element); if (!this.optional(element)) { var otherProperty = $('#' + params.otherproperty) return (otherProperty.val() != value); } return true; }); $.validator.unobtrusive.adapters.add( 'unlike', ['otherproperty', 'otherpropertyname'], function (options) { var params = { otherproperty: options.params.otherproperty, otherpropertyname: options.params.otherpropertyname }; options.rules['unlike'] = params; options.messages['unlike'] = options.message; });
First function just adds the method and the second adds the unobtrusive adapter. Take note of the names as this is the one you declared early on your ModelClientValidationRule on the UnlikeAttribute.
Next is to make sure you reference to the following JavaScript in this specific order.
<script src="@Url.Content("~/Scripts/jquery-2.0.3.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> <script src="@Url.Content("~/Scripts/custom-validators.js")" type="text/javascript"></script>
And if you are using Bundle Collections then it will be like this
bundles.Add(new ScriptBundle("~/bundles/jquery") .Include("~/scripts/jquery-2.0.3.min.js") .Include("~/scripts/jquery.validate.min.js") .Include("~/scripts/jquery.validate.unobtrusive.min.js") .Include("~/scripts/custom-validators.js") );
and if you’re using Bundle Collection clear the default ordering as it will sort it alphabetically, you can do so by calling FileSetOrderList.Clear() after all the bundles have been created.
bundles.FileSetOrderList.Clear();
Dont also forget to put this on your web.config
<appSettings> <add key="ClientValidationEnabled" value="true" /> <add key="UnobtrusiveJavaScriptEnabled" value="true" />
Now lets see if everything works fine, code behind should show your data-val’s created from your attribute
And it should do its validation
Pingback: How to compare a model property for No Equality with multiple fields in Model in MVC | FYTRO SPORTS