2015-08-26 3 views
16

Я пытаюсь понять, почему framework отказывается привязывать значение «1,234.00» к десятичной. Что может быть причиной этого?ASP.NET MVC обязательное десятичное значение

Значения типа «123.00» или «123.0000» успешно связываются.

У меня есть настройки моей культуры конфигурации в 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; 
    } 

Французская культура устанавливаются как культура по умолчанию в Web.Config

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

Я нырнул источники системы .Web.Mvc.dll класс ValueProviderResult. Он использует System.ComponentModel.DecimalConverter.

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

Здесь сообщение «1,234.0000 является недопустимым значением для десятичного числа». происходит от.

Я попытался запустить следующий код в моей игровой площадке:

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 кидает то же исключение. Decimal.Parse правильно разбирает одну и ту же строку.

ответ

10

Проблема заключается в том, что DecimalConverter.ConvertFrom не поддерживает AllowThousands флаг NumberStyles перечисления при вызове Number.Parse. Хорошей новостью является то, что существует способ «научить» это делать!

Decimal.Parse внутренне называет Number.Parse со стилем номер установлен в Number, для которого AllowThousands флаг устанавливается истина.

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

Когда вы получаете преобразователь типа из дескриптора, вы на самом деле получить экземпляр DecimalConverter. Метод ConvertFrom является своего рода общим и большим, поэтому я просто цитирую соответствующие части для текущего сценария здесь. Недостающие части реализуют поддержку шестнадцатеричных строк и обработку исключений.

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 также перезаписывает FromString реализации и там проблема возникает:

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

С стиле номера установлен в Float, то AllowThousands флаг установлен в ложь! Однако вы можете написать собственный конвертер с несколькими строками кода, который исправляет эту проблему.

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

Обратите внимание, что код похож на оригинальные реализации. Если вам нужно «неупомянутое» вещество передать его непосредственно base или реализовать его самостоятельно. Вы можете просмотреть реализацию, используя ILSpy/DotPeek/etc. или путем отладки в них из Visual Studio.

И наконец, с небольшой помощью Reflection вы можете установить преобразователь типа для Decimal, чтобы использовать новый пользовательский!

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

очень впечатляет – Johnv2020

+0

Почему вы использовали Double.Parse, а не Decimal.Parse? –

+0

@IvanGritsenko: Просто опечатка;) – Carsten

2

Вы можете попробовать переопределить DefaultModelBinder. Дайте мне знать, если это не сработает, и я удалю этот пост. Я на самом деле не соединили приложение MVC и протестировать его, но, основываясь на опыте это должно работать:

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

Основываясь на комментарий из статьи о десятичной модели связывания Phil Хаака here, я считаю частью ответ на вопрос «почему» заключается в том, что культура в браузерах является сложной, и вы не можете гарантировать, что культура вашего приложения будет та же самая настройка культуры, используемая пользователем/браузером для десятичных знаков. В любом случае это известная «проблема», и подобные вопросы были заданы ранее с использованием множества предлагаемых решений в дополнение к следующим: Accept comma and dot as decimal separator и How to set decimal separators in ASP.NET MVC controllers?, например.

2

Проблема здесь представляется по умолчанию Number Styles, применяемой к Decimal.Parse (string).

Из MSDN документации

Остальные отдельные флаги поля определяют элементы стиля, которые могут быть, но не должны быть, присутствует в строковое представление десятичного числа для операции синтаксического анализа, чтобы добиться успеха.

Таким образом, это означает, что и d1 и d2 ниже успешно разобрать

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

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

Однако при применении типа конвертора кажется, что это в основном только позволяет разрешить учебные помещения, позволяют десятичную точку и позволяют ведущим знак. В качестве такого d3 выразить ниже выбросит ошибку времени выполнения

   var d3 = Decimal.Parse("1,232.000", NumberStyles.AllowLeadingSign | NumberStyles.AllowLeadingWhite | 
             NumberStyles.AllowTrailingWhite | NumberStyles.AllowDecimalPoint); 
Смежные вопросы