Exceptions possibles en utilisant XmlSerializer

Quand on utilise la classe System.Xml.Serialization.XmlSerializer, outre les erreurs classiques de sérialisation/désérialisation il peut survenir des exceptions sans qu’on est fait de changement particulier dans le code. On ne comprends pas toujours facilement l’origine des ces exceptions car le code ne semble pas avoir changé et qu’il a toujours bien fonctionné auparavant.
Ces exceptions sont de 2 natures:

  1. System.IO.FileNotFoundException avec un message du type: Assembly loading failure [Nom de l'exécutable].XmlSerializers
  2. System.InvalidOperationException à cause de problèmes de sérialisation.

Ces 2 erreurs se produisent, le plus souvent, sans vraiment modifier le code mais en changeant l’environnement d’exécution de l’assembly:

  • En mettant à jour le framework .NET de la machine sur laquelle l’assembly sera exécutée.
  • En déployant l’exécutable sur une machine avec un environnement compatible mais pas tout à fait similaire à celui de la machine où l’exécutable a été développé.

Avant de rentrer dans le détail, il faut rappeler que le framework .NET 4.5 n’est pas installé à coté du framework .NET 4.0. A l’installation des frameworks .NET 4.5 et suivants, les assemblies .NET déjà présentes sont remplacées. Les applications compilées avec le framework 4.0 sont toujours compatibles et peuvent être exécutées avec le framework 4.5 parce que les nouvelles assemblies 4.5 assurent une compatibilité des fonctionnalités avec les versions précédentes. Toutefois il peut se produire certains comportements inattendus notamment avec la classe System.Xml.Serialization.XmlSerializer.

Pour avoir plus de détails sur le remplacement des assemblies du framework à l’installation des versions 4.5 et suivantes: Remplacement du Framework 4.0 par la version 4.5.

Pour avoir plus de détails sur les problèmes de compatibilités concernant la classe XmlSerializer: Compatibilité d’applications dans le .NET Framework 4.5 sur MSDN.

System.IO.FileNotFoundException avec un message “Assembly loading failure”

Cette erreur survient lorsqu’on utilise un des constructeurs:

  • XmlSerializer.XmlSerializer(Type)
  • XmlSerializer.XmlSerializer(Type, string)

A l’exécution de ces constructeurs, XmlSerializer essaie de charger une assembly contenant du code pour sérialiser et désérialiser des classes. Les comportements sont différents entre le framework .NET 4.0 et 4.5:

  • Quand le framework 4.0 est installé: le code et l’assembly de sérialisation sont générés à la compilation.
  • Quand le framework 4.5 (ou supérieur) est installé: par défaut, le code et l’assembly de sérialisation sont générés à l’exécution par le CLR.

A partir du framework 4.5, l’assembly de sérialisation est générée à l’exécution de façon à réduire la dépendance de l’exécutable avec le compilateur C#. Avant de générer une assembly, la classe XmlSerializer essaie de charger une assembly de sérialisation. Si l’assembly n’existe pas, une exception est lancée toutefois elle est “catchée” par le CLR directement. Durant une exécution sans debug, cette exception ne sera pas visible. Toutefois si on débuggue, Visual Studio va “catcher” l’exception.

Désactiver les exceptions dans Visual Studio

Pour désactiver le traitement de cette exception par Visual Studio, aller dans:

  1. Le menu “Debug”
  2. Exceptions
  3. Managed Debugging Assistances
  4. Décocher “BindingFailure”

L’exception n’est visible qu’en mode Debug et si l’option dans Visual Studio est activée. Quand le CLR “catche” l’exception, il génère l’assembly dans le répertoire temporaire pour que la classe XmlSerializer puisse sérialiser ou désérialiser des objets.

On peut facilement tester ce comportement en exécutant le code suivant:

[Serializable]
public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Person()
    {}
	
    public Person(string firstName, string lastName)
    {
        this.FirstName = firstName;
        this.LastName = lastName;
    }
}

static void Main(string[] args)
{
    string fileName = "person.xml";
    Person objectToSerialize = new Person("Charles", "Lindbergh");

    XmlSerializer serializer = new XmlSerializer(typeof(Person));
    using (FileStream s = new FileStream(fileName, FileMode.Create))
    {
        serialiser.Serialize(s, objectToSerialize);
    }
}
Les autres constructeurs de XmlSerializer peuvent dégrader les performances

C’est possible d’éviter l’exception en utilisant d’autres constructeurs de XmlSerializer mais ils peuvent entraîner des fuites mémoire et dégrader les performances car une nouvelle assembly est générée à chaque utilisation du constructeur. Il ne faut pas les utiliser des façons répétitives dans une application mais de façon très ponctuelle.

Pour avoir plus de détails sur ces constructeurs en allant dans “Dynamically Generated Assemblies” dans la page suivante: la classe XmlSerializer sur MSDN.

Générer les assemblies de sérialisation à la compilation

Une autre solution consiste à générer les assemblies de sérialisation à la compilation avec les étapes suivantes:

  1. Aller dans les propriétés du projet => Onglet “Build” => Dans la partie “Generate serialization assembly” => Sélectionner “On”.
  2. Editer le fichier .csproj du projet avec un éditeur de type Notepad et ajouter le nœud SGenUseProxyTypes:
    <?xml version="1.0" encoding="utf-8"?>
    <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
        <!-- ... -->
        <GenerateSerializationAssemblies>On</GenerateSerializationAssemblies>
        <SGenUseProxyTypes>false</SGenUseProxyTypes>
      </PropertyGroup>
    </Project>
    

    Ne pas oublier d’ajouter le nœud SGenUseProxyTypes pour toutes les plateformes cible (i.e. target platform).

  3. Compiler l’assembly en “AnyCPU” (la génération ne fonctionnera pas si la compilation se fait avec les plateformes cible “x86” ou “x64”).

System.InvalidOperationException à cause de problèmes de sérialisation

Par exemple, une exception du type suivant peut survenir:

System.InvalidOperationException: There was an error generating the XML document.
--> System.InvalidProgramException: Common Language Runtime detected an invalid program 
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriteFrmFrm.Write53_FrmTable(String n, String ns, FrmTable o, Boolean isNullable, Boolean needType)
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriteFrmFrm.Write55_FrmFrm(String n, String ns, FrmTable o, Boolean isNullable, Boolean needType)
at Microsoft.Xml.Serialization.GeneratedAssembly.XmlSerializationWriteFrmFrm.Write56_frm(Object o)
--- End of inner exception stack trace ---
at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o, XmlSerializerNamespaces namespaces, String encodingStyle, String id)
at System.Xml.Serialization.XmlSerializer.Serialize(XmlWriter xmlWriter, Object o)

Ce problème est dû à un comportement différent de la classe XmlSerializer durant la sérialisation ou la désérialisation entre les différentes versions de framework. Comme indiqué plus haut, l’implémentation de cette classe a été modifiée entre ces 2 versions car la génération n’est plus faite à la compilation mais durant l’exécution. Elle peut être résolue en ajoutant le paramètre suivant dans le fichier de configuration de l’application:

<configuration>
  <system.xml.serialization>
    <xmlSerializer useLegacySerializerGeneration="true" />
  </system.xml.serialization>
</configuration>

Ce paramètre est à utiliser si on souhaite déployer une application sans la recompiler sur une machine sur laquelle est installé le framework 4.5 (ou supérieur) et que l’application a été développé avec le framework 4.0.

Références:

Explications sur la génération d’assembly de sérialisation:

Leave a Reply