Partial forms validation in ASP.NET MVC 2 (something like WebForms ValidationGroup)
In one of my recent ASP.NET MVC 2 projects I had to make a form divided into few tabs. Each tab had to be validated before user can move to next one. Unfortunately ASP.NET MVC 2 built in validation doesn't provide mechanism for this. Well we are developers, so if there is no built in mechanism, we should create our own. First lets create a ValidationAttribute, which will be used to add information about validation group:
We will also need a DataAnnotationsModelValidator for this attribute:
Don't forget to register above classes in your Global.asax:
So this is all we need on server side (please remember, that we don't want to achieve any server side logic, only partial validation on client side before posting the whole form). To make it work on client side, first we need to modify Sys.Mvc.FormContext._parseJsonOptions in MicrosoftMvcValidation.debug.js (or corresponding part in MicrosoftMvcValidation.js):
Now we can write ourselves function, which will perform partial validation based on group name:
And our job is done. Now we can call Sys.Mvc.FormContext.validateGroup('formId', 'validationGroup'); whenever we want to perform partial validation. I have created a sample application which make use of those modification, you can download it from my repository. If there is a need for same modification for jQuery validation, let me know and I will look into it.
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] public class ValidationGroupAttribute : ValidationAttribute { #region Properties /// <summary> /// Name of the validation group /// </summary> public string GroupName { get; set; } #endregion #region Constructor public ValidationGroupAttribute(string groupName) { GroupName = groupName; } #endregion #region Methods public override bool IsValid(object value) { //No validation logic, always return true return true; } #endregion }
We will also need a DataAnnotationsModelValidator for this attribute:
public class ValidationGroupValidator : DataAnnotationsModelValidator<ValidationGroupAttribute> { #region Fields string _groupName; #endregion #region Constructor public ValidationGroupValidator(ModelMetadata metadata, ControllerContext context, ValidationGroupAttribute attribute) : base(metadata, context, attribute) { _groupName = attribute.GroupName; } #endregion #region Methods public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() { //Add informations about validation group to metadata var validatioRule = new ModelClientValidationRule { ErrorMessage = String.Empty, ValidationType = "validationGroup" }; validatioRule.ValidationParameters.Add("groupName", _groupName); return new[] { validatioRule }; } #endregion }
Don't forget to register above classes in your Global.asax:
DataAnnotationsModelValidatorProvider.RegisterAdapter(typeof(ValidationGroupAttribute), typeof(ValidationGroupValidator));
So this is all we need on server side (please remember, that we don't want to achieve any server side logic, only partial validation on client side before posting the whole form). To make it work on client side, first we need to modify Sys.Mvc.FormContext._parseJsonOptions in MicrosoftMvcValidation.debug.js (or corresponding part in MicrosoftMvcValidation.js):
Sys.Mvc.FormContext._parseJsonOptions = function Sys_Mvc_FormContext$_parseJsonOptions(options) { var formElement = $get(options.FormId); var validationSummaryElement = (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(options.ValidationSummaryId)) ? $get(options.ValidationSummaryId) : null; var formContext = new Sys.Mvc.FormContext(formElement, validationSummaryElement); formContext.enableDynamicValidation(); formContext.replaceValidationSummary = options.ReplaceValidationSummary; for (var i = 0; i < options.Fields.length; i++) { var field = options.Fields[i]; var fieldElements = Sys.Mvc.FormContext._getFormElementsWithName(formElement, field.FieldName); var validationMessageElement = (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(field.ValidationMessageId)) ? $get(field.ValidationMessageId) : null; var fieldContext = new Sys.Mvc.FieldContext(formContext); Array.addRange(fieldContext.elements, fieldElements); fieldContext.validationMessageElement = validationMessageElement; fieldContext.replaceValidationMessageContents = field.ReplaceValidationMessageContents; for (var j = 0; j < field.ValidationRules.length; j++) { var rule = field.ValidationRules[j]; //Here goes our small modification if (rule.ValidationType == 'validationGroup') { fieldContext.validationGroup = rule.ValidationParameters['groupName']; } else { var validator = Sys.Mvc.ValidatorRegistry.getValidator(rule); if (validator) { var validation = Sys.Mvc.$create_Validation(); validation.fieldErrorMessage = rule.ErrorMessage; validation.validator = validator; Array.add(fieldContext.validations, validation); } } } fieldContext.enableDynamicValidation(); Array.add(formContext.fields, fieldContext); } var registeredValidatorCallbacks = formElement.validationCallbacks; if (!registeredValidatorCallbacks) { registeredValidatorCallbacks = []; formElement.validationCallbacks = registeredValidatorCallbacks; } registeredValidatorCallbacks.push(Function.createDelegate(null, function () { return Sys.Mvc._validationUtil.arrayIsNullOrEmpty(formContext.validate('submit')); })); return formContext; }
Now we can write ourselves function, which will perform partial validation based on group name:
Sys.Mvc.FormContext.validateGroup = function Sys_Mvc_FormContext$validateGroup(formId, groupName) { //Get form element var formElement = $get(formId); //Get form context var formContext = Sys.Mvc.FormContext.getValidationForForm(formElement); //Get form fields var fields = formContext.fields; //Array for errors var errors = []; //For each field for (var i = 0; i < fields.length; i++) { var field = fields[i]; //If field has validation group and its name matches the one we are looking for if (field.validationGroup && field.validationGroup == groupName) { //Validate field var fieldErrors = field.validate('submit'); if (fieldErrors) { Array.addRange(errors, fieldErrors); } } } //Return true it there are no errors, otherwise false return (!errors || !errors.length); }
And our job is done. Now we can call Sys.Mvc.FormContext.validateGroup('formId', 'validationGroup'); whenever we want to perform partial validation. I have created a sample application which make use of those modification, you can download it from my repository. If there is a need for same modification for jQuery validation, let me know and I will look into it.