How to specify array index in ModelState when validating custom validation attribute? - asp.net-mvc

I have the following model (simplified version):
public class AddZoneModel
{
/// <summary>
/// Gets or sets the name of the zone.
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// Gets or sets the minimum of optimal disponibilities.
/// </summary>
[Required]
[DisponibilityCollection]
public int[] Disponibilities { get; set; }
}
The array of ints should have exactly 12 (monthly) values, each value within a range taken from the database.
This is validated in the following attribute:
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class DisponibilityCollectionAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
var values = value as int[];
if (values == null || values.Length != 12)
{
return new ValidationResult("The specified object is not a collection of 12 integers", new string[] { validationContext.MemberName });
}
using (AdminRepository db = new AdminRepository())
{
ValidationRangesModel ranges = db.GetValidationRanges();
int min = ranges.MinimumDisponibilidad;
int max = ranges.MaximumDisponibilidad;
var errorMembers = new List<string>();
for (int i = 0; i < 12; ++i)
{
if (values[i] < min || values[i] > max)
{
errorMembers.Add(string.Format("Disponibilities[{0}]", i));
}
}
if (errorMembers.Count > 0)
return new ValidationResult(string.Format("Disponibilidad should be between {0} and {1}", min, max), errorMembers);
}
return ValidationResult.Success;
}
}
If the ModelState is not valid, my API returns a JSON object with a parsed version of the model state errors.
Problem is that the validated array does not show which item was invalid.
The ModelState contains only one message for the array property itself "Disponibilities".
This breaks error validation, as I have 12 textboxes.
(Actually I have a lot more than 12 textboxes, because the pattern repeats itself. This is simplified)
How do I customize the property names so they will be like "Disponibilities[2]" or "Disponibilities_2_" so I can recognize the appropriate controls with JS and show validation errors?

Related

Custom Model Validator for Integer value in ASP.NET Core Web API

I have developed a custom validator Attribute class for checking Integer values in my model classes. But the problem is this class is not working. I have debugged my code but the breakpoint is not hit during debugging the code. Here is my code:
public class ValidateIntegerValueAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
if (value != null)
{
int output;
var isInteger = int.TryParse(value.ToString(), out output);
if (!isInteger)
{
return new ValidationResult("Must be a Integer number");
}
}
return ValidationResult.Success;
}
}
I have also an Filter class for model validation globally in application request pipeline. Here is my code:
public class MyModelValidatorFilter: IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
if (context.ModelState.IsValid)
return;
var errors = new Dictionary<string, string[]>();
foreach (var err in actionContext.ModelState)
{
var itemErrors = new List<string>();
foreach (var error in err.Value.Errors){
itemErrors.Add(error.Exception.Message);
}
errors.Add(err.Key, itemErrors.ToArray());
}
actionContext.Result = new OkObjectResult(new MyResponse
{
Errors = errors
});
}
}
The model class with validation is below:
public class MyModelClass
{
[ValidateIntegerValue(ErrorMessage = "{0} must be a Integer Value")]
[Required(ErrorMessage = "{0} is required")]
public int Level { get; set; }
}
Can anyone please let me know why the attribute integer validation class is not working.
Model validation comes into play after the model is deserialized from the request. If the model contains integer field Level and you send value that could not be deserialized as integer (e.g. "abc"), then model will not be even deserialized. As result, validation attribute will also not be called - there is just no model for validation.
Taking this, there is no much sense in implementing such ValidateIntegerValueAttribute. Such validation is already performed by deserializer, JSON.Net in this case. You could verify this by checking model state in controller action. ModelState.IsValid will be set to false and ModelState errors bag will contain following error:
Newtonsoft.Json.JsonReaderException: Could not convert string to
integer: abc. Path 'Level', ...
One more thing to add: for correct work of Required validation attribute, you should make the underlying property nullable. Without this, the property will be left at its default value (0) after model deserializer. Model validation has no ability to distinguish between missed value and value equal to default one. So for correct work of Required attribute make the property nullable:
public class MyModelClass
{
[Required(ErrorMessage = "{0} is required")]
public int? Level { get; set; }
}

MVC data annotations - dynamic string length

