Search code examples
jqueryasp.net-mvcvalidationrangeclient-side-validation

ASP.NET MVC client side validation issue for range function


I have an ASP.NET MVC5 application and This is my viewmodel:

    [RegularExpression("[0-9]+(,[0-9]+)*"]
    [Range(1, double.MaxValue, ErrorMessage = "value is not correct")]
    [Required]
    [DisplayFormat(DataFormatString = "{0:0,0}", ApplyFormatInEditMode = true, HtmlEncode = true)]
    public double? Amount { get; set; }

and my view:

<div class="col-sm-8">
     @Html.EditorFor(m => m.Amount, new { @class = "form-control font-num numbers", maxlength = "10" })
     @Html.ValidationMessageFor(m => m.Amount, "", new { @class = "text-danger" })
</div>

The only notable thing is I format numbers using a comma separator every three digits in my view.

For example: 123456789 => 123,456,789

I know that I should use custom model binder for server side validation and override the default implementation from the jquery.validate.js file for the range() and number() functions.

So I did as follows:

public class DoubleModelBinder : IModelBinder
{
    /// <summary>
    /// Binds the value to the model.
    /// </summary>
    /// <param name="controllerContext">The current controller context.</param>
    /// <param name="bindingContext">The binding context.</param>
    /// <returns>The new model.</returns>
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var culture = GetUserCulture(controllerContext);

        string value = bindingContext.ValueProvider
                           .GetValue(bindingContext.ModelName)
                           .ConvertTo(typeof(string)) as string;

        double result = 0;
        double.TryParse(value, NumberStyles.Any, culture, out result);

        return result;
    }

    /// <summary>
    /// Gets the culture used for formatting, based on the user's input language.
    /// </summary>
    /// <param name="context">The controller context.</param>
    /// <returns>An instance of <see cref="CultureInfo" />.</returns>
    public CultureInfo GetUserCulture(ControllerContext context)
    {
        var request = context.HttpContext.Request;
        if (request.UserLanguages == null || request.UserLanguages.Length == 0)
            return CultureInfo.CurrentUICulture;

        return new CultureInfo(request.UserLanguages[0]);
    }
}

And for client side validation:

$.validator.methods.range = function (value, element, param) {
var globalizedValue = value.replace(",", "");
return this.optional(element) || (globalizedValue >= param[0] && globalizedValue <= param[1]);}

$.validator.methods.number = function (value, element) {
return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:[\s\.,]\d{3})+)(?:[\.,]\d+)?$/.test(value);}

The server side validation works well, but the client side validation doesn’t work correctly and I always get “value is not correct” error validation. If I ignore [Range(1, double.MaxValue, ErrorMessage = "value is not correct")] attribute, it works well, but the user can enter zero for this field. What is wrong in my client side validation?


Solution

  • After spending much time and tracing range() function, finally I found that I should use:

    var globalizedValue = value.replace(new RegExp(",", "g"), "");
    

    Instead of

    var globalizedValue = value.replace(",", "");
    

    Because I have more than one comma (For example: 123,456,789) and the default .replace() behavior is to replace only the first match.

    My mistake was I used the solution in this blog, while my issue was different. I use comma as a separator every three digits, not as the decimal separator. So I have more than one comma and default replace() method is not suitable. In fact, RegExp(",", "g") replaces all occurrences.