DataAnnotation DisplayFormat for decimal numbers - asp.net-mvc

I've got his code.
PartialView.
<div class="input width110">
#Html.EditorFor(x => x.Price, #Html.Attributes(#class: "right_text_align", #disabled: "true", #id: "Price"))
</div>
Model.
public class ServiceModel
{
[DisplayFormat(DataFormatString = "{0:0.00}", ApplyFormatInEditMode = true)]
public decimal Price { get; set; }
}
Controller
public ActionResult SetService(ServiceModel model, string action)
{
if (ModelState.IsValid)
{
/*Does smthg.*/
ModelState.Clear();
}
return View("Index", rcpModel);
//Index is main view, which holds partialView
//rcpModel holds, model
}
When view loads Decimal is displayed in format "0.00". But after post when modelState is invalid number in displayed in format "0.0000". If model state isvalid, everything goes well. Has anyone encountered anything similar?

If you have javascript modifying the values on textboxes (currency formatting or commas) then you might be getting binding errors because it will behave as a string. Try this:
Create a BindingProperty for decimal values
public class DecimalModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext,
ModelBindingContext bindingContext)
{
var valueResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
var 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;
}
}
On your global.asax app_start or WebActivator.PostApplicationStartMethod add an entry to register the custom binder:
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());

To display dot instead of comma is enough to change the culture to english in every point of the code which is used before the view is called.
Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("En");

Related

Asp.net mvc 4 getting decimal value instead integer

I have a class like:
public class Item
{
public int ItemID { get; set; }
[Display(Name = "Value")]
[Column(TypeName = "money")]
public decimal Value{ get; set; }
}
In the form I enter 12.50 and in my post Action the object has Item.Value = 1250 when should have 12.50, How to fix this?
The Action Method:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Item item)
{
...code...
}
View:
<div class="editor-label">
#Html.LabelFor(model => model.Value)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Value)
#Html.ValidationMessageFor(model => model.Value)
</div>
When type 12. the validation client side say 'The field Valor must be a number.' however let me execute post action method, with 12, say the same thing but don't let me.
I solved with custom bind thanks a Phil Haack (http://haacked.com/archive/2011/03/19/fixing-binding-to-decimals.aspx). I modified the DecimalModelBind class to:
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
{
var value = valueResult.AttemptedValue.Replace('.', ',');
actualValue = Convert.ToDecimal(value,
CultureInfo.CurrentCulture);
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
if (bindingContext.ModelState[bindingContext.ModelName] == null)
{
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
}
return actualValue;
}
}
I added these lines:
var value = valueResult.AttemptedValue.Replace('.', ',');
if (bindingContext.ModelState[bindingContext.ModelName] == null)
The First one to replace ',' by '.' and the second one to check if don't have a ModelState with the same name avoiding exception.
Try specifying a Culture on the server that will use . as decimal separator such as:
<globalization uiCulture="en-US" culture="en-US" />
Alternatively if you want to use auto culture (i.e. infer from the client browser settings and the Accept-Language request header) then you could write a custom model binder for the decimal type and use the [DisplayFormat] attribute to explicitly set the desired format. I have illustrated this at the following post: https://stackoverflow.com/a/11272982/29407 (it's for the DateTime type but you could trivially easy adapt the example for the decimal type as well)

Escape Certain Characters On Model Property in ASP.NET MVC