I have a data attribute for the string length defined as below for 40 characters.
[Display(Name = "Name"), StringLength(40, ErrorMessage = "The name cannot be more than 40 characters")]
public string EmployeeName { get; set; }
Now the requirement is changed to get that value from the service.
Is there a way to get that value into these data attributes something like:
string s = 50; //Let's say calls the service to get this value
[Display(Name = "Name"), StringLength(s, ErrorMessage = "The name cannot be more than {0} characters")]
public string EmployeeName { get; set; }
You can not pass a non-constant value to an attribute, so your solution can not be implemented.
However, you can pass a const value from config file. If you are accepting behaviour like the following: validation of the string will be of single maximum length for a whole lifetime of application and to change it you should reboot an app, take a look at variables in application config.
If you does not accept this behaviour, the one of possible solutions is to store your MaxLength somewhere in database and create your own StringLengthAttribute, which will query DB (or another data source) during valigation in the following way:
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
sealed class MyValidationAttribute : ValidationAttribute
{
public MyValidationAttribute()
{
}
public override bool IsValid(object value)
{
if (value != null && value.GetType() == typeof(string))
{
int maxLength = //query your data source
return ((string)value).Length <= maxLength;
}
return base.IsValid(value);
}
}
Another possible solution is to perform client-side validation instead of server-side. If you will query your data source from the client-side it will look better, than querying it from attribute, in my opinion.
You cannot pass dynamic value to the attribute, but you can retrieve the dynamic value it self inside the attribute implementation and pass only a key to the attribute. ex:
public class DynamicLengthAttribute : ValidationAttribute
{
private string _lengthKey;
public DynamicLengthAttribute (string lengthKey)
{
_lengthKey = lengthKey;
}
public override bool IsValid(object value)
{
if (value != null && value.GetType() == typeof(string))
{
//retrive teh max length from the database according to the lengthKey variable, if you will store it in web.config you can do:
int maxLength = ConfigurationManager.AppSettings[_lengthKey];
return ((string)value).Length <= maxLength;
}
return base.IsValid(value);
}
}
and in your model
[DynamicLength(maxLengths="EmpNameMaxLength", ErrorMessage = "The name cannot be more than {0} characters")]
public string EmployeeName { get; set; }

Writing a custom attribute with an infinite number of matching parameters

