2015-08-26 19 views
16

Tôi đang cố gắng tìm ra lý do tại sao khuôn khổ từ chối liên kết giá trị "1,234.00" với số thập phân. Điều gì có thể là lý do cho nó?Giá trị thập phân ràng buộc ASP.NET MVC

Giá trị như "123.00" hoặc "123.0000" liên kết thành công.

tôi có các thiết lập cấu hình văn hóa của tôi trong Global.asax mã sau

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

văn hóa Pháp được thiết lập như văn hóa mặc định trong Web.Config

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

Tôi đã lặn vào nguồn của hệ thống Lớp ValueProviderResult của .Web.Mvc.dll. Nó đang sử dụng System.ComponentModel.DecimalConverter.

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

Đây là nơi thông báo "1,234.0000 không phải là giá trị hợp lệ cho thập phân". đến từ.

Tôi đã cố gắng để chạy đoạn mã sau vào sân chơi của tôi:

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 ném cùng một ngoại lệ. Decimal.Parse phân tích chính xác cùng một chuỗi.

Trả lời

10

Vấn đề là, rằng DecimalConverter.ConvertFrom không hỗ trợ AllowThousands lá cờ của NumberStyles liệt kê khi nó gọi Number.Parse. Tin tốt là, có một cách để "dạy" nó để làm như vậy!

Decimal.Parse gọi nội bộ Number.Parse với kiểu số được đặt thành Number, trong đó cờ AllowThousands được đặt thành true.

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

Khi bạn nhận được bộ chuyển đổi loại từ bộ mô tả, bạn thực sự có được một phiên bản DecimalConverter. Các phương pháp ConvertFrom là một kinda chung và lớn, vì vậy tôi chỉ trích dẫn các phần có liên quan cho kịch bản hiện tại ở đây. Các bộ phận còn thiếu đang thực hiện hỗ trợ cho chuỗi hex và xử lý ngoại lệ.

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 cũng ghi đè thi FromString và có những vấn đề đặt ra:

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

Với số lượng phong cách thiết lập để Float, cờ AllowThousands được thiết lập để sai! Tuy nhiên, bạn có thể viết trình chuyển đổi tùy chỉnh với một vài dòng mã để khắc phục sự cố này.

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

Lưu ý rằng mã trông tương tự như việc thực hiện ban đầu. Nếu bạn cần công cụ "không được kiểm duyệt", hãy ủy quyền trực tiếp cho số base hoặc tự mình thực hiện nó. Bạn có thể xem việc thực hiện bằng cách sử dụng ILSpy/DotPeek/etc. hoặc bằng cách gỡ lỗi vào chúng từ Visual Studio.

Cuối cùng, với một chút trợ giúp từ Reflection, bạn có thể đặt bộ chuyển đổi loại cho Decimal để sử dụng tùy chỉnh mới của bạn!

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

rất ấn tượng – Johnv2020

+0

Tại sao bạn sử dụng Double.Parse và không Decimal.Parse? –

+0

@IvanGritsenko: Chỉ cần một lỗi đánh máy;) – Carsten

2

Bạn có thể thử ghi đè DefaultModelBinder. Hãy cho tôi biết nếu điều này không hoạt động và tôi sẽ xóa bài đăng này. Tôi không thực sự đặt cùng một ứng dụng MVC và thử nghiệm nó, nhưng dựa trên kinh nghiệm này nên làm việc:

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

Dựa trên một bình luận từ một bài viết về mô hình thập phân ràng buộc bởi Phil Haack here, tôi tin rằng một phần của câu trả lời cho "lý do" là văn hóa trong trình duyệt phức tạp và bạn không thể đảm bảo rằng nền văn hóa của ứng dụng của bạn sẽ là cùng một cài đặt văn hóa được người dùng/trình duyệt sử dụng cho số thập phân. Trong mọi trường hợp, nó là một "vấn đề" đã biết và các câu hỏi tương tự đã được hỏi trước đây với một loạt các giải pháp được cung cấp, ngoài việc: Accept comma and dot as decimal separatorHow to set decimal separators in ASP.NET MVC controllers? chẳng hạn.

2

Vấn đề ở đây có vẻ là mặc định Number Styles được áp dụng cho Thập phân.Parse (chuỗi).

Từ tài liệu MSDN

Cờ lĩnh vực cá nhân còn lại xác định các yếu tố phong cách mà bạn có thể, nhưng không phải như vậy, hiện diện trong chuỗi đại diện của một số thập phân cho các hoạt động phân tích cú pháp để thành công.

Vì vậy, điều này có nghĩa rằng cả d1 và d2 dưới đây phân tích thành công

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

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

Tuy nhiên khi áp dụng các chuyển đổi loại nó xuất hiện rằng đây thực chất chỉ cho phép cho phép không gian đào tạo, cho phép dấu thập phân và cho phép đăng nhập hàng đầu. Khi d3 như thể hiện dưới đây sẽ ném một lỗi runtime

   var d3 = Decimal.Parse("1,232.000", NumberStyles.AllowLeadingSign | NumberStyles.AllowLeadingWhite | 
             NumberStyles.AllowTrailingWhite | NumberStyles.AllowDecimalPoint); 
Các vấn đề liên quan