Can I create an attribute that will let me modify the value of it in my ASP.NET MVC Model? It relates to this question below where '%' is being sent to the database, but I would like a generic way to escape certain characters with the data comes from the UI. I know you can validate properties, but can you modify them on the SET?
MySQL and LIKE comparison with %
[Clean]
public string FirstName { get; set; }
[Clean]
public string LastName{ get; set; }
Does this have a lot of value over just calling a clean method in the setter for each property? I worry that even if this were possible, it would introduce a lot of complexity depending on what the expected behavior was.
My suggestion is to just make a function and call it from the setter instead.
I think your Attribute should be at the class level to get access to this class properties
Lets say :
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class ClearAttribute : ValidationAttribute
{
private string[] wantedProperties;
public ClearAttribute(params string[] properties)
{
wantedProperties = properties;
}
public override object TypeId
{
get { return new object(); }
}
public override bool IsValid(object value)
{
PropertyInfo[] properties = value.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
if (wantedProperties.Contains(property.Name))
{
var oldValue = property.GetValue(value, null).ToString();
var newValue = oldValue + "Anything you want because i don't know a lot about your case";
property.SetValue(value, newValue, null);
}
}
return true;
}
}
And the usage should be:
[Clear("First")]
public class TestMe{
public string First {get; set;}
public string Second {get; set;}
}
Hope this helped :)
All you have to do is create a Custom Model Binder and override the SetProperty method to do the clean up.
public class CustomModelBinder: DefaultModelBinder
{
protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
{
if (propertyDescriptor.Attributes.Contains(new Clean()) && propertyDescriptor.PropertyType == typeof(string))
{
value = value != null ? ((string)value).Replace("%", "") : value;
}
base.SetProperty(controllerContext, bindingContext, propertyDescriptor, value);
}
}
You can employ any of these options to use your custom model binder.
Registering the custom binder for a particular model in Global.asax.cs
ModelBinders.Binders.Add(typeof(MyModel), new CustomModelBinder());
Registering the custom binder in action parameter
public ActionResult Save([ModelBinder(typeof(CustomModelBinder))]MyModel myModel)
{
}
Registering the custom binder as the default model binder.
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();

Format datetime in asp.net mvc 4

How can I force the format of datetime in asp.net mvc 4 ?
In display mode it shows as I want but in edit model it doesn't.
I am using displayfor and editorfor and applyformatineditmode=true with dataformatstring="{0:dd/MM/yyyy}"
What I have tried:
globalization in web.config (both of them) with my culture and uiculture.
modifying the culture and uiculture in application_start()
custom modelbinder for datetime
I have no idea how to force it and I need to input the date as dd/MM/yyyy not the default.
MORE INFO:
my viewmodel is like this
[DisplayName("date of birth")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public DateTime? Birth { get; set; }
in view I use #Html.DisplayFor(m=>m.Birth) but this works as expected (I see the formatting)
and to input the date I use #Html.EditorFor(m=>m.Birth) but if I try and input something like 13/12/2000 is fails with the error that it is not a valid date (12/13/2000 and 2000/12/13 are working as expected but I need dd/MM/yyyy).
The custom modelbinder is called in application_start() b/c I don't know where else.
Using <globalization/> I have tried with culture="ro-RO", uiCulture="ro" and other cultures that would give me dd/MM/yyyy.
I have also tried to set it on a per thread basis in application_start() (there are a lot of examples here, on how to do this)
For all that will read this question:
It seems that Darin Dimitrov's answer will work as long as I don't have client validation.
Another approach is to use custom validation including client side validation.
I'm glad I found this out before recreating the entire application.
Ahhhh, now it is clear. You seem to have problems binding back the value. Not with displaying it on the view. Indeed, that's the fault of the default model binder. You could write and use a custom one that will take into consideration the [DisplayFormat] attribute on your model. I have illustrated such a custom model binder here: https://stackoverflow.com/a/7836093/29407
Apparently some problems still persist. Here's my full setup working perfectly fine on both ASP.NET MVC 3 & 4 RC.
Model:
public class MyViewModel
{
[DisplayName("date of birth")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public DateTime? Birth { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel
{
Birth = DateTime.Now
});
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return View(model);
}
}
View:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.LabelFor(x => x.Birth)
#Html.EditorFor(x => x.Birth)
#Html.ValidationMessageFor(x => x.Birth)
<button type="submit">OK</button>
}
Registration of the custom model binder in Application_Start:
ModelBinders.Binders.Add(typeof(DateTime?), new MyDateTimeModelBinder());
And the custom model binder itself:
public class MyDateTimeModelBinder : DefaultModelBinder
{
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var displayFormat = bindingContext.ModelMetadata.DisplayFormatString;
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (!string.IsNullOrEmpty(displayFormat) && value != null)
{
DateTime date;
displayFormat = displayFormat.Replace("{0:", string.Empty).Replace("}", string.Empty);
// use the format specified in the DisplayFormat attribute to parse the date
if (DateTime.TryParseExact(value.AttemptedValue, displayFormat, CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
{
return date;
}
else
{
bindingContext.ModelState.AddModelError(
bindingContext.ModelName,
string.Format("{0} is an invalid date format", value.AttemptedValue)
);
}
}
return base.BindModel(controllerContext, bindingContext);
}
}
Now, no matter what culture you have setup in your web.config (<globalization> element) or the current thread culture, the custom model binder will use the DisplayFormat attribute's date format when parsing nullable dates.
Client validation issues can occur because of MVC bug (even in MVC 5) in jquery.validate.unobtrusive.min.js which does not accept date/datetime format in any way. Unfortunately you have to solve it manually.
My finally working solution:
$(function () {
$.validator.methods.date = function (value, element) {
return this.optional(element) || moment(value, "DD.MM.YYYY", true).isValid();
}
});
You have to include before:
#Scripts.Render("~/Scripts/jquery-3.1.1.js")
#Scripts.Render("~/Scripts/jquery.validate.min.js")
#Scripts.Render("~/Scripts/jquery.validate.unobtrusive.min.js")
#Scripts.Render("~/Scripts/moment.js")
You can install moment.js using:
Install-Package Moment.js
Thanks Darin,
For me, to be able to post to the create method, It only worked after I modified the BindModel code to :
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
var displayFormat = bindingContext.ModelMetadata.DisplayFormatString;
var value = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (!string.IsNullOrEmpty(displayFormat) && value != null)
{
DateTime date;
displayFormat = displayFormat.Replace("{0:", string.Empty).Replace("}", string.Empty);
// use the format specified in the DisplayFormat attribute to parse the date
if (DateTime.TryParse(value.AttemptedValue, CultureInfo.GetCultureInfo("en-GB"), DateTimeStyles.None, out date))
{
return date;
}
else
{
bindingContext.ModelState.AddModelError(
bindingContext.ModelName,
string.Format("{0} is an invalid date format", value.AttemptedValue)
);
}
}
return base.BindModel(controllerContext, bindingContext);
}
Hope this could help someone else...

