remote net mvc for custom asp c# asp.net-mvc decimal

c# - for - ASP.NET MVC valor decimal vinculante



remote validation asp net core (4)

Basado en un comentario de un artículo sobre el enlace de modelos decimales de Phil Haack here , creo que parte de la respuesta al "por qué" es que la cultura en los navegadores es complicada y no se puede garantizar que la cultura de su aplicación sea la misma. Configuraciones utilizadas por el usuario / navegador para decimales. En cualquier caso, se trata de un "problema" conocido y se han formulado preguntas similares anteriormente con una variedad de soluciones ofrecidas, además de las siguientes: ¿ Aceptar coma y punto como separador decimal y cómo establecer separadores decimales en los controladores MVC de ASP.NET? por ejemplo.

Estoy tratando de averiguar por qué el marco se niega a enlazar el valor "1,234.00" al decimal. ¿Cuál puede ser la razón para ello?

Valores como "123.00" o "123.0000" se unen correctamente.

Tengo el siguiente código que configura mi configuración de cultura en Global.asax

public void Application_AcquireRequestState(object sender, EventArgs e) { var culture = (CultureInfo)Thread.CurrentThread.CurrentCulture.Clone(); culture.NumberFormat.NumberDecimalSeparator = culture.NumberFormat.CurrencyDecimalSeparator = culture.NumberFormat.PercentDecimalSeparator = "."; culture.NumberFormat.NumberGroupSeparator = culture.NumberFormat.CurrencyGroupSeparator = culture.NumberFormat.PercentGroupSeparator = ","; Thread.CurrentThread.CurrentCulture = culture; }

La cultura francesa se establece como cultura por defecto en Web.Config.

<globalization uiCulture="fr-FR" culture="fr-FR" />

He buceado en fuentes de la clase ValueProviderResult de System.Web.Mvc.dll. Está utilizando System.ComponentModel.DecimalConverter.

converter.ConvertFrom((ITypeDescriptorContext) null, culture, value)

Aquí es donde el mensaje "1,234.0000 no es un valor válido para Decimal". viene de.

He intentado ejecutar el siguiente código en mi área de juegos:

static void Main() { var decConverter = TypeDescriptor.GetConverter(typeof(decimal)); var culture = new CultureInfo("fr-FR"); culture.NumberFormat.NumberDecimalSeparator = culture.NumberFormat.CurrencyDecimalSeparator = culture.NumberFormat.PercentDecimalSeparator = "."; culture.NumberFormat.NumberGroupSeparator = culture.NumberFormat.CurrencyGroupSeparator = culture.NumberFormat.PercentGroupSeparator = ","; Thread.CurrentThread.CurrentCulture = culture; var d1 = Decimal.Parse("1,232.000"); Console.Write("{0}", d1); // prints 1234.000 var d2 = decConverter.ConvertFrom((ITypeDescriptorContext)null, culture, "1,232.000"); // throws "1,234.0000 is not a valid value for Decimal." Console.Write("{0}", d2); }

DecimalConverter lanza la misma excepción. Decimal.Parse analiza correctamente la misma cadena.


El problema aquí parece ser los Estilos de número predeterminados aplicados a Decimal.Parse (cadena).

De la documentación de MSDN

Los indicadores de campo individuales restantes definen elementos de estilo que pueden estar presentes en la representación de cadena de un número decimal, pero no tienen que estar presentes, para que la operación de análisis tenga éxito.

Esto significa que tanto d1 como d2 a continuación analizan con éxito

var d1 = Decimal.Parse("1,232.000"); var d2 = Decimal.Parse("1,232.000", NumberStyles.Any);

Sin embargo, cuando se aplica el convertidor de tipo, parece que esto esencialmente solo permite los espacios de entrenamiento, permite el punto decimal y permite el signo principal. Como tal, el d3 express a continuación arrojará un error de tiempo de ejecución

var d3 = Decimal.Parse("1,232.000", NumberStyles.AllowLeadingSign | NumberStyles.AllowLeadingWhite | NumberStyles.AllowTrailingWhite | NumberStyles.AllowDecimalPoint);


El problema es que DecimalConverter.ConvertFrom no admite el indicador AllowThousands de la enumeración NumberStyles cuando llama a Number.Parse . ¡La buena noticia es que existe una manera de "enseñarlo" a hacerlo!

Decimal.Parse llama internamente a Number.Parse con el estilo de número establecido en Number , para el que la AllowThousands se establece en true.

[__DynamicallyInvokable] public static decimal Parse(string s) { return Number.ParseDecimal(s, NumberStyles.Number, NumberFormatInfo.CurrentInfo); }

Cuando recibe un convertidor de tipo del descriptor, en realidad obtiene una instancia de DecimalConverter . El método ConvertFrom es un tanto general y grande, por lo que solo cito las partes relevantes para el escenario actual aquí. Las partes faltantes están implementando soporte para cadenas hexadecimales y manejo de excepciones. 1

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string) { // ... string text = ((string)value).Trim(); if (culture == null) culture = CultureInfo.CurrentCulture; NumberFormatInfo formatInfo = (NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo)); return FromString(text, formatInfo); // ... } return base.ConvertFrom(context, culture, value); }

DecimalConverter también sobrescribe la implementación de FromString y ahí FromString el problema:

internal override object FromString(string value, NumberFormatInfo formatInfo) { return Decimal.Parse(value, NumberStyles.Float, formatInfo); }

¡Con el estilo de número establecido en Float , la bandera AllowThousands se establece en falso! Sin embargo, puede escribir un convertidor personalizado con unas pocas líneas de código que solucione este problema.

class NumericDecimalConverter : DecimalConverter { public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string) { string text = ((string)value).Trim(); if (culture == null) culture = CultureInfo.CurrentCulture; NumberFormatInfo formatInfo = (NumberFormatInfo)culture.GetFormat(typeof(NumberFormatInfo)); return Decimal.Parse(text, NumberStyles.Number, formatInfo); } else { return base.ConvertFrom(value); } } }

1 Tenga en cuenta que el código es similar a la implementación original. Si necesita el material "sin comillas", puede delegarlo directamente a la base o implementarlo por su cuenta. Puede ver la implementación utilizando ILSpy / DotPeek / etc. o mediante la depuración en ellos desde Visual Studio.

Finalmente, con un poco de ayuda de Reflection, ¡puedes configurar el convertidor de tipo para Decimal para que use tu nuevo personalizado!

TypeDescriptor.AddAttributes(typeof(decimal), new TypeConverterAttribute(typeof(NumericDecimalConverter)));


Puede intentar anular el DefaultModelBinder. Avísame si esto no funciona y eliminaré esta publicación. Realmente no armé una aplicación MVC y la probé, pero según la experiencia, esto debería funcionar:

public class CustomModelBinder : DefaultModelBinder { protected override void BindProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor) { if(propertyDescriptor.PropertyType == typeof(decimal)) { propertyDescriptor.SetValue(bindingContext.Model, double.Parse(propertyDescriptor.GetValue(bindingContext.Model).ToString())); base.BindProperty(controllerContext, bindingContext, propertyDescriptor); } else { base.BindProperty(controllerContext, bindingContext, propertyDescriptor); } } }