How to change 'data-val-number' message validation in MVC while it is generated by @Html helper
Asked Answered
L

14

79

Assume this model:

Public Class Detail
    ...
    <DisplayName("Custom DisplayName")>
    <Required(ErrorMessage:="Custom ErrorMessage")>
    Public Property PercentChange As Integer
    ...
end class

and the view:

@Html.TextBoxFor(Function(m) m.PercentChange)

will proceed this html:

   <input data-val="true" 
    data-val-number="The field 'Custom DisplayName' must be a number." 
    data-val-required="Custom ErrorMessage"     
    id="PercentChange" 
    name="PercentChange" type="text" value="0" />

I want to customize the data-val-number error message which I guess has generated because PercentChange is an Integer. I was looking for such an attribute to change it, range or whatever related does not work.
I know there is a chance in editing unobtrusive's js file itself or override it in client side. I want to change data-val-number's error message just like others in server side.

Latoyialatreece answered 28/1, 2011 at 12:38 Comment(1)
I've used Griffin MVC Contrib project to localize the validation texts without ugly attributesFilippa
S
40

This is not gonna be easy. The default message is stored as an embedded resource into the System.Web.Mvc assembly and the method that is fetching is a private static method of an internal sealed inner class (System.Web.Mvc.ClientDataTypeModelValidatorProvider+NumericModelValidator.MakeErrorString). It's as if the guy at Microsoft coding this was hiding a top secret :-)

You may take a look at the following blog post which describes a possible solution. You basically need to replace the existing ClientDataTypeModelValidatorProvider with a custom one.

If you don't like the hardcore coding that you will need to do you could also replace this integer value inside your view model with a string and have a custom validation attribute on it which would do the parsing and provide a custom error message (which could even be localized).

Shepard answered 28/1, 2011 at 18:27 Comment(5)
wow, you must be very good at these secret things :). very tough code. I don't understand why they make it this difficult! I was kind of hopping that they fix these things in MVC3 RTM but they didn't. I place it in my controller just to check if it is working. and thanks it works like a charm. but where should I put this remove thing? in my global.asax? is it ok then?Latoyialatreece
@GtEx, yes the Providers.Remove and Providers.Add should be placed in the Application_Start method of your Global.asax.Shepard
Has anyone been able to do something similar to this, but be able to create the message based on other values from the object that the number is being validated on? e.g., if my object had "Prompt" and "value", my message would say "{Prompt} must be a number." Or, do I have to do object validation for that?Antechamber
Looking into the source code of MVC 4, it looks like it's being fixed allowing us to specify our own ResourceClassKey aspnetwebstack.codeplex.com/SourceControl/changeset/view/…Materfamilias
Yes. Replacing ResourceClassKey and using the name FieldMustBeNumeric worked in this case. For Required and Invalid error messes use this other approach also sugested by Darin Dimitrov #12545676Rella
P
80

You can override the message by supplying the data-val-number attribute yourself when rendering the field. This overrides the default message. This works at least with MVC 4.

@Html.EditorFor(model => model.MyNumberField, new { data_val_number="Supply an integer, dude!" })

Remember that you have to use underscore in the attribute name for Razor to accept your attribute.

Poirer answered 18/4, 2013 at 11:7 Comment(7)
I don't know how did you get it to work. In my MVC4-app attribute added in such way is ignored.Alginate
This worked for me, though in my case I just wanted to turn it off. data_val="false" sufficed.Mendelssohn
I can't see how this would work. If this was a new htmlAttributes object, it might work, but its an additionalViewData object, so you just get an extra attribute called data_val_number.Hone
@Poirer Works great for me, thanks for saving me a bunch of time :)Herminiahermione
I agree with @Sprintstar. It didn't work for me but @Html.EditorFor(model => model.age, new { htmlAttributes = new { @class = "form-control", data_val_required = "Field required"} }) did.Frisco
This should be the accepted answer! Compare the other answers... occam's razor anyone?Dragoman
Works perfectly for me, and a very simple solution compared to so many others.Orchardman
W
53

What you have to do is:

