Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET MVC: Implement client side validation with attribute without IClientValidatable

How can I create a custom validation attribute with client side validation without implementing IClientValidatable?

How does System.ComponentModel.DataAnnotations.RequiredAttribute client side validate?

The reason to do this is because I'm using objects from classes in another project as models in my views and I don't want to add the System.Web.MVC reference to that project.

EDIT to add more information:

  • I know that IClientValidatable is used to add custom attributes to the HTML to be used later by the unobtrusive validation.

  • I know I'll need to add the javascript code to made the validation in the client.

What I don't know is how to use the information from the custom validation attribute to add the necessary attributes to the HTML for unobtrusive validation to work.

This is my custom validation attribute:

public class RequiredGuidAttribute : ValidationAttribute
{
    public override bool IsValid(object value)
    {
        Guid? guidValue = value as Guid?;

        if (guidValue == null)
            return false;

        return guidValue != Guid.Empty;
    }
}

This is my property with the attribute applied:

    [RequiredGuid(ErrorMessageResourceType = typeof(ClientOrderResources), ErrorMessageResourceName = "RequiredShippingMethod")]
    public Guid ShippingMethodId
    {
        get { return GetProperty(ShippingMethodIdProperty); }
        set { SetProperty(ShippingMethodIdProperty, value); }
    }

And finally I'm rendering a hidden input for that property in the view using Html.HiddenFor.

Now, how can I get the error message from the attribute to apply it to the HTML? Should I do it my self using Reflection or there is a better way?

And then how can I tell Html.HiddenFor to use that information to add the necessary attributes to the HTML?

like image 445
Marques Avatar asked Jan 11 '13 17:01

Marques


1 Answers

We had a similar problem. We have a model we use for our account creation that uses IClientValidatable on its custom attributes. However, we created a batch account creation process that sits outside of the website that we weren't able to reference System.Web.Mvc in. Because of this, when we called Validator.TryValidateObject, any custom validator that inherited from IClientValidatable was simply skipped. Here's what we were working with that was failing to validate outside of our website:

public class AgeValidatorAttribute : ValidationAttribute, IClientValidatable
{
    public int AgeMin { get; set; }
    public int AgeMax { get; set; }

    public override bool IsValid(object value)
    {
        //run validation
    }
}

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = ErrorMessageString,
            ValidationType = "agevalidator"
        };

        rule.ValidationParameters["agemin"] = AgeMin;
        rule.ValidationParameters["agemax"] = AgeMax;

        yield return rule;
    }

Removing System.Web.Mvc required us to also remove GetClientValidationRules and the IClientValidatable reference. In order to do this and still have client side validation, we had to create a new class:

public class AgeValidatorClientValidator : DataAnnotationsModelValidator<AgeValidatorAttribute>
{
    private readonly string _errorMessage;
    private readonly string _validationType;

    public AgeValidatorClientValidator(ModelMetadata metadata, ControllerContext context, AgeValidatorAttribute attribute)
        : base(metadata, context, attribute)
    {
        this._errorMessage = attribute.FormatErrorMessage(metadata.DisplayName);
        this._validationType = "agevalidator";
    }

    public override IEnumerable<ModelClientValidationRule> GetClientValidationRules()
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = this._errorMessage,
            ValidationType = this._validationType
        };

        rule.ValidationParameters["agemin"] = base.Attribute.AgeMin;
        rule.ValidationParameters["agemax"] = base.Attribute.AgeMax;

        yield return rule;
    }
}

As you can see, it does essentially the same thing as it used to, it's just done using the DataAnnatotationsModelValidator rather than IClientValidatable. There's one more step we need to do to actually attach the DataAnnotationsModelValidator to the atttribute, and that's done in the Global.asax.cs Application_Start method

DataAnnotationsModelValidatorProvider.RegisterAdapter( typeof(AgeValidatorAttribute), typeof(AgeValidatorClientValidator));

Now you can use this just as you would use a normal attribute:

[AgeValidator(AgeMax = 110, AgeMin = 18, ErrorMessage = "The member must be between 18 and 110 years old")]
public string DateOfBirth { get; set; }

I know this question is a year old, but I spent all day yesterday and half of today trying to figure this issue out. So I hope this helps somebody who runs into the same problem if OP hasn't figured the answer out yet.

Please note, I did not include any javascript in this writeup as it required no changes from the standard implementation of custom validation rules using jQuery.validate.

like image 160
Zack Huber Avatar answered Sep 21 '22 21:09

Zack Huber