furyfish
furyfish

Reputation: 2135

error with decimal in mvc3 - the value is not valid for field

I'm following [Getting started with ASP.NET MVC 3][1]. And I can't add/edit with value of Price = 9.99 or 9,99. It said: "The value '9.99' is not valid for Price." and "The field Price must be a number."

How to fix this?

Model:

    public class Movie
{
    public int ID { get; set; }
    public string Title { get; set; }
    public DateTime ReleaseDate { get; set; }
    public string Genre { get; set; }
    public decimal Price { get; set; }
}

public class MovieDbContext : DbContext
{
    public DbSet<Movie> Movies { get; set; }
}

Controller:

public class MovieController : Controller
{
    private MovieDbContext db = new MovieDbContext();

    //
    // GET: /Movie/

    public ViewResult Index()
    {
        var movie = from m in db.Movies
                     where m.ReleaseDate > new DateTime(1984, 6, 1)
                     select m;

        return View(movie.ToList()); 
    }

    //
    // GET: /Movie/Details/5

    public ViewResult Details(int id)
    {
        Movie movie = db.Movies.Find(id);
        return View(movie);
    }

    //
    // GET: /Movie/Create

    public ActionResult Create()
    {
        return View();
    } 

    //
    // POST: /Movie/Create

    [HttpPost]
    public ActionResult Create(Movie movie)
    {
        if (ModelState.IsValid)
        {
            db.Movies.Add(movie);
            db.SaveChanges();
            return RedirectToAction("Index");  
        }

        return View(movie);
    }

    //
    // GET: /Movie/Edit/5

    public ActionResult Edit(int id)
    {
        Movie movie = db.Movies.Find(id);
        return View(movie);
    }

    //
    // POST: /Movie/Edit/5

    [HttpPost]
    public ActionResult Edit(Movie movie)
    {
        if (ModelState.IsValid)
        {
            db.Entry(movie).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(movie);
    }

    //
    // GET: /Movie/Delete/5

    public ActionResult Delete(int id)
    {
        Movie movie = db.Movies.Find(id);
        return View(movie);
    }

    //
    // POST: /Movie/Delete/5

    [HttpPost, ActionName("Delete")]
    public ActionResult DeleteConfirmed(int id)
    {            
        Movie movie = db.Movies.Find(id);
        db.Movies.Remove(movie);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
}
}

View:

    @model MvcMovies.Models.Movie

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

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

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

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

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

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

<div>
@Html.ActionLink("Back to List", "Index")
</div>
public DbSet<Movie> Movies { get; set; }
}

Upvotes: 32

Views: 43014

Answers (10)

Ciro Corvino
Ciro Corvino

Reputation: 2128

This is an extension of Leniel Maccaferri solution that avoids problems related to the sending by the user-agent of decimal values in a different culture format from the server one. Its limits are bound to the thousands separator parsing that, when it is the only separator in the value, can raise wrong bindings.

/// <summary>
/// custom decimal model binder
/// </summary>
/// <author>https://stackoverflow.com/users/114029/leniel-maccaferri</author>
/// <see cref="https://stackoverflow.com/a/19339424/3762855"/>
/// <remarks>Integrated with a fix for the decimal separator issue.
/// <para>This issue maybe depends from browsers interpretation of decimal values posted-back to the server when they receive response without any http content-language specific indication.</para>
/// <para>Important! decimal values caming from UI must not be formatted with thousands separator.</para>
/// </remarks>
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;

        if (valueResult.AttemptedValue != string.Empty)
        {
            try
            {
                var culture = Thread.CurrentThread.CurrentCulture;

                //This is needed to convert in the right manner decimal values coming from UI, as seems they always represent the decimal separator as a period("."). 
                //Maybe depends from browsers interpretation of decimal values posted-back to the server when they receive response without any http content-language specific indication.
                if (culture.NumberFormat.NumberDecimalSeparator == "," && valueResult.AttemptedValue.LastIndexOf(".") > 0)
                {
                    culture = new CultureInfo("en"); 
                }
                else if (Thread.CurrentThread.CurrentCulture.NumberFormat.NumberDecimalSeparator == "." && valueResult.AttemptedValue.LastIndexOf(",") > 0)
                {
                    culture = new CultureInfo("it");
                }

                actualValue = Convert.ToDecimal(valueResult.AttemptedValue, culture);

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

        bindingContext.ModelState.Add(bindingContext.ModelName, modelState);

        return actualValue;
    }
}

Upvotes: 0

XavierA
XavierA

Reputation: 1775

In 2019, this problem is still not solved. Using ASP Core 2.1, my UI is in French (decimal separator= ',') and I couldn't get the validation to work anytime I had a decimal number.

I found a workaround, not ideal though: I created a french-based CultureInfo but I changed the decimal separator to be the same as in Invariant Culture : '.'.

This made the trick, my decimal numbers are now displayed US style (but I am ok with it) and validation works.

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseCookiePolicy();

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });


        //Culture specific problems
        var cultureInfo = new CultureInfo("fr-FR");
        cultureInfo.NumberFormat.NumberDecimalSeparator = ".";
        System.Threading.Thread.CurrentThread.CurrentUICulture = cultureInfo;

    }

Upvotes: 4

Rafael Ara&#250;jo
Rafael Ara&#250;jo

Reputation: 3874

I've tried @Leniel Macaferi but it didn't work for me.

