Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CompareAttribute for case insensitive comparison

I am using CompareAttribute in MVC3 and its working fine. But I want to use case insensitive classCode. Is there any way to get that working

Thanks in Advance

[CompareAttribute("ClassCode", ErrorMessageResourceName = "ClassCode_DontMatch", ErrorMessageResourceType = typeof(Resources.Class))]
public string ConfirmClassCode {get; set; }
like image 856
Scorpion Avatar asked Oct 10 '12 14:10

Scorpion


2 Answers

A little late to the party, but here is an implementation I just wrote that also includes support for client-side validation using the IClientValidatable interface. You could use Darin Dimitrov's answer as a starting point as well, I just already had some of this.

Server-Side Validation:

//Create your custom validation attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class CompareStrings : ValidationAttribute, IClientValidatable
{
    private const string _defaultErrorMessage = "{0} must match {1}";

    public string OtherPropertyName { get; set; }

    public bool IgnoreCase { get; set; }

    public CompareStrings(string otherPropertyName)
        : base(_defaultErrorMessage)
    {
        if (String.IsNullOrWhiteSpace(otherPropertyName)) throw new ArgumentNullException("OtherPropertyName must be set.");

        OtherPropertyName = otherPropertyName;
    }

    public override string FormatErrorMessage(string name)
    {
        return String.Format(ErrorMessage, name, OtherPropertyName);
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        string otherPropVal = validationContext.ObjectInstance.GetType().GetProperty(OtherPropertyName).GetValue(validationContext.ObjectInstance, null) as string;

        //Convert nulls to empty strings and trim spaces off the result
        string valString = (value as string ?? String.Empty).Trim();
        string otherPropValString = (otherPropVal ?? String.Empty).Trim();

        bool isMatch = String.Compare(valString, otherPropValString, IgnoreCase) == 0;

        if (isMatch)
            return ValidationResult.Success;
        else
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
    }

Client-Side Validation

    //...continuation of CompareStrings class
    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        return new[] { new ModelClientValidationCompareStringsRule(FormatErrorMessage(metadata.GetDisplayName()), OtherPropertyName, IgnoreCase) };
    }
}

Define ModelClientValidationCompareStringsRule which is used (above) to pass the attribute's properties to the client-side script.

public class ModelClientValidationCompareStringsRule : ModelClientValidationRule
{
    public ModelClientValidationCompareStringsRule(string errorMessage, string otherProperty, bool ignoreCase)
    {
        ErrorMessage = errorMessage;  //The error message to display when invalid. Note we used FormatErrorMessage above to ensure this matches the server-side result.
        ValidationType = "comparestrings";    //Choose a unique name for your validator on the client side. This doesn't map to anything on the server side.
        ValidationParameters.Add("otherprop", otherProperty);  //Pass the name of the property to compare to
        ValidationParameters.Add("ignorecase", ignoreCase.ToString().ToLower());  //And whether to ignore casing
    }
}

Javascript:

(function ($) {
    //Add an adapter for our validator. This maps the data from the ModelClientValidationCompareStringsRule
    //we defined above, to the validation plugin. Make sure to use the same name as we chose for the ValidationType property ("comparestrings")
    $.validator.unobtrusive.adapters.add("comparestrings", ["otherprop", "ignorecase"],
        function (options) {
            options.rules["comparestrings"] = {
                otherPropName: options.params.otherprop,
                ignoreCase: options.params.ignorecase == "true"
            };
            options.messages["comparestrings"] = options.message;
        });

    //Add the method, again using the "comparestrings" name, that actually performs the client-side validation to the page's validator
    $.validator.addMethod("comparestrings", function (value, element, params) {
        //element is the element we are validating and value is its value

        //Get the MVC-generated prefix of element
        //(E.G. "MyViewModel_" from id="MyViewModel_CompareEmail"
        var modelPrefix = getModelIDPrefix($(element).prop("id"));

        //otherPropName is just the name of the property but we need to find
        //its associated element to get its value. So concatenate element's
        //modelPrefix with the other property name to get the full MVC-generated ID. If your elements use your own, overridden IDs, you'd have to make some modifications to allow this code to find them (e.g. finding by the name attribute)
        var $otherPropElem = $("#" + modelPrefix + params.otherPropName);

        var otherPropValue = getElemValue($otherPropElem);

        //Note: Logic for comparing strings needs to match what it does on the server side

        //Trim values
        value = $.trim(value);
        otherPropValue = $.trim(otherPropValue);

        //If ignoring case, lower both values
        if (params.ignoreCase) {
            value = value.toLowerCase();
            otherPropValue = otherPropValue.toLowerCase();
        }

        //compare the values
        var isMatch = value == otherPropValue;

        return isMatch;
    });

    function getElemValue(element){
        var value;
        var $elem = $(element);

        //Probably wouldn't use checkboxes or radio buttons with
        //comparestrings, but this method can be used for other validators too
        if($elem.is(":checkbox") || $elem.is(":radio") == "radio")
            value = $elem.prop("checked") ? "true" : "false";
        else
            value = $elem.val();

        return value;
    }

    //Gets the MVC-generated prefix for a field by returning the given string
    //up to and including the last underscore character
    function getModelIDPrefix(fieldID) {
        return fieldID.substr(0, fieldID.lastIndexOf("_") + 1);
    }
}(jQuery));

Usage is standard:

public string EmailAddress { get; set; }

[CompareStrings("EmailAddress", ErrorMessage = "The email addresses do not match", IgnoreCase=true)]
public string EmailAddressConfirm { get; set; }

This plugs into the Unobtrusive Validation framework, so you need to already have that installed and working. At the time of writing I am on Microsoft.jQuery.Unobtrusive.Validation v 3.0.0.

like image 118
xr280xr Avatar answered Nov 02 '22 15:11

xr280xr


You could write a custom attribute that will perform the case insensitive comparison:

public class CaseInsensitiveCompareAttribute : System.Web.Mvc.CompareAttribute
{
    public CaseInsensitiveCompareAttribute(string otherProperty)
        : base(otherProperty)
    { }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectType.GetProperty(this.OtherProperty);
        if (property == null)
        {
            return new ValidationResult(string.Format(CultureInfo.CurrentCulture, "Unknown property {0}", this.OtherProperty));
        }
        var otherValue = property.GetValue(validationContext.ObjectInstance, null) as string;
        if (string.Equals(value as string, otherValue, StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }

        return new ValidationResult(this.FormatErrorMessage(validationContext.DisplayName));
    }
}

and then decorate your view model property with it:

[CaseInsensitiveCompare("ClassCode", ErrorMessageResourceName = "ClassCode_DontMatch", ErrorMessageResourceType = typeof(Resources.Class))]
public string ConfirmClassCode { get; set; }
like image 34
Darin Dimitrov Avatar answered Nov 02 '22 14:11

Darin Dimitrov