Add the following code inside Application_Start() in Global.asax:

 ClientDataTypeModelValidatorProvider.ResourceClassKey = "Messages";
 DefaultModelBinder.ResourceClassKey = "Messages";

Right click your ASP.NET MVC project in VS. Select Add => Add ASP.NET Folder => App_GlobalResources.

Add a .resx file called Messages.resx in that folder.

Add these string resources in the .resx file:

FieldMustBeDate        The field {0} must be a date.
FieldMustBeNumeric     The field {0} must be a number.
PropertyValueInvalid   The value '{0}' is not valid for {1}.
PropertyValueRequired  A value is required.

Change the FieldMustBeNumeric value as you want... :)

You're done.


Check this post for more details:

Localizing Default Error Messages in ASP.NET MVC and WebForms

Windowlight answered 22/8, 2013 at 23:50 Comment(4)
I don't receive the translation for PropertyValueInvalid , only the default text... Any idea's?Unexacting
While @DarinDimitrov has a valid solution, for me this should be the accepted answer, as it uses a built-in extensible mechanism instead of copying the entire .Net class and changing it, which is much less maintainableBlub
This really is the best answer for this question.Alkalify
@Nick Coad. Yes it is the best - and no frills answer to this question. I can confirm it works in MVC 4 and MVC 5Lohr
S
40

This is not gonna be easy. The default message is stored as an embedded resource into the System.Web.Mvc assembly and the method that is fetching is a private static method of an internal sealed inner class (System.Web.Mvc.ClientDataTypeModelValidatorProvider+NumericModelValidator.MakeErrorString). It's as if the guy at Microsoft coding this was hiding a top secret :-)

You may take a look at the following blog post which describes a possible solution. You basically need to replace the existing ClientDataTypeModelValidatorProvider with a custom one.

If you don't like the hardcore coding that you will need to do you could also replace this integer value inside your view model with a string and have a custom validation attribute on it which would do the parsing and provide a custom error message (which could even be localized).

Shepard answered 28/1, 2011 at 18:27 Comment(5)
wow, you must be very good at these secret things :). very tough code. I don't understand why they make it this difficult! I was kind of hopping that they fix these things in MVC3 RTM but they didn't. I place it in my controller just to check if it is working. and thanks it works like a charm. but where should I put this remove thing? in my global.asax? is it ok then?Latoyialatreece
@GtEx, yes the Providers.Remove and Providers.Add should be placed in the Application_Start method of your Global.asax.Shepard
Has anyone been able to do something similar to this, but be able to create the message based on other values from the object that the number is being validated on? e.g., if my object had "Prompt" and "value", my message would say "{Prompt} must be a number." Or, do I have to do object validation for that?Antechamber
Looking into the source code of MVC 4, it looks like it's being fixed allowing us to specify our own ResourceClassKey aspnetwebstack.codeplex.com/SourceControl/changeset/view/…Materfamilias
Yes. Replacing ResourceClassKey and using the name FieldMustBeNumeric worked in this case. For Required and Invalid error messes use this other approach also sugested by Darin Dimitrov #12545676Rella
H
23

As an alternate way around this, I applied a RegularExpression attribute to catch the invalid entry and set my message there:

[RegularExpression(@"[0-9]*$", ErrorMessage = "Please enter a valid number ")]

This slightly a hack but this seemed preferable to the complexity the other solutions presented, at least in my particular situation.

EDIT: This worked well in MVC3 but it seems that there may well be better solutions for MVC4+.

Handedness answered 11/10, 2011 at 17:9 Comment(6)
still i prefer the first solution, much neater. just one line of code in global.asax. the problem with this is you have to add this attribute to every single say integer properties in your project and it use regular expression which makes me uncomfortable with the process that only takes 0.0001 sec to regEx it ;)Latoyialatreece
No argument that it is a bit of a hack. My only defense that it should be extremely easy to override any error message in the framework.Handedness
Well the right way to do it would be to have it be override-able in an easy way...but since we can't do that I am glad you found my thoughts useful.Handedness
Seems like the dollar i not needed, maybe ^ and $ are implicit then.Delija
I know the OP only what an integer but this will get out of hand quickly if you consider decimal numbers and i18n?Tertias
@Tertias I haven't looked into it in a while as I don't do much of this sort of development anymore, but I would have to imagine there are better alternatives in more recent versions of MVC. But yes I agree that the scenario you describe would get out of hand, especially the i18n.Handedness
H
10

