2013-07-03 9 views
5

Viene generata una eccezione System.FormatExwn quando provo ad analizzare XML in un oggetto. Per quanto posso dire, è dovuto alla cultura utilizzata in System.Xml.Serialization.XmlSerializer.Deserialize, che si aspetta un punto come carattere decimale, ma l'xml contiene una virgola.La deserializzazione XML si arresta in modo anomalo sull'analisi decimale a causa della formattazione

L'oggetto appare come segue:

 

public sealed class Transaction 
{ 
    [XmlElement("transactionDate")] 
    public DateTime TransactionDate { get; set; } 

    [XmlElement("transactionAmount")] 
    public decimal Amount { get; set; } 

    [XmlElement("transactionDescription")] 
    public string Description { get; set; } 

    [XmlElement("transactionType")] 
    public int Type { get; set; } 

    public static Transaction FromXmlString(string xmlString) 
    { 
     var reader = new StringReader(xmlString); 
     var serializer = new XmlSerializer(typeof(Transaction)); 
     var instance = (Transaction) serializer.Deserialize(reader); 

     return instance; 
    } 
} 
 

Il xml:

 

<transaction> 
    <transactionDate> 2013-07-02 <transactionDate> 
    <transactionAmount>-459,00</transactionAmount> 
    <transactionDescription>description</transactionDescription> 
    <transactionType>1</transactionType> 
</transaction> 
 

Ho fatto funzionare con l'introduzione di una seconda proprietà che analizza il primo utilizzo la mia cultura:

 

namespace MyNamespace 
{ 
    [XmlRoot("transaction"), XmlType("Transaction")] 
    public sealed class Transaction 
    { 
     [XmlElement("transactionDate")] 
     public DateTime TransactionDate { get; set; } 

     [XmlElement("transactionAmount")] 
     public string Amount { get; set; } 

     public decimal AmountAsDecimal { 
      get 
      { 
       decimal value; 
       Decimal.TryParse(Amount, NumberStyles.Any, CultureInfo.CreateSpecificCulture("sv-SE"), out value); 
       return value; 
      } 
     } 

     [XmlElement("transactionDescription")] 
     public string Description { get; set; } 

     [XmlElement("transactionType")] 
     public int Type { get; set; } 

     public static Transaction FromXmlString(string xmlString) 
     { 
      var reader = new StringReader(xmlString); 
      var serializer = new XmlSerializer(typeof(Transaction)); 
      var instance = (Transaction) serializer.Deserialize(reader); 

      return instance; 
     } 
    } 
} 

 

che espone una proprietà extra t cappello non voglio lì.

Quindi la mia domanda è: c'è un altro modo per farlo, senza iterare su ogni elemento e analizzarlo/assegnarlo all'oggetto "manualmente"?

+1

