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);
}
}
}