From this book on MVC 3 that I have. All you have to do is this:

public class ClientNumberValidatorProvider : ClientDataTypeModelValidatorProvider 
{ 
   public override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, 
                                                          ControllerContext context) 
   { 
       bool isNumericField = base.GetValidators(metadata, context).Any(); 
       if (isNumericField) 
           yield return new ClientSideNumberValidator(metadata, context); 
   } 
} 

public class ClientSideNumberValidator : ModelValidator 
{ 
  public ClientSideNumberValidator(ModelMetadata metadata,  
      ControllerContext controllerContext) : base(metadata, controllerContext) { } 

  public override IEnumerable<ModelValidationResult> Validate(object container) 
  { 
     yield break; // Do nothing for server-side validation 
  } 

  public override IEnumerable<ModelClientValidationRule> GetClientValidationRules() 
  { 
     yield return new ModelClientValidationRule { 
        ValidationType = "number", 
        ErrorMessage = string.Format(CultureInfo.CurrentCulture,  
                                     ValidationMessages.MustBeNumber,  
                                     Metadata.GetDisplayName()) 
        }; 
  } 
} 

protected void Application_Start() 
{ 
    // Leave the rest of this method unchanged 

    var existingProvider = ModelValidatorProviders.Providers 
        .Single(x => x is ClientDataTypeModelValidatorProvider); 
    ModelValidatorProviders.Providers.Remove(existingProvider); 
    ModelValidatorProviders.Providers.Add(new ClientNumberValidatorProvider()); 
} 

Notice how the ErrorMessage is yielded, you specify the current culture and the localized message is extracted from the ValidationMessages(here be culture specifics).resx resource file. If you don't need that, just replace it with your own message.

Heartsease answered 19/6, 2011 at 21:47 Comment(2)
It sound much more complex than my chosen solution. not to mention we don't have yield return in vb.net, although there is a work around. Still i don't get the advantages of this solution.Latoyialatreece
I really like this solution. One question: we're replacing the existing provider with a new one. What if the existing provider used to do more than just this validation? Is it guaranteed that by removing it we're not breaking any other functionality?Sink
K
7

Here is another solution which changes the message client side without changed MVC3 source. Full details in this blog post:

https://greenicicle.wordpress.com/2011/02/28/fixing-non-localizable-validation-messages-with-javascript/

In short what you need to do is include the following script after jQuery validation is loaded plus the appropriate localisation file.

(function ($) {
    // Walk through the adapters that connect unobstrusive validation to jQuery.validate.
    // Look for all adapters that perform number validation
    $.each($.validator.unobtrusive.adapters, function () {
        if (this.name === "number") {
            // Get the method called by the adapter, and replace it with one 
            // that changes the message to the jQuery.validate default message
            // that can be globalized. If that string contains a {0} placeholder, 
            // it is replaced by the field name.
            var baseAdapt = this.adapt;
            this.adapt = function (options) {
                var fieldName = new RegExp("The field (.+) must be a number").exec(options.message)[1];
                options.message = $.validator.format($.validator.messages.number, fieldName);
                baseAdapt(options);
            };
        }
    });
} (jQuery));
Kynan answered 15/6, 2011 at 14:1 Comment(1)
Thanks Phil, (+1 for) that's good to know. But I personally rather changing MVC3 in this particular problem. I hope they put this fix in next release.Latoyialatreece
N
5

You can set ResourceKey of ClientDataTypeModelValidatorProvider class to name of a global resource that contains FieldMustBeNumeric key to replace MVC validation error message of number with your custom message. Also key of date validation error message is FieldMustBeDate.

ClientDataTypeModelValidatorProvider.ResourceKey="MyResources"; // MyResource is my global resource

See here for more details on how to add the MyResources.resx file to your project:

