94 votes

Utilisation de StringWriter pour la sérialisation XML

Je suis actuellement à la recherche d'un moyen simple de sérialiser des objets (en C# 3).

J'ai cherché des exemples sur Google et j'ai trouvé quelque chose comme ça :

MemoryStream memoryStream = new MemoryStream ( );
XmlSerializer xs = new XmlSerializer ( typeof ( MyObject) );
XmlTextWriter xmlTextWriter = new XmlTextWriter ( memoryStream, Encoding.UTF8 );
xs.Serialize ( xmlTextWriter, myObject);
string result = Encoding.UTF8.GetString(memoryStream .ToArray());

Après avoir lu ceci question Je me suis demandé, pourquoi ne pas utiliser StringWriter ? Cela semble beaucoup plus facile.

XmlSerializer ser = new XmlSerializer(typeof(MyObject));
StringWriter writer = new StringWriter();
ser.Serialize(writer, myObject);
serializedValue = writer.ToString();

Un autre problème était que le premier exemple générait un XML que je ne pouvais pas simplement écrire dans une colonne XML de la base de données SQL Server 2005.

La première question est la suivante : y a-t-il une raison pour laquelle je ne devrais pas utiliser StringWriter pour sérialiser un objet lorsque j'en ai besoin sous forme de chaîne par la suite ? Je n'ai jamais trouvé de résultat utilisant StringWriter en cherchant sur Google.

La seconde est, bien sûr : Si vous ne devez pas le faire avec StringWriter (pour n'importe quelles raisons), quelle serait la bonne et correcte manière ?


Ajout :

Comme cela a déjà été mentionné dans les deux réponses, je vais approfondir le problème du XML vers la base de données.

Lors de l'écriture dans la base de données, j'ai obtenu l'exception suivante :

System.Data.SqlClient.SqlException : XML parsing : ligne 1, caractère 38, impossible de changer l'encodage

Pour la chaîne

<?xml version="1.0" encoding="utf-8"?><test/>

J'ai pris la chaîne créée à partir du XmlTextWriter et j'ai simplement mis comme xml là. Celui-ci n'a pas fonctionné (ni avec l'insertion manuelle dans la BD).

Ensuite, j'ai essayé l'insertion manuelle (en écrivant simplement INSERT INTO ... ) avec encodage="utf-16", ce qui a également échoué. La suppression de l'encodage a alors fonctionné. Après ce résultat, je suis revenu au code StringWriter et voilà, ça a marché.

Problème : je ne comprends pas vraiment pourquoi.

à Christian Hayter : Avec ces tests, je ne suis pas sûr que je doive utiliser utf-16 pour écrire dans la BD. Est-ce que le fait de mettre l'encodage en UTF-16 (dans la balise xml) ne fonctionnerait pas alors ?

210voto

Jon Skeet Points 692016

Un problème avec StringWriter est que par défaut il ne vous permet pas de définir l'encodage qu'il annonce. - Vous pouvez donc vous retrouver avec un document XML annonçant son encodage en UTF-16, ce qui signifie que vous devez l'encoder en UTF-16 si vous l'écrivez dans un fichier. J'ai un petit cours pour vous aider dans ce domaine :

public sealed class StringWriterWithEncoding : StringWriter
{
    public override Encoding Encoding { get; }

    public StringWriterWithEncoding (Encoding encoding)
    {
        Encoding = encoding;
    }    
}

