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:
[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.