Noninterference answered 2/2, 2013 at 19:32 Comment(1)
Amazing, this actually works! It's ResourceClassKey though, not ResourceKeyAssyrian
F
4

Here is another solution in pure js that works if you want to specify messages globally not custom messages for each item.

The key is that validation messages are set using jquery.validation.unobtrusive.js using the data-val-xxx attribute on each element, so all you have to do is to replace those messages before the library uses them, it is a bit dirty but I just wanted to get the work done and fast, so here it goes for number type validation:

    $('[data-val-number]').each(function () {
    var el = $(this);
    var orig = el.data('val-number');

    var fieldName = orig.replace('The field ', '');
    fieldName = fieldName.replace(' must be a number.', '');

    el.attr('data-val-number', fieldName + ' باید عددی باشد')
});

the good thing is that it does not require compiling and you can extend it easily later, not robust though, but fast.

Fredette answered 29/1, 2014 at 12:40 Comment(0)
H
3

Check this out too:

The Complete Guide To Validation In ASP.NET MVC 3 - Part 2

Main parts of the article follow (copy-pasted).

There are four distinct parts to creating a fully functional custom validator that works on both the client and the server. First we subclass ValidationAttribute and add our server side validation logic. Next we implement IClientValidatable on our attribute to allow HTML5 data-* attributes to be passed to the client. Thirdly, we write a custom JavaScript function that performs validation on the client. Finally, we create an adapter to transform the HTML5 attributes into a format that our custom function can understand. Whilst this sounds like a lot of work, once you get started you will find it relatively straightforward.

Subclassing ValidationAttribute

In this example, we are going to write a NotEqualTo validator that simply checks that the value of one property does not equal the value of another.

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
public sealed class NotEqualToAttribute : ValidationAttribute
{
    private const string DefaultErrorMessage = "{0} cannot be the same as {1}.";

    public string OtherProperty { get; private set; }

    public NotEqualToAttribute(string otherProperty)
        : base(DefaultErrorMessage)
    {
        if (string.IsNullOrEmpty(otherProperty))
        {
            throw new ArgumentNullException("otherProperty");
        }

        OtherProperty = otherProperty;
    }

    public override string FormatErrorMessage(string name)
    {
        return string.Format(ErrorMessageString, name, OtherProperty);
    }

    protected override ValidationResult IsValid(object value, 
        ValidationContext validationContext)
    {
        if (value != null)
        {
            var otherProperty = validationContext.ObjectInstance.GetType()
                .GetProperty(OtherProperty);

            var otherPropertyValue = otherProperty
                .GetValue(validationContext.ObjectInstance, null);

            if (value.Equals(otherPropertyValue))
            {
                return new ValidationResult(
                    FormatErrorMessage(validationContext.DisplayName));
            }
        }
    return ValidationResult.Success;
    }        
}

Add the new attribute to the password property of the RegisterModel and run the application.

[Required]
[DataType(DataType.Password)]
[Display(Name = "Password")]
[NotEqualTo("UserName")]
public string Password { get; set; }
...

Implementing IClientValidatable

ASP.NET MVC 2 had a mechanism for adding client side validation but it was not very pretty. Thankfully in MVC 3, things have improved and the process is now fairly trivial and thankfully does not involve changing the Global.asax as in the previous version.

The first step is for your custom validation attribute to implement IClientValidatable. This is a simple, one method interface:

public IEnumerable<ModelClientValidationRule> GetClientValidationRules(
    ModelMetadata metadata,
    ControllerContext context)
{
    var clientValidationRule = new ModelClientValidationRule()
    {
        ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
        ValidationType = "notequalto"
    };

    clientValidationRule.ValidationParameters.Add("otherproperty", OtherProperty);

    return new[] { clientValidationRule };
}

If you run the application now and view source, you will see that the password input html now contains your notequalto data attributes:

<div class="editor-field">
    <input data-val="true" data-val-notequalto="Password cannot be the same as UserName." 
    data-val-notequalto-otherproperty="UserName" 
    data-val-regex="Weak password detected." 
    data-val-regex-pattern="^(?!password$)(?!12345$).*" 
    data-val-required="The Password field is required." 
    id="Password" name="Password" type="password" />
    <span class="hint">Enter your password here</span>
    <span class="field-validation-valid" data-valmsg-for="Password" 
    data-valmsg-replace="true"></span>
