mvc3 validate input 'not-equal-to'
Asked Answered
H

6

15

My forms have inputs with default helper text that guides the user on what to enter (rather than using labels). This makes validation tricky because the input value is never null.

How can I extend unobtrusive validation to handle this? The form shouldn't be valid if the Name input is equal to "Please enter your name..."

I started reading Brad Wilson's blog post on validation adapters, but I'm not sure if this is the right way to go? I need to be able to validate against different default values depending on the field.

Thanks

Haim answered 20/4, 2011 at 21:13 Comment(1)
Alternatively, consider using the placeholder attribute to display instruction text like "Please enter your name..." as a much friendlier UX and also HTML5 standards compliant way to provide a hint to the user.Cathycathyleen
T
3

Yes thats the right way to go. You should implement your own atribute and implement IClientValidatable.

You could also have a required boolean value set initially to false as a hidden form field. When the user changes the textbox, set it to true.

Trevatrevah answered 20/4, 2011 at 21:20 Comment(0)
B
34

Here's a sample illustrating how you could proceed to implement a custom validation attribute:

public class NotEqualAttribute : ValidationAttribute, IClientValidatable
{
    public string OtherProperty { get; private set; }
    public NotEqualAttribute(string otherProperty)
    {
        OtherProperty = otherProperty;
    }

    protected override ValidationResult IsValid(object value, ValidationContext validationContext)
    {
        var property = validationContext.ObjectType.GetProperty(OtherProperty);
        if (property == null)
        {
            return new ValidationResult(
                string.Format(
                    CultureInfo.CurrentCulture, 
                    "{0} is unknown property", 
                    OtherProperty
                )
            );
        }
        var otherValue = property.GetValue(validationContext.ObjectInstance, null);
        if (object.Equals(value, otherValue))
        {
            return new ValidationResult(FormatErrorMessage(validationContext.DisplayName));
        }
        return null;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = ErrorMessage,
            ValidationType = "notequalto",
        };
        rule.ValidationParameters["other"] = OtherProperty;
        yield return rule;
    }
}

and then on the model:

public class MyViewModel
{
    public string Prop1 { get; set; }

    [NotEqual("Prop1", ErrorMessage = "should be different than Prop1")]
    public string Prop2 { get; set; }
}

controller:

public class HomeController : Controller
{
    public ActionResult Index()
    {
        return View(new MyViewModel
        {
            Prop1 = "foo",
            Prop2 = "foo"
        });
    }

    [HttpPost]
    public ActionResult Index(MyViewModel model)
    {
        return View(model);
    }
}

and view:

@model MyViewModel

<script src="@Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
<script type="text/javascript">
    jQuery.validator.unobtrusive.adapters.add(
        'notequalto', ['other'], function (options) {
            options.rules['notEqualTo'] = '#' + options.params.other;
            if (options.message) {
                options.messages['notEqualTo'] = options.message;
            }
    });

    jQuery.validator.addMethod('notEqualTo', function(value, element, param) {
        return this.optional(element) || value != $(param).val();
    }, '');
</script>

@using (Html.BeginForm())
{
    <div>
        @Html.LabelFor(x => x.Prop1)
        @Html.EditorFor(x => x.Prop1)
        @Html.ValidationMessageFor(x => x.Prop1)
    </div>
    <div>
        @Html.LabelFor(x => x.Prop2)
        @Html.EditorFor(x => x.Prop2)
        @Html.ValidationMessageFor(x => x.Prop2)
    </div>
    <input type="submit" value="OK" />
}
Barclay answered 21/4, 2011 at 10:1 Comment(4)
Works like a charm, but the solution you provided can't use ErrorMessageResourceType and ErrorMessageResourceName. You could use something like: errorMessage = new ResourceManager(ErrorMessageResourceType).GetString(ErrorMessageResourceName);Tijuana
Great answer, very helpful to get me on the right track to do some custom validators.Harryharsh
I'm curious how to user an error message from a resource. What Rookian said isn't a constant expression, so it can't be used in the attribute. This solution devtrends.co.uk/blog/… works with the resource error message, but I can't see the difference. I think the view message isn't being hooked up correctly?Postobit
it works with EditorFor but not with DropDownList. it doesn't generate the data validtion messages. any idea what should I do?Exultant
T
3

Yes thats the right way to go. You should implement your own atribute and implement IClientValidatable.

You could also have a required boolean value set initially to false as a hidden form field. When the user changes the textbox, set it to true.

Trevatrevah answered 20/4, 2011 at 21:20 Comment(0)
O
0

You could make your ViewModel implement IValidatableObject and when implementing the Validate method (from IValidatableObject) add some logic to check the values of the properties e.g.

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
        var results = new List<ValidationResult>();

        if (Name == "Please enter your name...") 
            results.Add(new ValidationResult("You must enter a name");

        ...
        Enter other validation here
        ...     

        return results;
    }

Now, when Model.IsValid is called in your controller, this bit of logic will be ran and will return validation errors as normal.

Olwena answered 21/4, 2011 at 9:31 Comment(0)
B
0

It took a little while since your question was asked, but if you still like data annotations, this problem can be easily solved using this library:

[Required]
[AssertThat("FieldA != 'some text'")]
public string FieldA { get; set; }

Above, the field value is compared with some pre-defined text. Alternatively, you can compare fields values with each other:

[AssertThat("FieldA != FieldB")]

...and when the case of the strings being compared does not matter:

[AssertThat("CompareOrdinalIgnoreCase(FieldA, FieldB) != 0")]
Balzer answered 17/7, 2014 at 21:28 Comment(0)
H
0

To improve a little bit of @Darin Dimitrov answer, if you want to add messages from the resources using ErrorMessageResourceName and ErrorMessageResourceType, just add this to the to the Error message ErrorMessage = ErrorMessage ?? ErrorMessageString

The ErrorMessageString will look for the localized version of error message that you set in the model using those parameters (ErrorMessageResourceName and ErrorMessageResourceType)

Himalayas answered 18/1, 2016 at 18:39 Comment(0)
E
-1

The ideal solutions is a custom Attribute where you specify minimum and maximum lengths as well as MustNotContain="Please enter your name...".

Edmiston answered 20/4, 2011 at 22:3 Comment(2)
I really don't see how this answer has anything to do with what is being asked here which is how to validate that two properties on the view model are not equal.Barclay
I read the question as - How do I know the Name field contains a valid name and not the default value of 'Please enter your name'. Validation of this nature can be performed with a very generic attribute which can be used on many fields.Edmiston

© 2022 - 2024 — McMap. All rights reserved.