Ou si vous n'avez besoin que d'UTF-8 (ce qui est tout ce dont j'ai souvent besoin) :

public sealed class Utf8StringWriter : StringWriter
{
    public override Encoding Encoding => Encoding.UTF8;
}

Quant à la raison pour laquelle vous n'avez pas pu enregistrer votre XML dans la base de données, vous devez nous donner plus de détails sur ce qui s'est passé lorsque vous avez essayé, si vous voulez que nous soyons en mesure de diagnostiquer/réparer le problème.

126voto

Christian Hayter Points 17999

Lors de la sérialisation d'un document XML en une chaîne .NET, l'encodage doit être défini sur UTF-16. Les chaînes sont stockées en UTF-16 en interne, c'est donc le seul encodage qui ait un sens. Si vous souhaitez stocker des données dans un autre encodage, vous pouvez utiliser un tableau d'octets.

Le serveur SQL fonctionne selon un principe similaire ; toute chaîne de caractères passée dans un fichier xml doit être codée en UTF-16. Le serveur SQL rejettera toute chaîne dont la déclaration XML ne spécifie pas UTF-16. Si la déclaration XML n'est pas présente, la norme XML exige que la valeur par défaut soit UTF-8, et le serveur SQL rejettera également cette chaîne.

En gardant cela à l'esprit, voici quelques méthodes utilitaires pour effectuer la conversion.

public static string Serialize<T>(T value) {

    if(value == null) {
        return null;
    }

    XmlSerializer serializer = new XmlSerializer(typeof(T));

    XmlWriterSettings settings = new XmlWriterSettings()
    {
        Encoding = new UnicodeEncoding(false, false), // no BOM in a .NET string
        Indent = false,
        OmitXmlDeclaration = false
    };

    using(StringWriter textWriter = new StringWriter()) {
        using(XmlWriter xmlWriter = XmlWriter.Create(textWriter, settings)) {
            serializer.Serialize(xmlWriter, value);
        }
        return textWriter.ToString();
    }
}

public static T Deserialize<T>(string xml) {

    if(string.IsNullOrEmpty(xml)) {
        return default(T);
    }

    XmlSerializer serializer = new XmlSerializer(typeof(T));

    XmlReaderSettings settings = new XmlReaderSettings();
    // No settings need modifying here

    using(StringReader textReader = new StringReader(xml)) {
        using(XmlReader xmlReader = XmlReader.Create(textReader, settings)) {
            return (T) serializer.Deserialize(xmlReader);
        }
    }
}

20voto

John Saunders Points 118808

Tout d'abord, méfiez-vous des anciens exemples. Vous en avez trouvé un qui utilise XmlTextWriter qui est obsolète depuis .NET 2.0. XmlWriter.Create doit être utilisé à la place.

Voici un exemple de sérialisation d'un objet dans une colonne XML :

public void SerializeToXmlColumn(object obj)
{
    using (var outputStream = new MemoryStream())
    {
        using (var writer = XmlWriter.Create(outputStream))
        {
            var serializer = new XmlSerializer(obj.GetType());
            serializer.Serialize(writer, obj);
        }

        outputStream.Position = 0;
        using (var conn = new SqlConnection(Settings.Default.ConnectionString))
        {
            conn.Open();

            const string INSERT_COMMAND = @"INSERT INTO XmlStore (Data) VALUES (@Data)";
            using (var cmd = new SqlCommand(INSERT_COMMAND, conn))
            {
                using (var reader = XmlReader.Create(outputStream))
                {
                    var xml = new SqlXml(reader);

                    cmd.Parameters.Clear();
                    cmd.Parameters.AddWithValue("@Data", xml);
                    cmd.ExecuteNonQuery();
                }
            }
        }
    }
}

1voto

public static T DeserializeFromXml<T>(string xml)
{
    T result;
    XmlSerializerFactory serializerFactory = new XmlSerializerFactory();
    XmlSerializer serializer =serializerFactory.CreateSerializer(typeof(T));

    using (StringReader sr3 = new StringReader(xml))
    {
        XmlReaderSettings settings = new XmlReaderSettings()
        {
            CheckCharacters = false // default value is true;
        };

        using (XmlReader xr3 = XmlTextReader.Create(sr3, settings))
        {
            result = (T)serializer.Deserialize(xr3);
        }
    }

    return result;
}

-1voto

DLG Points 1

Cela a peut-être été abordé ailleurs, mais il suffit de changer la ligne d'encodage de la source XML en 'utf-16' pour que le XML soit inséré dans un type de données 'xml' du serveur SQL.

using (DataSetTableAdapters.SQSTableAdapter tbl_SQS = new DataSetTableAdapters.SQSTableAdapter())
{
    try
    {
        bodyXML = @"<?xml version="1.0" encoding="UTF-8" standalone="yes"?><test></test>";
        bodyXMLutf16 = bodyXML.Replace("UTF-8", "UTF-16");
        tbl_SQS.Insert(messageID, receiptHandle, md5OfBody, bodyXMLutf16, sourceType);
    }
    catch (System.Data.SqlClient.SqlException ex)
    {
        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

Le résultat est que tout le texte XML est inséré dans le champ de type de données 'xml' mais que la ligne 'header' est supprimée. Ce que vous voyez dans l'enregistrement résultant est juste

<test></test>

L'utilisation de la méthode de sérialisation décrite dans l'entrée "Answered" permet d'inclure l'en-tête d'origine dans le champ cible, mais le résultat est que le texte XML restant est enfermé dans un fichier XML <string></string> étiquette.

L'adaptateur de table dans le code est une classe construite automatiquement à l'aide de l'assistant "Add New Data Source :" de Visual Studio 2013. Les cinq paramètres de la méthode d'insertion correspondent aux champs d'une table SQL Server.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X