</div>

Creating a custom jQuery validate function

All of this code is best to be placed in a separate JavaScript file.

(function ($) {
    $.validator.addMethod("notequalto", function (value, element, params) {
        if (!this.optional(element)) {
            var otherProp = $('#' + params);
            return (otherProp.val() != 
        }
    return true;
});

$.validator.unobtrusive.adapters.addSingleVal("notequalto", "otherproperty");

}(jQuery));

Depending on your validation requirements, you may find that the jquery.validate library already has the code that you need for the validation itself. There are lots of validators in jquery.validate that have not been implemented or mapped to data annotations, so if these fulfil your need, then all you need to write in javascript is an adapter or even a call to a built-in adapter which can be as little as a single line. Take a look inside jquery.validate.js to find out what is available.

Using an existing jquery.validate.unobtrusive adapter

The job of the adapter is to read the HTML5 data-* attributes on your form element and convert this data into a form that can be understood by jquery.validate and your custom validation function. You are not required to do all the work yourself though and in many cases, you can call a built-in adapter. jquery.validate.unobtrusive declares three built-in adapters which can be used in the majority of situations. These are:

jQuery.validator.unobtrusive.adapters.addBool - used when your validator does not need any additional data.
jQuery.validator.unobtrusive.adapters.addSingleVal - used when your validator takes in one piece of additional data.
jQuery.validator.unobtrusive.adapters.addMinMax - used when your validator deals with minimum and maximum values such as range or string length.

If your validator does not fit into one of these categories, you are required to write your own adapter using the jQuery.validator.unobtrusive.adapters.add method. This is not as difficulty as it sounds and we'll see an example later in the article.

We use the addSingleVal method, passing in the name of the adapter and the name of the single value that we want to pass. Should the name of the validation function differ from the adapter, you can pass in a third parameter (ruleName):

jQuery.validator.unobtrusive.adapters.addSingleVal("notequalto", "otherproperty", "mynotequaltofunction");

At this point, our custom validator is complete.

For better understanding refer to the article itself which presents more description and a more complex example.

HTH.

Hortense answered 3/2, 2015 at 10:48 Comment(0)
M
1

I just did this and then used a regex expression:

$(document).ready(function () {
    $.validator.methods.number = function (e) {
        return true;
    };
});


[RegularExpression(@"^[0-9\.]*$", ErrorMessage = "Invalid Amount")]
public decimal? Amount { get; set; }
Miffy answered 19/12, 2012 at 12:22 Comment(0)
B
1

Or you can simply do this.

@Html.ValidationMessageFor(m => m.PercentChange, "Custom Message: Input value must be a number"), new { @style = "display:none" })

Hope this helps.

Bookout answered 26/7, 2014 at 22:27 Comment(0)
D
1

I make this putting this on my view

@Html.DropDownListFor(m => m.BenefNamePos, Model.Options, new { onchange = "changePosition(this);", @class="form-control", data_val_number = "This is my custom message" })
Delmydeloach answered 14/5, 2018 at 14:41 Comment(0)
D
0

I have this problem in KendoGrid, I use a script at the END of View to override data-val-number:

@(Html.Kendo().Grid<Test.ViewModel>(Model)
  .Name("listado")
  ...
  .Columns(columns =>
    {
        columns.Bound("idElementColumn").Filterable(false);
    ...
    }

And at least, in the end of View I put:

<script type="text/javascript">
        $("#listado").on("click", function (e) {
            $(".k-grid #idElementColumn").attr('data-val-number', 'Ingrese un número.');
        });    
</script>
Delciedelcina answered 6/10, 2014 at 15:13 Comment(0)
M
0

a simple method is, use dataanotation change message on ViewModel:

[Required(ErrorMessage ="الزامی")]
[StringLength(maximumLength:50,MinimumLength =2)]
[Display(Name = "نام")]
public string FirstName { get; set; }
Marcellmarcella answered 10/11, 2020 at 22:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.