MVC UpdateModel when the names don't match up

Let's say that you have a Model that looks kind of like this:
public class MyClass {
public string Name { get; set; }
public DateTime MyDate { get; set; }
}
The default edit template that Visual Studio gives you is a plain textbox for the MyDate property. This is all fine and good, but let's say that you need to split that up into it's Month/Day/Year components, and your form looks like:
<label for="MyDate">Date:</label>
<%= Html.TextBox("MyDate-Month", Model.MyDate.Month) %>
<%= Html.TextBox("MyDate-Day", Model.MyDate.Day) %>
<%= Html.TextBox("MyDate-Year", Model.MyDate.Year) %>
When this is submitted, a call to UpdateModel won't work, since there isn't a definition for MyDate-Month. Is there a way to add a custom binder to the project to handle situations like this, or if the HTML inputs are named differently (for whatever reasons)?
One workaround I've found is to use JavaScript to inject a hidden input into the form before submission that concatenates the fields and is named properly, but that feels wrong.
I would suggest you a custom model binder:
using System;
using System.Globalization;
using System.Web.Mvc;
public class MyClassBinder : DefaultModelBinder
{
protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
{
var model = (MyClass)base.CreateModel(controllerContext, bindingContext, modelType);
var day = bindingContext.ValueProvider["MyDate-Day"];
var month = bindingContext.ValueProvider["MyDate-Month"];
var year = bindingContext.ValueProvider["MyDate-Year"];
var dateStr = string.Format("{0}/{1}/{2}", month.AttemptedValue, day.AttemptedValue, year.AttemptedValue);
DateTime date;
if (DateTime.TryParseExact(dateStr, "MM/dd/yyyy", null, DateTimeStyles.None, out date))
{
model.MyDate = date;
}
else
{
bindingContext.ModelState.AddModelError("MyDate", "MyDate has invalid format");
}
bindingContext.ModelState.SetModelValue("MyDate-Day", day);
bindingContext.ModelState.SetModelValue("MyDate-Month", month);
bindingContext.ModelState.SetModelValue("MyDate-Year", year);
return model;
}
}
This simplifies your controller action to:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult MyAction(MyClass myClass)
{
if (!ModelState.IsValid)
{
return View(myClass);
}
// Do something with myClass
return RedirectToAction("success");
}
And register the binder in Global.asax:
protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders.Add(typeof(MyClass), new MyClassBinder());
}
A simple way to handle this would be to get the values manually from the ValueProvider and construct the date server side, using UpdateModel with a white list that excludes these properties.
int month = int.Parse( this.ValueProvider["MyDate-Month"].AttemptedValue );
int day = ...
int year = ...
var model = db.Models.Where( m = > m.ID == id );
var whitelist = new string[] { "Name", "Company", ... };
UpdateModel( model, whitelist );
model.MyDate = new DateTime( year, month, day );
Of course, you'd need to add validation/error handling manually as well.

