Accept comma and dot as decimal separator [duplicate]
Asked Answered
C

4

23

Model binding in ASP.NET MVC is great, but it follows locale settings. In my locale decimal separator is comma (','), but users use dot ('.') too, because they are lazy to switch layouts. I want this implemented in one place for all decimal fields in my models.

Should I implement my own Value Provider (or event Model Binder) for decimal type or I've missed some simple way to do this?

Chargeable answered 18/1, 2013 at 14:9 Comment(1)
The accepted solution doesn't work in some cases. The following link has a solution that works for all the cases: https://mcmap.net/q/505802/-how-to-set-decimal-separators-in-asp-net-mvc-controllersThury
Q
41

Cleanest way is to implement your own model binder

public class DecimalModelBinder : DefaultModelBinder
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);

        return valueProviderResult == null ? base.BindModel(controllerContext, bindingContext) : Convert.ToDecimal(valueProviderResult.AttemptedValue);
        // of course replace with your custom conversion logic
    }    
}

And register it inside Application_Start():

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

Credits : Default ASP.NET MVC 3 model binder doesn't bind decimal properties

Quintuplet answered 18/1, 2013 at 14:14 Comment(8)
Where to put DecimalModelBinder class?Concettaconcettina
@ИванБишевац I put mine at \Common\ModelBinders.Thury
Convert.ToDecimal(valueProviderResult.AttemptedValue) works fine for comma, but for dot the number is multiplied by 100. Suggestions?Thury
Thanks this saved me some time, for just a minor improvement, see my answer below.Maestas
This accepted answer does not work every time. It depends of the Current Localization which is wrong.Milburt
Thank you so much for your answer. I just added a validation for when the AttemptedValue is empty, but in general is working fine!Poucher
Is there a solution that works in mvc6 .net core?Rousseau
I don't get it. How does it work? Do I keep my everything the same in my code and just add this and it works? Or do I have to somehow apply the binder to my controller?Popple
M
4

To properly handle group separator, just replace

Convert.ToDecimal(valueProviderResult.AttemptedValue);

in selected answer with

Decimal.Parse(valueProviderResult.AttemptedValue, NumberStyles.Currency);
Maestas answered 23/6, 2015 at 22:58 Comment(1)
Or NumberStyles.Any if you want to go buck wild.Ashley
H
4

Thanks to accepted answer I ended up with the following implementation to handle float, double and decimal.

public abstract class FloatingPointModelBinderBase<T> : DefaultModelBinder
{
    protected abstract Func<string, IFormatProvider, T> ConvertFunc { get; }

    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
        if (valueProviderResult == null) return base.BindModel(controllerContext, bindingContext);
        try
        {
            return ConvertFunc.Invoke(valueProviderResult.AttemptedValue, CultureInfo.CurrentUICulture);
        }
        catch (FormatException)
        {
            // If format error then fallback to InvariantCulture instead of current UI culture
            return ConvertFunc.Invoke(valueProviderResult.AttemptedValue, CultureInfo.InvariantCulture);
        }
    }
}

public class DecimalModelBinder : FloatingPointModelBinderBase<decimal>
{
    protected override Func<string, IFormatProvider, decimal> ConvertFunc => Convert.ToDecimal;
}

public class DoubleModelBinder : FloatingPointModelBinderBase<double>
{
    protected override Func<string, IFormatProvider, double> ConvertFunc => Convert.ToDouble;
}

public class SingleModelBinder : FloatingPointModelBinderBase<float>
{
    protected override Func<string, IFormatProvider, float> ConvertFunc => Convert.ToSingle;
}

Then you just have to set your ModelBinders on Application_Start method

ModelBinders.Binders[typeof(float)] = new SingleModelBinder();
ModelBinders.Binders[typeof(double)] = new DoubleModelBinder();
ModelBinders.Binders[typeof(decimal)] = new DecimalModelBinder();
Highroad answered 1/3, 2017 at 16:7 Comment(0)
L
2
var nfInfo = new System.Globalization.CultureInfo(lang, false)
{
    NumberFormat =
    {
        NumberDecimalSeparator = "."
    }
};
Thread.CurrentThread.CurrentCulture = nfInfo;
Thread.CurrentThread.CurrentUICulture = nfInfo;
Lorislorita answered 15/10, 2014 at 10:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.