6

My controller looks like this:

 public class PortefeuilleController : Controller
    {
            public ActionResult Create()
            {
                return View(new PortefeuilleViewModel{Saldo = 0.0});
            }
    }

My create view looks like this:

@model PortefeuilleViewModel

@{
    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>PortefeuilleViewModel</legend>

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

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

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

And my PortefeuilleViewModel looks like this:

public class PortefeuilleViewModel
{
    [DisplayName("Naam")]
    [Required(ErrorMessage = "Gelieve een naam in te voeren")]
    public string Naam { get; set; }

    [DisplayName("Saldo")]
    public double Saldo { get; set; }
}

My problem lies with the "Saldo" field. I want to validate it as follows:

Leaving the field empty should be valid (because my code-behind will change "" into "0.0" and save that value in the database). But, for example, both '19,99' and '19.99' should also be passed as being valid. All the rest is invalid.

I don't really know how I can pull this off. I already found this article: http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx But that didn't solve (the second part of) my problem at all...

Any help would be greatly appreciated. Thanks.

tereško
  • 58,060
  • 25
  • 98
  • 150
Matthias
  • 12,704
  • 13
  • 35
  • 56

1 Answers1

23

OK, so you have validation activated at 2 levels: server side and client side. Let's first deal with the server side validation issue.

The first thing when working with money (which is what I suppose the Saldo field represents is to use decimals, not doubles). So the first change would be:

[DisplayName("Saldo")]
public decimal Saldo { get; set; }

OK, now let's adapt a little the Haacked model binder to your situation (accepting . and , as decimal separator as well as empty values)

public class DecimalModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (string.IsNullOrEmpty(valueResult.AttemptedValue))
        {
            return 0m;
        }
        var modelState = new ModelState { Value = valueResult };
        object actualValue = null;
        try
        {
            actualValue = Convert.ToDecimal(
                valueResult.AttemptedValue.Replace(",", "."), 
                CultureInfo.InvariantCulture
            );
        }
        catch (FormatException e)
        {
            modelState.Errors.Add(e);
        }

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

which of course will be registered in Application_Start:

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

At this stage we have solved the server side validation issue. As far as the client side validation is concerned you could use the jQuery globalization plugin that Microsoft released. Scott Hanselman has also blogged about it. So once you install it you could force the client side culture and override the $.validator.methods.number method which is responsible for performing the client side validation:

$.validator.methods.number = function (value, element) {
    // TODO: return true or false if value is a valid decimal
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    Ok thanks you, that works. But just a question: Why use decimal over double? – Matthias Sep 04 '11 at 13:29
  • 2
    @Matthias, always use decimals for currency: http://stackoverflow.com/questions/1165761/decimal-vs-double-which-one-should-i-use-and-when unless you want to start loosing currency :-) Doubles are stored using IEEE 754 whereas decimals are stored in a 10 base. – Darin Dimitrov Sep 04 '11 at 13:32
  • Thank you. Just one more thing I just realise while testing: If the user types in a string instead of a number it doesn't give a warning. Is there an annotation like [Decimal(ErrorMessage = "Please give a decimal number")] that I can use to validate that? – Matthias Sep 04 '11 at 13:35
  • @Matthias, that's weird. It works fine for me. When you type some string the custom model binder is invoked and the `Convert.ToDecimal` throws an exception which is intercepted and an error message is shown. Isn't what happens for you? Isn't the model binder invoked? – Darin Dimitrov Sep 04 '11 at 13:55
  • Added (if ModelState.IsValid) check in my HttpPost Edit method, it works now. But the message "The value 'qsdf' is not valid for Saldo." is shown. Is it possible to change this message? My application is in Dutch, so all my error messages should be in Dutch as well. – Matthias Sep 04 '11 at 14:04
  • @Matthias, of course that you can change it, inside the model binder: `modelState.Errors.Add(e);`. Here the exception is directly added, but you could add any text you like. – Darin Dimitrov Sep 04 '11 at 14:09
  • Great. Learned a lot of new things now, thank you very much :) – Matthias Sep 04 '11 at 14:14