Decimal values with thousand separator in Asp.Net MVC

I have a custom model class which contains a decimal member and a view to accept entry for this class. Everything worked well till I added javascripts to format the number inside input control. The format code format the inputted number with thousand separator ',' when focus blur.
The problem is that the decimal value inside my modal class isn't bind/parsed well with thousand separator. ModelState.IsValid returns false when I tested it with "1,000.00" but it is valid for "100.00" without any changes.
Could you share with me if you have any solution for this?
Thanks in advance.
Sample Class
public class Employee
{
public string Name { get; set; }
public decimal Salary { get; set; }
}
Sample Controller
public class EmployeeController : Controller
{
[AcceptVerbs(HttpVerbs.Get)]
public ActionResult New()
{
return View();
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult New(Employee e)
{
if (ModelState.IsValid) // <-- It is retruning false for values with ','
{
//Subsequence codes if entry is valid.
//
}
return View(e);
}
}
Sample View
<% using (Html.BeginForm())
{ %>
Name: <%= Html.TextBox("Name")%><br />
Salary: <%= Html.TextBox("Salary")%><br />
<button type="submit">Save</button>
<% } %>
I tried a workaround with Custom ModelBinder as Alexander suggested. The probelm solved. But the solution doesn't go well with IDataErrorInfo implementation. The Salary value become null when 0 is entered because of the validation. Any suggestion, please?
Do Asp.Net MVC team members come to stackoverflow? Can I get a little help from you?
Updated Code with Custom Model Binder as Alexander suggested
Model Binder
public class MyModelBinder : DefaultModelBinder {
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
if (bindingContext == null) {
throw new ArgumentNullException("bindingContext");
}
ValueProviderResult valueResult;
bindingContext.ValueProvider.TryGetValue(bindingContext.ModelName, out valueResult);
if (valueResult != null) {
if (bindingContext.ModelType == typeof(decimal)) {
decimal decimalAttempt;
decimalAttempt = Convert.ToDecimal(valueResult.AttemptedValue);
return decimalAttempt;
}
}
return null;
}
}
Employee Class
public class Employee : IDataErrorInfo {
public string Name { get; set; }
public decimal Salary { get; set; }
#region IDataErrorInfo Members
public string this[string columnName] {
get {
switch (columnName)
{
case "Salary": if (Salary <= 0) return "Invalid salary amount."; break;
}
return string.Empty;
}
}
public string Error{
get {
return string.Empty;
}
}
#endregion
}
The reason behind it is, that in ConvertSimpleType in ValueProviderResult.cs a TypeConverter is used.
The TypeConverter for decimal does not support a thousand separator.
Read here about it: http://social.msdn.microsoft.com/forums/en-US/clr/thread/1c444dac-5d08-487d-9369-666d1b21706e
I did not check yet, but at that post they even said the CultureInfo passed into TypeConverter is not used. It will always be Invariant.
string decValue = "1,400.23";
TypeConverter converter = TypeDescriptor.GetConverter(typeof(decimal));
object convertedValue = converter.ConvertFrom(null /* context */, CultureInfo.InvariantCulture, decValue);
So I guess you have to use a workaround. Not nice...
I didn't like the solutions above and came up with this:
In my custom modelbinder, I basically replace the value with the culture invariant value if it is a decimal and then hand over the rest of the work to the default model binder.
The rawvalue being a array seems strange to me, but this is what I saw/stole in the original code.
public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
if(bindingContext.ModelType == typeof(decimal) || bindingContext.ModelType==typeof(Nullable<decimal>))
{
ValueProviderResult valueProviderResult = bindingContext.ValueProvider[bindingContext.ModelName];
if (valueProviderResult != null)
{
decimal result;
var array = valueProviderResult.RawValue as Array;
object value;
if (array != null && array.Length > 0)
{
value = array.GetValue(0);
if (decimal.TryParse(value.ToString(), out result))
{
string val = result.ToString(CultureInfo.InvariantCulture.NumberFormat);
array.SetValue(val, 0);
}
}
}
}
return base.BindModel(controllerContext, bindingContext);
}
It seems there are always workarounds of some form or another to be found in order to make the default model binder happy! I wonder if you could create a "pseudo" property that is used only by the model binder? (Note, this is by no means elegant. Myself, I seem to resort to similar tricks like this more and more often simply because they work and they get the job "done"...) Note also, if you were using a separate "ViewModel" (which I recommend for this), you could put this code in there, and leave your domain model nice and clean.
public class Employee
{
private decimal _Salary;
public string MvcSalary // yes, a string. Bind your form values to this!
{
get { return _Salary.ToString(); }
set
{
// (Using some pseudo-code here in this pseudo-property!)
if (AppearsToBeValidDecimal(value)) {
_Salary = StripCommas(value);
}
}
}
public decimal Salary
{
get { return _Salary; }
set { _Salary = value; }
}
}
P.S., after I typed this up, I look back at it now and am even hesitating to post this, it is so ugly! But if you think it might be helpful I'll let you decide...
Best of luck!
-Mike
I implement custom validator, adding validity of grouping.
The problem (that i solved in code below)is that parse method remove all thousands separator, so also 1,2,2 is considered valid.
Here my binder for decimal
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace EA.BUTruck.ContactCenter.Model.Extensions
{
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
{
var trimmedvalue = valueResult.AttemptedValue.Trim();
actualValue = Decimal.Parse(trimmedvalue, CultureInfo.CurrentCulture);
string decimalSep = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
string thousandSep = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
thousandSep = Regex.Replace(thousandSep, #"\u00A0", " "); //used for culture with non breaking space thousand separator
if (trimmedvalue.IndexOf(thousandSep) >= 0)
{
//check validity of grouping thousand separator
//remove the "decimal" part if exists
string integerpart = trimmedvalue.Split(new string[] { decimalSep }, StringSplitOptions.None)[0];
//recovert double value (need to replace non breaking space with space present in some cultures)
string reconvertedvalue = Regex.Replace(((decimal)actualValue).ToString("N").Split(new string[] { decimalSep }, StringSplitOptions.None)[0], #"\u00A0", " ");
//if are the same, it is a valid number
if (integerpart == reconvertedvalue)
return actualValue;
//if not, could be differences only in the part before first thousand separator (for example original input stirng could be +1.000,00 (example of italian culture) that is valid but different from reconverted value that is 1.000,00; so we need to make a more accurate checking to verify if input string is valid
//check if number of thousands separators are the same
int nThousands = integerpart.Count(x => x == thousandSep[0]);
int nThousandsconverted = reconvertedvalue.Count(x => x == thousandSep[0]);
if (nThousands == nThousandsconverted)
{
//check if all group are of groupsize number characters (exclude the first, because could be more than 3 (because for example "+", or "0" before all the other numbers) but we checked number of separators == reconverted number separators
int[] groupsize = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSizes;
bool valid = ValidateNumberGroups(integerpart, thousandSep, groupsize);
if (!valid)
throw new FormatException();
}
else
throw new FormatException();
}
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
private bool ValidateNumberGroups(string value, string thousandSep, int[] groupsize)
{
string[] parts = value.Split(new string[] { thousandSep }, StringSplitOptions.None);
for (int i = parts.Length - 1; i > 0; i--)
{
string part = parts[i];
int length = part.Length;
if (groupsize.Contains(length) == false)
{
return false;
}
}
return true;
}
}
}
For decimal? nullable you need to add a little code before
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web.Mvc;
namespace EA.BUTruck.ContactCenter.Model.Extensions
{
public class DecimalNullableModelBinder : 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
{
//need this condition against non nullable decimal
if (string.IsNullOrWhiteSpace(valueResult.AttemptedValue))
return actualValue;
var trimmedvalue = valueResult.AttemptedValue.Trim();
actualValue = Decimal.Parse(trimmedvalue,CultureInfo.CurrentCulture);
string decimalSep = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
string thousandSep = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
thousandSep = Regex.Replace(thousandSep, #"\u00A0", " "); //used for culture with non breaking space thousand separator
if (trimmedvalue.IndexOf(thousandSep) >=0)
{
//check validity of grouping thousand separator
//remove the "decimal" part if exists
string integerpart = trimmedvalue.Split(new string[] { decimalSep }, StringSplitOptions.None)[0];
//recovert double value (need to replace non breaking space with space present in some cultures)
string reconvertedvalue = Regex.Replace(((decimal)actualValue).ToString("N").Split(new string[] { decimalSep }, StringSplitOptions.None)[0], #"\u00A0", " ");
//if are the same, it is a valid number
if (integerpart == reconvertedvalue)
return actualValue;
//if not, could be differences only in the part before first thousand separator (for example original input stirng could be +1.000,00 (example of italian culture) that is valid but different from reconverted value that is 1.000,00; so we need to make a more accurate checking to verify if input string is valid
//check if number of thousands separators are the same
int nThousands = integerpart.Count(x => x == thousandSep[0]);
int nThousandsconverted = reconvertedvalue.Count(x => x == thousandSep[0]);
if(nThousands == nThousandsconverted)
{
//check if all group are of groupsize number characters (exclude the first, because could be more than 3 (because for example "+", or "0" before all the other numbers) but we checked number of separators == reconverted number separators
int[] groupsize = CultureInfo.CurrentCulture.NumberFormat.NumberGroupSizes;
bool valid = ValidateNumberGroups(integerpart, thousandSep, groupsize);
if (!valid)
throw new FormatException();
}
else
throw new FormatException();
}
}
catch (FormatException e)
{
modelState.Errors.Add(e);
}
bindingContext.ModelState.Add(bindingContext.ModelName, modelState);
return actualValue;
}
private bool ValidateNumberGroups(string value, string thousandSep, int[] groupsize)
{
string[] parts = value.Split(new string[] { thousandSep }, StringSplitOptions.None);
for(int i = parts.Length-1; i > 0; i--)
{
string part = parts[i];
int length = part.Length;
if (groupsize.Contains(length) == false)
{
return false;
}
}
return true;
}
}
}
You need to create similar binder for double, double?, float, float? (the code is the same of DecimalModelBinder and DecimalNullableModelBinder; you need just to replace type in 2 point where there is "decimal").
Then in global.asax
ModelBinders.Binders.Add(typeof(decimal), new DecimalModelBinder());
ModelBinders.Binders.Add(typeof(decimal?), new DecimalNullableModelBinder());
ModelBinders.Binders.Add(typeof(float), new FloatModelBinder());
ModelBinders.Binders.Add(typeof(float?), new FloatNullableModelBinder());
ModelBinders.Binders.Add(typeof(double), new DoubleModelBinder());
ModelBinders.Binders.Add(typeof(double?), new DoubleNullableModelBinder());
This solution works fine on server side, like the client part using jquery globalize and my fixing reported here
https://github.com/globalizejs/globalize/issues/73#issuecomment-275792643
Did you try to convert it to Decimal in the controller? This should do the trick:
string _val = "1,000.00";
Decimal _decVal = Convert.ToDecimal(_val);
Console.WriteLine(_decVal.ToString());
Hey I had one more thought... This builds on Naweed's answer, but will still let you use the default model binder. The concept is to intercept the posted form, modify some of the values in it, then pass the [modified] form collection to the UpdateModel (default model binder) method... I use a modified version of this for dealing with checkboxes/booleans, to avoid the situation where anything other than "true" or "false" causes an unhandled/silent exception within the model binder.
(You would of course want to refactor this to be more re-useable, to perhaps deal with all decimals)
public ActionResult myAction(NameValueCollection nvc)
{
Employee employee = new Employee();
string salary = nvc.Get("Salary");
if (AppearsToBeValidDecimal(salary)) {
nvc.Remove("Salary");
nvc.Add("Salary", StripCommas(salary));
}
if (TryUpdateModel(employee, nvc)) {
// ...
}
}
P.S., I may be confused on my NVC methods, but I think these will work.

Resources