Problemi di deserializzazione con serialization assembly pregenerati (sgen.exe)

Supponiamo che la vostra applicazione si connetta a diversi web service di cui ha generato i proxy tramite la funzionalità di Visual Studio “Add web reference” o “Add service reference”. Come molti di voi sapranno, la comunicazione con i web service avviene utilizzando lo standard SOAP: in sostanza vengono scambiati dei messaggi XML che trasportano il metodo web con i relativi parametri, e naturalmente la risposta ricevuta dal web service. I messaggi vengono quindi generati tramite l’XMLSerializer, che ci permette di convertire i nostri oggetti in XML, e di ottendere gli oggetti titornati dal web service deserializzando la risposta SOAP proveniente dal web service.

Il processo di deserializzazione può essere piuttosto dispendioso, perché il codice di deserializzazione viene generato e compilato al volo (alcuni dettagli li trovate in questo mio post, partendo da un problema tipico del processo di deserializzazione). Tuttavia c’è la possibilità di migliorare le performance del vostro client generando il codice di de-serializzazione in anticipo (quindi non più a runtime) in modo da diminuire significativamente il tempo di start up della deserializzazione: l’assembly di deserializzazione può essere ottenuto tramite il tool sgen.exe.

Quello che normalmente si fa è lanciare il comando sgen.exe passandogli l’assembly che contiene le classi che devono essere serializzate/deserializzate con l’XMLSerializer (questo si capisce dagli attributi dell’XMLSerializer contenuti nell’implementazione).

image

Nell’immagine soprastante, il comando sgen.exe lanciato sul una console application che consuma un web service produce il relativo ConsoleApplication1.XmlSerializers.dll. L’assembly di serializzazione va quindi distribuito con l’applicazione: essa ne verificherà automaticamente la presenza prima di autogenerare il codice di serializzazione e compilarlo, con un notevole beneficio in termini di performance.

La produzione di un serialization assembly può essere anche abilitata a livello di progetto di Visual Studio, con l’opzione “Generate Serialization Assembly” (On/Off/Auto):

image

Ma cosa contiene l’assembly di serializzazione? E’ possibile lanciare sgen con il parametro /k[eep], in modo da non cancellare il codice autogenerato dopo la compilazione. Anche pe run’interfaccia molto semplice, il codice puà essere piuttosto complesso:

 #if _DYNAMIC_XMLSERIALIZER_COMPILATION
[assembly:System.Security.AllowPartiallyTrustedCallers()]
[assembly:System.Security.SecurityTransparent()]
#endif
[assembly:System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
[assembly:System.Xml.Serialization.XmlSerializerVersionAttribute(ParentAssemblyId=@"0cab5a27-2548-4f64-9f31-0e1bb6164518,", Version=@"2.0.0.0")]
namespace Microsoft.Xml.Serialization.GeneratedAssembly {

    public class XmlSerializationWriter1 : System.Xml.Serialization.XmlSerializationWriter {

        public void Write5_GetData(object[] p) {
            WriteStartDocument();
            TopLevelElement();
            int pLength = p.Length;
            WriteStartElement(@"GetData", @"http://tempuri.org/", null, false);
            if (pLength > 0) {
                if (pLength <= 1 || ((bool) p[1])) {
                    WriteElementStringRaw(@"value", @"http://tempuri.org/", System.Xml.XmlConvert.ToString((global::System.Int32)((global::System.Int32)p[0])));
                }
            }
            WriteEndElement();
        }

        public void Write6_GetDataInHeaders(object[] p) {
            WriteStartDocument();
            TopLevelElement();
            int pLength = p.Length;
        }

        public void Write7_GetDataUsingDataContract(object[] p) {
            WriteStartDocument();
            TopLevelElement();
            int pLength = p.Length;
            WriteStartElement(@"GetDataUsingDataContract", @"http://tempuri.org/", null, false);
            if (pLength > 0) {
                Write4_CompositeType(@"composite", @"http://tempuri.org/", ((global::ConsoleApplication1.com.microsoft.corp.europe.andreal3.CompositeType)p[0]), true, false);
            }
            WriteEndElement();
        }

Come si può notare, il codice di deserializzazione usa il target namespace (http://tempuri.org/ nell’esempio) al fine di deserializzare correttamente i tipi.

Ma cosa succede se abbiamo più di un web service che espone lo stesso tipo, utilizzando lo stesso target namespace?

Se utilizziamo gli assembly di serializzazione temporanei, non c’è nessun problema: viene generato il codice relativo soltanto web reference utilizzato per la chiamata in corso.

Se utilizziamo l’assembly di serializzazione pre-generato (sgen.exe), potrebbero esserci dei problemi: tutti i tipi per tutte i web reference vengono tenuti in considerazione e il relativo codice di serializzazione viene inserito nell .XmlSerializers.dll assembly.

Quando riceviamo il tipo comune a più di un web service, che per il .NET runtime non è lo stesso tipo (infatti ha anche un C#/VB.NET namespace differente, da non confondere con il target namespace del web service, che è un URI), l’assembly di serializzazione può essere confuso dal fatto che i due tipi hanno nome a target namespace in comune, e scambiare un tipo per l’altro causando un InvalidCastException!

Soluzione: se siete anche gli sviluppatori dei web service, cercate sempre di non usare il namespace introdotto automaticamente con il progetto ASP.NET web service, ma modificatelo in modo che non ci siano conflitti quanto meno nel target namespace.

Se non siete gli sviluppatori del  web service, a causa di una cattiva pratica, sarete costretti a non usare gli assembly di serializzazione pregenerati con sgen.exe, rinunciando ai relativi benefici in termini di performance.