2015-08-26 18 views
16

Próbuję ustalić, dlaczego struktura nie chce powiązać wartości "1,234.00" z wartością dziesiętną. Jaki może być tego powód?Wartość dziesiętna wiązania MVC programu ASP.NET:

Wartości takie jak "123.00" lub "123.0000" wiążą się pomyślnie.

Mam następujący kod ustawienia moja kultura config w 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; 
    } 

francuska kultura jest ustawiony jako domyślny kultury w web.config

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

ja zanurkował źródeł Systemu Klasa ValueProviderResult .Web.Mvc.dll. Używa System.ComponentModel.DecimalConverter.

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

Tutaj pojawia się komunikat "1,234.0000 nie jest prawidłową wartością dziesiętną." pochodzi z.

Próbowałem uruchomić następujący kod w moim zabaw:

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 rzuca sam wyjątek. Decimal.Parse poprawnie analizuje ten sam ciąg.

Odpowiedz

10

Problem polega na tym, że DecimalConverter.ConvertFrom nie obsługuje AllowThousands flagę NumberStyles wyliczenie gdy wywołuje Number.Parse. Dobrą wiadomością jest to, że istnieje sposób, aby "nauczyć" tego robić!

Decimal.Parse wewnętrznie wywołuje Number.Parse ze stylem numeru ustawionym na Number, dla którego flaga AllowThousands jest ustawiona na true.

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

Podczas odbierania konwerter typu z deskryptora, rzeczywiście się wystąpienie DecimalConverter. Metoda ConvertFrom jest trochę ogólna i duża, więc przytaczam tylko odpowiednie części dla bieżącego scenariusza. Brakujące części implementują obsługę łańcuchów szesnastkowych i obsługę wyjątków.

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 nadpisuje również realizację FromString i tam problem podnosi:

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

ze stylem numerem ustawionym na Float, flaga AllowThousands jest ustawiona na false! Można jednak napisać niestandardowy konwerter z kilkoma liniami kodu, który rozwiązuje ten problem.

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

Należy pamiętać, że kod wygląda podobnie do oryginalnego realizacji. Jeśli potrzebujesz "niecytowanych" rzeczy, możesz przekazać je bezpośrednio do base lub wdrożyć je na własną rękę. Możesz zobaczyć implementację za pomocą ILSpy/DotPeek/etc. lub poprzez debugowanie ich z Visual Studio.

Wreszcie, przy odrobinie pomocy Reflection, możesz ustawić konwerter typów dla Decimal, aby użyć swojego nowego niestandardowego!

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

bardzo efektownie – Johnv2020

+0

Dlaczego używałeś Double.Parse, a nie Decimal.Parse? –

+0

@IvanGritsenko: Tylko literówka;) – Carsten

2

Możesz spróbować przesłonić DefaultModelBinder. Daj mi znać, jeśli to nie zadziała, a ja usunę ten wpis. I faktycznie nie ułożyła aplikacji MVC i przetestować go, ale w oparciu o doświadczenie to powinno działać:

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

oparciu o komentarz z artykułu o modelu dziesiętny wiązania przez Phil Haack here, wierzę częścią odpowiedź na pytanie "dlaczego" jest taka, że ​​kultura w przeglądarkach jest skomplikowana i nie można zagwarantować, że kultura danej aplikacji będzie miała te same ustawienia kulturowe, które użytkownik/przeglądarka używa po przecinku. W każdym razie jest to znany "problem" i podobne pytania zostały zadane wcześniej z różnymi rozwiązaniami, na przykład: Accept comma and dot as decimal separator i How to set decimal separators in ASP.NET MVC controllers?.

2

Problem wydaje się być domyślnym Number Styles zastosowanym do wartości Decimal.Parse (ciąg).

Z dokumentacji MSDN

Pozostałe poszczególne flagi pola definiują elementy stylu, który może być, ale nie muszą być obecne w ciągu reprezentującego liczbę dziesiętną dla operacji parsowania do sukcesu.

Więc oznacza to, że zarówno D1 i D2 poniżej powodzeniem analizować

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

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

Jednak przy stosowaniu konwerter typu wydaje się, że to w zasadzie tylko pozwala pozwalają przestrzenie szkoleniowe pozwalają dziesiętny i pozwalają wiodący znak. Jako taki d3 wyrazić poniżej rzuci błąd wykonania

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