I've written an If-IsRequired custom attribute to validate that a property contains a value depending on the values of some other properties in the model. Since I want to make this attribute apply to as many situations as possible, I want to allow the option for the developer leveraging the attribute to supply an infinite number of matched parameters. And lastly, I want to be able to enforce that all the parameters are matched correctly.
This is what I've written thus far. While I'm currently using arrays of strings, I'd be perfectly happy to use some sort of collection, which been unable to work. In addition, I now have a need to support the current attribute definition and create a new overload that includes the comparison operator. This will allow me to make less than, greater than, and not equal comparisons in addition to the original definition which just assumes all comparisons are done with equals.
/// <summary>
/// A custom attribute that checks the value of other properties passed to it in order to
/// determine if the property this attribute is bound to should be required.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true, Inherited = true)]
public class IsPropertyRequiredAttribute : ValidationAttribute
{
private const string DefaultErrorMessage = "{0} is required.";
public string[] _selectionContextNames { get; private set; }
public string[] _expectedValues { get; private set; }
/// <summary>
/// Creates a new instance of the IsPropertyRequriedAttribute.
/// </summary>
/// <param name="SelectionContextNames">The name of the other property in the view model to check the value of.</param>
/// <param name="ExpectedValues">The expected value of the other property in the view model in order to determine if the current property the attribute is bound to should be required.</param>
public IsPropertyRequiredAttribute(string[] SelectionContextNames, string ExpectedValues)
: base(DefaultErrorMessage)
{
_selectionContextNames = SelectionContextNames;
_expectedValues = ExpectedValues;
}
public override bool IsValid(object value)
{
if (_selectionContextNames == null || _expectedValues == null)
{
if (_selectionContextNames != null || _expectedValues != null)
{
string paramName;
if (_selectionContextNames == null)
{
paramName = "ExpectedValues";
}
else
{
paramName = "SelectionContextNames";
}
throw new ArgumentException("Key/Value pairs need to match for IsPropertyRequired.", paramName);
}
}
else if (_selectionContextNames.Length != _expectedValues.Length)
{
string paramName;
if (_selectionContextNames.Length < _expectedValues.Length)
{
paramName = "ExpectedValues";
}
else
{
paramName = "SelectionContextNames";
}
throw new ArgumentException("Parameter element counts need to match for IsPropertyRequired.", paramName);
}
bool paramsValid = true;
if (_selectionContextName!= null)
{
for (int i = 0; i < _selectionContextName.Length; i++)
{
string paramValue = HttpContext.Current.Request[_selectionContextName[i]];
if (_expectedValue[i] != paramValue)
{
paramsValid = false;
}
}
if (paramsValid == true)
{
return (value != null);
}
else
{
return true;
}
}
else
{
return true;
}
}
public override string FormatErrorMessage(string name)
{
return String.Format(DefaultErrorMessage, name);
}
}
While using the attribute to decorate the property will depend on how the attribute is defined, this is what I have currently implemented (which could also probably be improved):
[IsPropertyRequired(new string[] {"prop1", "prop2", "prop3", "prop4"}, new string[] {"1", "2", "3", "4"})]
public string SomeText { get; set; }
Also, I want to prevent, as much as I can, the following decoration from happening:
[IsPropertyRequired(new string[] {"prop1", "prop2", "prop3", "prop4", "prop5withoutvalue"}, new string[] {"1", "2", "3", "4"})]
public string SomeOtherText { get; set; }
And with the new overload including comparison operators as a parameter, we could now have:
[IsPropertyRequired(new string[] {"prop1", "prop2", "prop3", "prop4"}, new string[] {"==", ">", "!=", "<="}, new string[] {"1", "2", "3", "4"})]
public string SomeComparisonText { get; set; }
Attributes in .NET are very limited in the allowed types you can specify, as mentioned on MSDN. If you want more complex data to be specified, I would recommend writing the attribute to specify an alternate location for the richer data structure.
For example, imagine an attribute with this syntax:
[ValidationRules(typeof(MyValidationRuleInfo, "MyRuleSet")]
public int SomeProperty { get; set; }
...
public static class MyValidationRuleInfo {
public static Dictionary<string, ValidationRule> MyRuleSet {
get {
return new { ... rules go here ... }
}
}
And the attribute would look up the property on the target class and get all the rules there. It's still up to you to implement all the logic of all the rules, but you get to avoid attribute soup, and you also avoid unwieldy data structures.
In fact, the xUnit.NET unit testing library does something similar with its Theory and PropertyData attributes, as shown here.

How to handle Booleans/CheckBoxes in ASP.NET MVC 2 with DataAnnotations?

I've got a view model like this:
public class SignUpViewModel
{
[Required(ErrorMessage = "Bitte lesen und akzeptieren Sie die AGB.")]
[DisplayName("Ich habe die AGB gelesen und akzeptiere diese.")]
public bool AgreesWithTerms { get; set; }
}
The view markup code:
<%= Html.CheckBoxFor(m => m.AgreesWithTerms) %>
<%= Html.LabelFor(m => m.AgreesWithTerms)%>
The result:
No validation is executed. That's okay so far because bool is a value type and never null. But even if I make AgreesWithTerms nullable it won't work because the compiler shouts
"Templates can be used only with field access, property access, single-dimension array index, or single-parameter custom indexer expressions."
So, what's the correct way to handle this?
My Solution is as follows (it's not much different to the answers already submitted, but I believe it's named better):
/// <summary>
/// Validation attribute that demands that a boolean value must be true.
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class MustBeTrueAttribute : ValidationAttribute
{
public override bool IsValid(object value)
{
return value != null && value is bool && (bool)value;
}
}
Then you can use it like this in your model:
[MustBeTrue(ErrorMessage = "You must accept the terms and conditions")]
[DisplayName("Accept terms and conditions")]
public bool AcceptsTerms { get; set; }
I would create a validator for both Server AND Client side. Using MVC and unobtrusive form validation, this can be achieved simply by doing the following:
Firstly, create a class in your project to perform the server side validation like so:
public class EnforceTrueAttribute : ValidationAttribute, IClientValidatable
{
public override bool IsValid(object value)
{
if (value == null) return false;
if (value.GetType() != typeof(bool)) throw new InvalidOperationException("can only be used on boolean properties.");
return (bool)value == true;
}
public override string FormatErrorMessage(string name)
{
return "The " + name + " field must be checked in order to continue.";
}
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = String.IsNullOrEmpty(ErrorMessage) ? FormatErrorMessage(metadata.DisplayName) : ErrorMessage,
ValidationType = "enforcetrue"
};
}
}
Following this, annotate the appropriate property in your model:
[EnforceTrue(ErrorMessage=#"Error Message")]
public bool ThisMustBeTrue{ get; set; }
And Finally, enable client side validation by adding the following script to your View:
<script type="text/javascript">
jQuery.validator.addMethod("enforcetrue", function (value, element, param) {
return element.checked;
});
jQuery.validator.unobtrusive.adapters.addBool("enforcetrue");
</script>
Note: We already created a method GetClientValidationRules which pushes our annotation to the view from our model.
I got it by creating a custom attribute:
public class BooleanRequiredAttribute : RequiredAttribute
{
public override bool IsValid(object value)
{
return value != null && (bool) value;
}
}
This might be a "hack" but you can use the built in Range attribute:
[Display(Name = "Accepted Terms Of Service")]
[Range(typeof(bool), "true", "true")]
public bool Terms { get; set; }
The only problem is the "warning" string will say "The FIELDNAME must be between True and true".
[Compare("Remember", ErrorMessage = "You must accept the terms and conditions")]
public bool Remember { get; set; }
I'm just taking the best of the existing solutions and putting it together into a single answer that allows for both server side and client side validation.
The to apply to model a properties to ensure a bool value must be true:
/// <summary>
/// Validation attribute that demands that a <see cref="bool"/> value must be true.
/// </summary>
/// <remarks>Thank you <c>http://stackoverflow.com/a/22511718</c></remarks>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class MustBeTrueAttribute : ValidationAttribute, IClientValidatable
{
/// <summary>
/// Initializes a new instance of the <see cref="MustBeTrueAttribute" /> class.
/// </summary>
public MustBeTrueAttribute()
: base(() => "The field {0} must be checked.")
{
}
/// <summary>
/// Checks to see if the given object in <paramref name="value"/> is <c>true</c>.
/// </summary>
/// <param name="value">The value to check.</param>
/// <returns><c>true</c> if the object is a <see cref="bool"/> and <c>true</c>; otherwise <c>false</c>.</returns>
public override bool IsValid(object value)
{
return (value as bool?).GetValueOrDefault();
}
/// <summary>
/// Returns client validation rules for <see cref="bool"/> values that must be true.
/// </summary>
/// <param name="metadata">The model metadata.</param>
/// <param name="context">The controller context.</param>
/// <returns>The client validation rules for this validator.</returns>
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
if (metadata == null)
throw new ArgumentNullException("metadata");
if (context == null)
throw new ArgumentNullException("context");
yield return new ModelClientValidationRule
{
ErrorMessage = FormatErrorMessage(metadata.DisplayName),
ValidationType = "mustbetrue",
};
}
}
The JavaScript to include to make use of unobtrusive validation.
jQuery.validator.addMethod("mustbetrue", function (value, element) {
return element.checked;
});
jQuery.validator.unobtrusive.adapters.addBool("mustbetrue");
"Required" is the wrong validation, here. You want something akin to "Must have the value true," which is not the same as "Required". What about using something like:
[RegularExpression("^true")]
?
My solution is this simple custom attribute for boolean values:
public class BooleanAttribute : ValidationAttribute
{
public bool Value
{
get;
set;
}
public override bool IsValid(object value)
{
return value != null && value is bool && (bool)value == Value;
}
}
Then you can use it like this in your model:
[Required]
[Boolean(Value = true, ErrorMessage = "You must accept the terms and conditions")]
[DisplayName("Accept terms and conditions")]
public bool AcceptsTerms { get; set; }
For people who are having trouble getting this working for validation on the client side (formerly me): make sure you have also
Included <% Html.EnableClientValidation(); %> before the form in the view
Used <%= Html.ValidationMessage or Html.ValidationMessageFor for the field
Created a DataAnnotationsModelValidator which returns a rule with a custom validation type
Registered the class deriving from DataAnnotationsModelValidator in the Global.Application_Start
http://www.highoncoding.com/Articles/729_Creating_Custom_Client_Side_Validation_in_ASP_NET_MVC_2_0.aspx
is a good tutorial on doing this, but misses step 4.
The proper way to do this is to check the type!
[Range(typeof(bool), "true", "true", ErrorMessage = "You must or else!")]
public bool AgreesWithTerms { get; set; }
Found a more complete solution here (both server and client side validation):
http://blog.degree.no/2012/03/validation-of-required-checkbox-in-asp-net-mvc/#comments
It's enough to add [RegularExpression]:
[DisplayName("I accept terms and conditions")]
[RegularExpression("True", ErrorMessage = "You must accept the terms and conditions")]
public bool AgreesWithTerms { get; set; }
Note - "True" must start with capital T

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