41

In previous versions of the MVC framework custom validation would be achieved through implementing IClientValidatable and the GetClientValidationRules method.

However in ASP.Net Core MVC we do not have this interface, although we do have IClientModelValidator which a defining a very similar method. The implementation of which never gets called however.

So - how do we implement client-side validation for a custom attribute in ASP.NET Core MVC?

Will Ray
  • 10,621
  • 3
  • 46
  • 61
m.edmondson
  • 30,382
  • 27
  • 123
  • 206

1 Answers1

60

The IClientModelValidator is in fact the right interface. I've made a contrived sample implementation below.

Attribute

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class CannotBeRedAttribute : ValidationAttribute, IClientModelValidator
{
    public override bool IsValid(object value)
    {
        var message = value as string;
        return message?.ToUpper() == "RED";
    }

    public void AddValidation(ClientModelValidationContext context)
    {
        MergeAttribute(context.Attributes, "data-val", "true");
        var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
        MergeAttribute(context.Attributes, "data-val-cannotbered", errorMessage);
    }

    private bool MergeAttribute(
        IDictionary<string, string> attributes,
        string key,
        string value)
    {
        if (attributes.ContainsKey(key))
        {
            return false;
        }
        attributes.Add(key, value);
        return true;
    }
}

Model

public class ContactModel
{
    [CannotBeRed(ErrorMessage = "Red is not allowed!")]
    public string Message { get; set; }
}

View

@model WebApplication.Models.ContactModel

<form asp-action="Contact" method="post">
    <label asp-for="Message"></label>
    <input asp-for="Message" />
    <span asp-validation-for="Message"></span>
    <input type="submit" value="Save" />
</form>

@section scripts {
    <script src="~/lib/jquery-validation/dist/jquery.validate.min.js"></script>
    <script src="~/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js"></script>
    <script>
        $.validator.addMethod("cannotbered",
            function (value, element, parameters) {
                return value.toUpperCase() !== "RED";
            });

        $.validator.unobtrusive.adapters.add("cannotbered", [], function (options) {
            options.rules.cannotbered = {};
            options.messages["cannotbered"] = options.message;
        });
    </script>
}
Will Ray
  • 10,621
  • 3
  • 46
  • 61
  • 1
    I have done exactly as shown by you in the example. but its not working, it is not showing the message returned by GetClientValidationRules. Instead showing the default required message ? – XamDev Apr 26 '16 at 10:14
  • 1
    @Rohit The custom attribute in my example will only show an error if you type in the word "red" in the input box. – Will Ray Apr 26 '16 at 14:40
  • Thanks for the help. That's working. However, how can I return my custom message from GetClientValidationRules method. I do not want specify on model attribute like `[CannotBeRed(ErrorMessage = "Not red!")]` – XamDev Apr 27 '16 at 05:29
  • 1
    I have written a class from where I am getting custom error messages which I want to use in 'GetClientValidationRules' method. If I do so like `yield return new ModelClientValidationRule( "cannotbered", FormatErrorMessage(MyClass.GetCustomMessage('somekey'));` , instead it is showing the default Required field message as 'The message field is required'. Can you please help on this ? – XamDev Apr 27 '16 at 05:36
  • 1
    @Rohit Remove the `[Required]` attribute from the `Message` property in my example. That's what's giving you the default required message. As far as pulling down a custom message, I would recommend [this SO question](http://stackoverflow.com/a/24177263/4270650). It should still be valid for ASP.NET Core. – Will Ray Apr 27 '16 at 14:19
  • Thanks for the help ! Its working fine ! Also, can you please tell me how I can do other validation like comparison, length, currency,numeric validation ? Do I need to right different rules for each validation and what changes needs to be done in jQuery. – XamDev Apr 28 '16 at 07:06
  • I have done sample password comparison like `public IEnumerable GetClientValidationRules(ClientModelValidationContext context) { var modelClientValidationEqualToRule = new ModelClientValidationEqualToRule("Password ans confirm password do not match.", _OtherPropertyName); yield return modelClientValidationEqualToRule; }` – XamDev Apr 28 '16 at 07:08
  • and in jQuery `$.validator.addMethod("equalto", function (value, element, parameters) { return value; }); $.validator.unobtrusive.adapters.add('equalto', [], function (options) { options.rules.equalto = {}; options.messages['equalto'] = options.message; });` Is this a right approach ? – XamDev Apr 28 '16 at 07:11
  • 1
    @Rohit I think you've reached a point where asking a new question on SO would be beneficial. I'm having a little trouble following you in these last comments, and fresh question would open it up to more folks. All that being said, there is a [CompareAttribute](https://msdn.microsoft.com/en-us/library/system.componentmodel.dataannotations.compareattribute(v=vs.110).aspx) that I've used for password comparison in the past without issue. – Will Ray Apr 28 '16 at 13:40
  • 1
    Can't find ModelClientValidationRule in MVC core (RC2). Anyone? – ScottE May 27 '16 at 12:09
  • @ScottE There was a breaking change made to the interface between RC1 and RC2. See my updated answer for more information and an updated example. – Will Ray Jun 09 '16 at 00:43