14

I am trying to have my client side validation (model binding) to support different cultures, and I found an interesting blog on the subject on which I am trying to implement.

http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx

Poco

  public class Jogador
  {
    public int ID { get; set; }

    public string Name { get; set; }

    public decimal Salary { get; set; }
  }

I've got my custom DecimalModelBinder class

  public class DecimalModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      ValueProviderResult valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
      ModelState modelState = new ModelState {Value = valueResult};

      object actualValue = null;
      try
      {
        actualValue = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
      }
      catch (FormatException e)
      {
        modelState.Errors.Add(e);
      }

      bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
      return actualValue;
    }
  }

My web.config:

<compilation debug="true" targetFramework="4.0">
  <assemblies>
    <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <add assembly="System.Web.Helpers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
    <add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  </assemblies>
</compilation>

<authentication mode="Forms">
  <forms loginUrl="~/Account/LogOn" timeout="2880" />
</authentication>

<pages>
  <namespaces>
    <add namespace="System.Web.Helpers" />
    <add namespace="System.Web.Mvc" />
    <add namespace="System.Web.Mvc.Ajax" />
    <add namespace="System.Web.Mvc.Html" />
    <add namespace="System.Web.Routing" />
    <add namespace="System.Web.WebPages"/>
  </namespaces>
</pages>

Global.asax are altered to use my custom ModelBinder on decimal and decimal? values

protected void Application_Start()
{
  AreaRegistration.RegisterAllAreas();

  ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
  ModelBinders.Binders.Add(typeof(decimal?), new DecimalModelBinder());

  RegisterGlobalFilters(GlobalFilters.Filters);
  RegisterRoutes(RouteTable.Routes);
}

Still the client-side validation fails on decimal entered in my view with a "," as a decimal separator. It does not handle both "," and ".". The js validation does not seem to take my custom binding in consideration

Reading the blog article over and over again, I just can't seem to figure out what I am missing.

Here is the view:

@model MVC_Empty.Web.Models.Jogador

@{
    ViewBag.Title = "Create";
}

<h2>Create</h2>

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>

@using (Html.BeginForm()) {
    @Html.ValidationSummary(true)
    <fieldset>
        <legend>Jogador</legend>

        <div class="editor-label">
            @Html.LabelFor(model => model.Name)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Name)
            @Html.ValidationMessageFor(model => model.Name)
        </div>

        <div class="editor-label">
            @Html.LabelFor(model => model.Salary)
        </div>
        <div class="editor-field">
            @Html.EditorFor(model => model.Salary)
            @Html.ValidationMessageFor(model => model.Salary)
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Server side validation seems fine, but how to handle the client-side validation in order to send a POST when clicking the submit button.

The javascript validation does not handle the comma.enter image description here enter image description here

Kman
  • 4,809
  • 7
  • 38
  • 62

3 Answers3

21

Finally, by understanding that the Custom DecimalModelBinder would only handle server-side validation and not affect the jquery.validate.js which handles the client-side validation, I found a solution to the problem.

Extending the validation solved my issue.

enter image description here

Extend the validation by a new .js file as a workaround to the problem:

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

This blog was really helpful http://rebuildall.umbraworks.net/2011/03/02/jQuery_validate_and_the_comma_decimal_separator

Kman
  • 4,809
  • 7
  • 38
  • 62
6

Try this it's slightly modified version:

 public class DecimalModelBinder : IModelBinder
    {
        public object BindModel(ControllerContext controllerContext,
            ModelBindingContext bindingContext)
        {
            ValueProviderResult valueResult = bindingContext.ValueProvider
                .GetValue(bindingContext.ModelName);
            ModelState modelState = new ModelState { Value = valueResult };
            object actualValue = null;
            try
            {
                //if with period use InvariantCulture
                if (valueResult.AttemptedValue.Contains("."))
                {
                    actualValue = Convert.ToDecimal(valueResult.AttemptedValue,
                    CultureInfo.InvariantCulture);
                }
                else
                {
                    //if with comma use CurrentCulture
                    actualValue = Convert.ToDecimal(valueResult.AttemptedValue,
                    CultureInfo.CurrentCulture);
                }

            }
            catch (FormatException e)
            {
                modelState.Errors.Add(e);
            }

            bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
            return actualValue;
        }
    }

It works for me.

UPDATE

If you're using a partial view use $.validate.unobtrusive.parse("#selector") to reapply validation. If not try setting a globalization in your web.config (one in the root, not in the Views) to something like this:

<configuration>
  <system.web>
    <globalization fileEncoding="utf-8" 
                    requestEncoding="utf-8" 
                    responseEncoding="utf-8" 
                    culture="hr-HR" uiCulture="hr-HR" />
  </system.web>
</configuration>

I have globalization set to Hr where we use comma as delimiters but with the DecimalModelBinder i have posted it will properly parse decimal or comma. Regards

Matija Grcic
  • 12,963
  • 6
  • 62
  • 90
  • I'll give it a try. But my main concern is that my breakpoints are not hit. So if the code is never being executed, I don't see how changing the should solve my issue – Kman Apr 02 '12 at 16:46
  • If your hitting your post action? and did everything like you said so then i see no reason why your breakpoints are not hit. – Matija Grcic Apr 02 '12 at 17:00
  • 1
    Update: my custom decimal model binder is triggered on actual update, but the client side validation still fails. I'll update my main post. The problem is the validation BEFORE the post is submitted – Kman Apr 02 '12 at 17:00
  • Update: added several suggestions to my answer. – Matija Grcic Apr 02 '12 at 18:55
  • Thank you! I've updated the web.config with your example and also implemented the modified version of the ModelDecimalBinder. I even added the $.validate.unobtrusive.parse("#selector"). Though client validation does still not accept comma. As soon as the input looses focus it gives a validation error if the field contains a comma. Just can't see what I am missing – Kman Apr 02 '12 at 20:59
  • Update: Posted entire view and webconfig – Kman Apr 03 '12 at 10:07
  • 1
    As far as I understand, the DecimalModelBinder will only handle the server-side validation. NOT the client-side validation which is my problem – Kman Apr 03 '12 at 11:07
1

I've just fixed this issue replacing the line 1050 of the jquery.validate.js with this code:

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

I've just changed smth on the regular expression used to validate the number and now it's working great!!

brunoazev
  • 96
  • 6