ModelState.IsValid didn't accept numbers formatted like 7.000,00

The problem started when I changed the property type from:

[Column("PRICE")]
public decimal Price { get; set; }

to

[Column("PRICE")]
public decimal? Price { get; set; }

I've also tried to include the globalization on web.config that I had forgotten

<globalization culture="pt-BR" uiCulture="pt-BR" enableClientBasedCulture="true" />

The only workaround that worked was change back the property to decimal only:

[Column("PRICE")]
public decimal Price { get; set; }

and also changed the table column to NOT accept null values

Hope it helps somebody.

Upvotes: 1

R. Schreurs
R. Schreurs

Reputation: 9085

I encountered this issue when developing a web application for an English audience, on a Pc in The Netherlands.

A model property of type double, generated this server-side validation error:

The value '1.5' is not valid for .

On an breakpoint, I saw these values in the Immediate Window:

?System.Threading.Thread.CurrentThread.CurrentUICulture

{en-US}

?System.Threading.Thread.CurrentThread.CurrentCulture

{nl-NL}

As a solution (or maybe a work-around), you can specify the globalization settings in the web.config file.

<configuration>
  <system.web>
    <globalization culture="en" uiCulture="en" />

Of course this means that you force your users to enter numbers in English formatting, but that is just fine, in my case.

Upvotes: 4

BineG
BineG

Reputation: 365

I've adapted the code from Leniel Macaferi a little bit so you can use it for any type:

public class RequestModelBinder<TBinding> : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        ValueProviderResult valueResult = bindingContext.ValueProvider
            .GetValue(bindingContext.ModelName);

        ModelState modelState = new ModelState { Value = valueResult };

        object actualValue = null;

        if (valueResult.AttemptedValue != string.Empty)
        {
            try
            {
                // values really should be invariant
                actualValue = Convert.ChangeType(valueResult.AttemptedValue, typeof(TBinding), CultureInfo.CurrentCulture);
            }
            catch (FormatException e)
            {
                modelState.Errors.Add(e);
            }
        }

        bindingContext.ModelState.Add(bindingContext.ModelName, modelState);

        return actualValue;
    }
}

Upvotes: 1

JohanH
JohanH

Reputation: 19

I solved this problem by disabled jquery for price and only validate on server side for that input. I found the answer here: ASP .NET MVC Disable Client Side Validation at Per-Field Level

<div class="col-md-10">
                @{ Html.EnableClientValidation(false); }
                @Html.EditorFor(model => model.DecimalValue, new { htmlAttributes = new { @class = "form-control" } })
                @{ Html.EnableClientValidation(true); }
                @Html.ValidationMessageFor(model => model.DecimalValue, "", new { @class = "text-danger" })
            </div>

Upvotes: 0

Dave_cz
Dave_cz

Reputation: 1210

You can add:

protected void Application_BeginRequest()
{
    var currentCulture = (CultureInfo)CultureInfo.CurrentCulture.Clone();
    currentCulture.NumberFormat.NumberDecimalSeparator = ".";
    currentCulture.NumberFormat.NumberGroupSeparator = " ";
    currentCulture.NumberFormat.CurrencyDecimalSeparator = ".";

    Thread.CurrentThread.CurrentCulture = currentCulture;
    //Thread.CurrentThread.CurrentUICulture = currentCulture;
}

To Global.asax (tested on MVC 5.1). It works without changing UICulture for me.

Upvotes: 0

Bobi0204
Bobi0204

Reputation: 1

Just comment this link for the script:

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

Upvotes: -4

Andreas Reiff
Andreas Reiff

Reputation: 8404

You are one of the non-English customers, which MS has not foreseen. You will need to put some extra effort into making your version run. I had a similar problem, denying me both "9,99" and "9.99" as valid numbers. It seems like once server-side validation failed, and once client-side validation, causing no number to be accepted.

So you have to make the validation congruent.

Like suggested in the comments, have a look at http://msdn.microsoft.com/en-us/library/gg674880(VS.98).aspx and http://haacked.com/archive/2010/05/10/globalizing-mvc-validation.aspx and MVC 3 jQuery Validation/globalizing of number/decimal field or - should you understand German (or just look at the code examples) http://www.andreas-reiff.de/2012/06/probleme-mit-mvcmovies-beispiel-validierung-des-preises-mit-dezimalstellen-schlagt-fehl/

BTW, same problem exists for both the Music and Movie example tutorials.

Upvotes: 7

Leniel Maccaferri
Leniel Maccaferri

Reputation: 102448

I just stumbled on this again after 2 years. I thought ASP.NET MVC 5 had solved this but looks like it's not the case. So here goes how to solve the problem...

Create a class called DecimalModelBinder like the following and add it to the root of your project for example:

using System;
using System.Globalization;
using System.Web.Mvc;

namespace YourNamespace
{   
    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;

            if(valueResult.AttemptedValue != string.Empty)
            {
                try
                {
                    actualValue = Convert.ToDecimal(valueResult.AttemptedValue, CultureInfo.CurrentCulture);
                }
                catch(FormatException e)
                {
                    modelState.Errors.Add(e);
                }
            }

            bindingContext.ModelState.Add(bindingContext.ModelName, modelState);

            return actualValue;
        }
    }
}

Inside Global.asax.cs, make use of it in Application_Start() like this:

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

Upvotes: 42

Related Questions