Da [qui] (http://forums.asp.net/t/1365779.aspx) sembra che XmlSerializer stia utilizzando [questo schema W3C] (http://www.w3.org/TR/xmlschema -2 /) e si suppone che sia relativamente indipendente dalla cultura per evitare problemi di serializzazione/deserializzazione su macchine/culture. Vorrei capire che quello che stai facendo ora è probabilmente il modo migliore (magari adornare 'AmountAsDecimal' con' [XmlIgnore] 'solo così è un po 'più ovvio). Indipendentemente da ciò, fintanto che i vostri oggetti serializzati sono puramente oggetti per il trasferimento dei dati e astratti dalla vostra applicazione/logica di business, non dovrebbe farmi troppo male, spero. –

+0

Ma in seconda lettura, è possibile capovolgere quale proprietà analizza e quali serializza. Esporre 'Importo decimale pubblico' come valore che si otterrebbe/imposta normalmente nell'API _code_ ma che ha come '[XmlIgnore]'.Quindi disponi di una proprietà 'stringa seriale SerializedAmount' le cui implementazioni get/set formatteranno/analizzeranno la tua cultura specializzata. Almeno in questo modo l'API che usi per _write_ l'oggetto 'Transaction' non deve pensare a come formattare la stringa; scrive solo un valore 'decimale'. Se il modo in cui lo scrivi (ad esempio modifichi la proprietà 'SerializedAmount'), il tuo codice non interessa. –

risposta

5

Il serializzatore XML utilizza un formato standardizzato Numero e DateTime, lo standard è definito nelle specifiche del tipo di dati dello schema W3C http://www.w3.org/TR/xmlschema-2/.

Non aspettatevi che XmlSerializer presti attenzione al thread CultureInfo, esso utilizza intenzionalmente un formato standardizzato per garantire la possibilità di serializzare/deserializzare indipendentemente dalla lingua/dalla lingua.

+0

Anche così, l'XML restituito dalle mie query contiene doppi con virgole. – Softnux

+1

@Softnux, penso che tu abbia trovato una soluzione possibile. –

+1

+1. @Softnux "le mie query" come in "il tuo crea questo XML" o "query eseguite contro il servizio di terze parti"? Se ex consiglio vivamente di utilizzare il formato XML appropriato per numeri/valori datetime. Sarà molto più facile a lungo termine. –

2

Se si conosce la cultura in cui è stato generato l'XML, una soluzione semplice è quella di passare la cultura del thread corrente a quella cultura prima della deserializzazione.

System.Globalization.CultureInfo oCurrentCulture = null; 
    try 
    { 
     // Save the current culture 
     oCurrentCulture = System.Threading.Thread.CurrentThread.CurrentCulture; 
     System.Threading.Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("de-DE"); 

     // Do your work 
    } 
    finally 
    { 
        // Restore the saved culture 
     System.Threading.Thread.CurrentThread.CurrentCulture = oCurrentCulture; 
    } 
+0

Ho trovato la migliore pratica è la quantità minima di codice all'interno di un tentativo (o utilizzo). Dal momento che salvare la cultura non è necessario essere nel tentativo di salvare la cultura del thread prima che la mano sia appropriata – Steve

5

Che cosa si può fare, invece è di avere una proprietà che verrà utilizzato per serializzare/deserializzare il decimal.

See: Partially deserialize XML to Object

[XmlType("transaction")] 
public sealed class Transaction 
{ 
    [XmlElement("transactionDate")] 
    public DateTime TransactionDate { get; set; } 

    [XmlIgnore] 
    public decimal Amount { get; set; } 

    [XmlElement("transactionAmount")] 
    [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] 
    public string AmountSerialized 
    { 
     get 
     { 
      return Amount.ToString(CultureInfo.CreateSpecificCulture("sv-SE")); 
     } 
     set 
     { 
      decimal amount; 
      Decimal.TryParse(value, NumberStyles.Any, CultureInfo.CreateSpecificCulture("sv-SE"), out amount); 
      Amount = amount; 
     } 
    } 

    [XmlElement("transactionDescription")] 
    public string Description { get; set; } 

    [XmlElement("transactionType")] 
    public int Type { get; set; } 

    public static Transaction FromXmlString(string xmlString) 
    { 
     var reader = new StringReader(xmlString); 
     var serializer = new XmlSerializer(typeof(Transaction)); 
     var instance = (Transaction) serializer.Deserialize(reader); 

     return instance; 
    } 
} 

In questo modo è possibile ottenere/impostare la Amount senza bisogno di preoccuparsi di come è serializzato. Poiché si tratta di un DTO, è possibile creare un'altra classe senza lo AmountSerialized come oggetto dominio (e utilizzare qualcosa come AutoMapper per rendere la conversione indolore).

Usage:

var data = @"<transaction> 
       <transactionDate>2013-07-02</transactionDate> 
       <transactionAmount>-459,00</transactionAmount> 
       <transactionDescription>description</transactionDescription> 
       <transactionType>1</transactionType> 
      </transaction>"; 

var serializer = new XmlSerializer(typeof(Transaction)); 

using(var stream = new StringReader(data)) 
using(var reader = XmlReader.Create(stream)) 
{ 
    Console.Write(serializer.Deserialize(reader)); 
} 

Inoltre vi era un errore di battitura nel tag finale per transactionDate.