9

C'è un problema con l'utilizzo di TypeNameAssemblyFormat con PCL? Non ho problemi ad usare altre impostazioni con Newtonsoft.Json tranne quando utilizzo questa impostazione di serializzazione.MissingMethodException con Newtonsoft.Json quando si utilizza TypeNameAssemblyFormat con PCL

Ecco il mio codice JSON-related:

var settings = new JsonSerializerSettings() 
     { 
      TypeNameHandling = TypeNameHandling.Objects, 
      Formatting = Formatting.Indented, 
      TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Full 
     }; 

var json = JsonConvert.SerializeObject(obj, settings); 

var jsonBytes = Encoding.UTF8.GetBytes(json); 

return jsonBytes; 

Quando faccio la chiamata all'interno della stessa libreria in cui è dichiarata, va bene. Tuttavia, quando effettuo la chiamata da un altro PCL che chiama il codice precedente, ottengo l'eccezione del metodo mancante. Ciò si verifica solo quando utilizzo l'impostazione TypeNameAssemblyFormat (ad esempio, se non dovessi utilizzare quell'impostazione, non scriverei questo post;).

Sto usando il profilo PCL 7.

eccezione (non volevo bla l'intera traccia dello stack, ma posso se qualcuno pensa che avrebbe aiutato):

"System.MissingMethodException: Method not found: 'Void Newtonsoft.Json.JsonSerializerSettings.set_TypeNameAssemblyFormat(System.Runtime.Serialization.Formatters.FormatterAssemblyStyle)' 
+0

nel test, quando si arriva sopra eccezione, si 'Newtonsoft.Json' riferimento ** ** solo dalla prima il PCL in cui è stato utilizzato, oppure ci sono altri assembly non PCL (o potrebbero essere anche assembly PCL con profilo diverso) che fanno riferimento anche a 'Newtonsoft.Json'. In altre parole, quando questo codice non funziona, quali tutti gli assembly fanno riferimento a 'Newtonsoft.Json' e quali sono i loro profili? D'altro canto, quando funziona, 'Newtonsoft.Json' fa riferimento solo a un assembly, il PCL che contiene il codice precedente? –

+0

Newtonsoft fa riferimento a PCL1, PCL2, Test1 e Test2 (.net 4.5). Passa su Test1 che fa riferimento a PCL1, non riesce su Test2 che fa riferimento a PCL1, PCL2. Non penso che il progetto verrebbe compilato se ognuno di questi non avesse fatto riferimento a Newtonsoft. – ibgib

+0

Scratch il commento sopra: Newtonsoft è referenziato da PCL1, PCL2, Test1 e Test2 (.net 4.5). Errore su Test1 e Test2. Ho fatto diversi giorni per risolvere questo problema e non ricordo a che punto ho posto questa domanda SO, e non ricordo le circostanze specifiche di successo/fallimento. Dopo aver ripeterlo alcuni stamattina, sia Test1 che Test2 falliscono. – ibgib

risposta

5

Anche se ci isn 't abbastanza informazioni in questione per confermare la causa principale con il 100% di fiducia .. Personalmente, dopo qualche sperimentazione sono positivo che l'unica plausibile spiegazione è la seguente -

Insomma - Nella prova che non riesce, la versione corretta (portatile) di Newtonsoft.Json.dll è non sempre caricato.

In lungo - Sono in corso due test.

  1. pass - presumo un exe, che chiama in PCL1, che chiama in portatile versione di NewtonSoft.Json.dll

  2. non riesce - presumo un'altra exe, che chiama in PCL2, che chiama PCL1, che chiama in una versione (quale versione?) di NewtonSoft.Json.dll

La questione è non che PCL2 chiama in PCL1 e in qualche modo riesce, a causa di indiretta chiamata viene trasformato in NewtonSoft.Json.dll. Piuttosto, il problema è, come sto cercando di mettere in evidenza sopra, la seconda prova sembra essere messa a punto in un modo, che il torto/non portabile versione di NewtonSoft.Json.dll è disponibile per PCL1 di consumare.

Nello scenario che non riesce, immaginare che l'exe o qualsiasi altro gruppo non portatile di tale domanda richiede anche una dipendenza (non portatile) versione di NewtonSoft.Json.dll.In tal caso, nella cartella di output dell'applicazione/exe, ci sarà solo una versione di NewtonSoft.Json.dll, e se è il non portabile uno, allora fallirà con l'eccezione di cui sopra ..

Ulteriori spiegazioni - Perché?

Il tipo System.Runtime.Serialization.Formatters.FormatterAssemblyStyle è in genere definito in mscorlib.dll. Tuttavia, questo tipo non è disponibile per le librerie di classi portatili da usare (non so su tutti i profili, ma ci sono alcuni profili di sicuro, che non hanno questo tipo disponibile). Quindi la versione portatile di NewtonSoft.Json.dll, lo dichiara autonomamente, nel proprio assieme.

Verificare la versione decompilata di portatileNewtonSoft.Json.dll nel decompilatore preferito. Nota numero riga di seguito .. il seguente snippet è da NewtonSoft.Json.dll.

// Decompiled with JetBrains decompiler 
// Type: System.Runtime.Serialization.Formatters.FormatterAssemblyStyle 
// Assembly: Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed 
// Assembly location: C:\Users\.....\packages\Newtonsoft.Json.6.0.7\lib\portable-net40+sl5+wp80+win8+wpa81\Newtonsoft.Json.dll 
// Compiler-generated code is shown 

namespace System.Runtime.Serialization.Formatters 
{ 
    /// <summary> 
    /// Indicates the method that will be used during deserialization for locating and loading assemblies. 
    /// 
    /// </summary> 
    public enum FormatterAssemblyStyle 
    { 
    Simple, 
    Full, 
    } 
} 

Ora, quando si compila il codice in un PCL, che fa riferimento TypeNameAssemblyFormat proprietà, presumeable di tipo System.Runtime.Serialization.Formatters.FormatterAssemblyStyledefinito inNewtonsoft.Json.dll, seguendo IL viene generata (decompilato con Ildasm) -

IL_0017: ldloc.0 
    IL_0018: ldc.i4.1 
    IL_0019: callvirt instance void [Newtonsoft.Json]Newtonsoft.Json.JsonSerializerSettings::set_TypeNameAssemblyFormat(valuetype [Newtonsoft.Json]System.Runtime.Serialization.Formatters.FormatterAssemblyStyle) 

Nota come riferimento al tipo è qualificato con il nome assembly [Newtonsoft.Json] (scorrere a destra -----> per vederlo sul parametro FormatterAssemblyStyle essere passato).

Ora, se questa versione portatile di Newtonsoft.Json.dll, viene sostituita con versione non portatile (poiché altre parti del progetto fanno riferimento a una versione non portatile), in fase di runtime, CLR non sarà in grado di trovare il metodo che corrisponde alla firma precedente (come visto in IL sopra) .. e quindi non riescono con System.MissingMethodException.

Sfortunatamente, l'eccezione per sé non dà sufficienti informazioni sulla firma completa e precisa di metodo che è alla ricerca di, compresi i nomi di assemblaggio .. Il nome del tipo da solo ingannevolmente assomiglia a qualcosa che esisterebbe in una delle DLL di sistema (mscorlib.dll in questo caso) .. e non la versione portatile di Newtonsoft.json.dll.

+0

Ho rinunciato a questo per un po 'di tempo fa. Lo guarderò di nuovo oggi e cercherò di fornire informazioni basate sulla tua risposta. – ibgib

+0

Dalla memoria, tuttavia, * posso * dirvi che, nel caso di successo, è il progetto di test dell'unità .Tests in esecuzione e che è destinato a .Net 4.5. In uno dei casi di fallimento, anche il progetto di test dell'unità .Tests è rivolto a .Net 4.5. L'unica differenza è che il secondo test di progetto unitario fa riferimento a due PCL, entrambi con lo stesso profilo che quando caricato con Xamarin Studio mostrano il profilo 7 (con questi progetti, sviluppo in VS.Tuttavia non ha un indicatore di profilo. In VS vedo .Net 4.5, Win 8, Xamarin.Android/iOS/iOS (Classic) controllato nelle proprietà del progetto in entrambi i PCL). – ibgib

+0

Dopo aver ripeterlo, risulta che la mia memoria mi ha deluso. Fallisce in entrambi i progetti di test dell'unità Test1 e Test2. (A mia difesa, questo è stato chiesto un mese e mezzo fa!) C'è davvero solo una versione della dll di Newtonsoft nella directory di output. Dopo aver esaminato la tua risposta, sarei d'accordo con la tua valutazione della situazione. Ma il problema rimane ancora. C'è un modo per farlo funzionare? A proposito, andrò avanti e contrassegnerò la risposta come la risposta in quanto identifica chiaramente la situazione. Per chiunque sia interessato, sto usando ServiceStack.Text ora e ha funzionato finora. – ibgib

3

Ok, ho finito per completare la risposta della mia stessa domanda a causa della chiarezza dell'inquadramento del problema da parte di Vikas. E la risoluzione è l'approccio PCL standard con questo tipo di problema: Crea interfaccia, configura contenitore, usa DI.

In questo caso, nel mio PCL, ho creato un'interfaccia INewtonsoftJsonSettingsProvider che ha le due impostazioni che uso come proprietà come segue:

public interface INewtonsoftJsonSettingsProvider 
{ 
    JsonSerializerSettings Default { get; set; } 
    JsonSerializerSettings Concrete { get; set; } 
} 

Poi, nel mio PCL, creo una concreta implementazione di questa classe come segue:

public class NewtonsoftJsonSettingsProvider : Interfaces.INewtonsoftJsonSettingsProvider 
{ 
    public JsonSerializerSettings Default { get; set; } 
    public JsonSerializerSettings Concrete { get; set; } 
} 

Nota: ho potuto facilmente saltare l'interfaccia e basta usare questa classe di supporto da sola, ma io piace usare le interfacce quando si tratta del contenitore.

Poi, nel mio PCL dove esiste il serializzatore Newtonsoft, che consumano le impostazioni dal contenitore invece di creare direttamente quelli all'interno dei metodi di serializzazione. Vado avanti e comprendono che il codice anche qui (I Sottratto serializzazione a un'interfaccia a causa di questo problema, in modo da poter scambiare le implementazioni):

public class NewtonsoftJsonSerializer : ICustomSerializer 
{ 
    public static void RegisterAsSerializerInContainer() 
    { 
     var key = Resx.DIKeys.DefaultSerializer; 
     var typeContract = typeof(ICustomSerializer); 

     if (DI.Ton.KeyExists(key)) 
     { 
      var instance = DI.Ton.Get(typeContract, key); 
      DI.Ton.RemoveInstance(key, instance, typeContract); 
     } 

     DI.Ton.AddImplementationType(typeof(ICustomSerializer), 
            typeof(NewtonsoftJsonSerializer), 
            isShared: true); 

     var serializer = new NewtonsoftJsonSerializer(); 
     DI.Ton.AddInstance<ICustomSerializer>(Resx.DIKeys.DefaultSerializer, serializer); 
    } 

    /// <summary> 
    /// This is used to overcome the problem of PCL vs non-PCL versions when using TypeNameAssemblyFormat. 
    /// see http://stackoverflow.com/questions/27080363/missingmethodexception-with-newtonsoft-json-when-using-typenameassemblyformat-wi 
    /// </summary> 
    /// <returns></returns> 
    private static INewtonsoftJsonSettingsProvider GetSettingsProvider() 
    { 
     var key = typeof(INewtonsoftJsonSettingsProvider).Name; 
     var settings = DI.Ton.GetInstance<INewtonsoftJsonSettingsProvider>(key); 
     return settings; 
    } 

    public byte[] SerializeToBytes(object obj) 
    { 
     var settings = GetSettingsProvider(); 

     var json = JsonConvert.SerializeObject(obj, settings.Default); 

     var jsonBytes = Encoding.UTF8.GetBytes(json); 

     return jsonBytes; 
    } 


    public T DeserializeFromBytes<T>(byte[] serializedBytes) 
    { 
     var json = Encoding.UTF8.GetString(serializedBytes, 0, serializedBytes.Length); 

     var settings = GetSettingsProvider(); 

     var obj = JsonConvert.DeserializeObject<T>(json, settings.Default); 

     return obj; 
    } 

    public byte[] SerializeToBytes_UseConcreteTypes(object obj) 
    { 
     var settings = GetSettingsProvider(); 

     var json = JsonConvert.SerializeObject(obj, settings.Concrete); 

     var jsonBytes = Encoding.UTF8.GetBytes(json); 

     return jsonBytes; 
    } 

    public T DeserializeFromBytes_UseConcreteTypes<T>(byte[] serializedBytes) 
    { 
     var json = Encoding.UTF8.GetString(serializedBytes, 0, serializedBytes.Length); 

     var settings = GetSettingsProvider(); 

     var obj = JsonConvert.DeserializeObject<T>(json, settings.Concrete); 

     return obj; 
    } 
} 

Poi, nel mio consumo non-PCL e non Xamarin (può funzionare in PCL, ma Xamarin ha problema - vedi sotto), configura il contenitore con il corretto System.Runtime.Serialization.Formatters.FormatterAssemblyStyle come spiegato nella risposta Vikas':

private static void UseNewtonsoft() 
{ 
    var defaultNewtonsoftSettings = new Newtonsoft.Json.JsonSerializerSettings() 
    { 
     Formatting = Newtonsoft.Json.Formatting.Indented 
    }; 
    var concreteNewtonsoftSettings = new Newtonsoft.Json.JsonSerializerSettings() 
    { 
     TypeNameHandling = TypeNameHandling.Objects, 
     Formatting = Formatting.Indented, 
     TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Full 
    }; 
    Serialization.NewtonsoftJsonSettingsProvider.CreateAndRegisterWithContainerIdem(defaultNewtonsoftSettings, concreteNewtonsoftSettings); 
    Serialization.NewtonsoftJsonSerializer.RegisterAsSerializerInContainer(); 
} 

Questo esegue senza problemi nei miei progetti di test .Net. Tuttavia, quando sono andato a utilizzarlo nel progetto Xamarin.Android, ho ricevuto un errore che indica che FormatterAssemblyStyle esiste sia in Newtonsoft che in MonoAndroid mscorlib. Dal momento che Xamarin Studio non sembra fare alias extern, ho usato la riflessione e dinamica come segue:

void UseNewtonsoft() 
{ 
    var defaultNewtonsoftSettings = new Newtonsoft.Json.JsonSerializerSettings() 
     { 
      Formatting = Newtonsoft.Json.Formatting.Indented 
     }; 
    //hack: FormatterAssemblyStyle exists in two dlls and extern alias doesn't work in xamarin studio 
    var assmNewtonsoft = System.Reflection.Assembly.GetAssembly(typeof(Newtonsoft.Json.ConstructorHandling)); 
    var enumType = assmNewtonsoft.GetType("System.Runtime.Serialization.Formatters.FormatterAssemblyStyle"); 
    dynamic enumInstance = Enum.Parse(enumType, "Full"); 
    var concreteNewtonsoftSettings = new Newtonsoft.Json.JsonSerializerSettings() 
     { 
      TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Objects, 
      Formatting = Newtonsoft.Json.Formatting.Indented, 
      //TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Full //set using dynamic 
     }; 
    dynamic dynSettings = concreteNewtonsoftSettings; 
    dynSettings.TypeNameAssemblyFormat = enumInstance; 
    commonGib.Serialization.NewtonsoftJsonSettingsProvider.CreateAndRegisterWithContainerIdem(defaultNewtonsoftSettings, concreteNewtonsoftSettings); 
    commonGib.Serialization.NewtonsoftJsonSerializer.RegisterAsSerializerInContainer(); 
} 
+0

Penso che la radice del problema sia un errore in Json.NET o NuGet o entrambi. Ti ho assegnato la taglia perché in realtà risolve il problema e stavo perdendo tempo per assegnare la generosità a qualcuno. Grazie per lo sforzo che hai effettuato. – craftworkgames

+0

@craftworkgames Non penso che questo sia un bug in Json.Net o Nuget .. Possiamo essere discusse in chat .. perché penso ci siano variazioni dell'essenza del problema che vedrai anche in altri posti .. per me, la migliore ** analogia ** è l'inferno della DLL .. cioè i conflitti di versione. Sono lieto che tu abbia assegnato la taglia ad ibgib perché DI sembra essere un'ottima soluzione al problema reale, che non ho potuto confermare con il 100% di confidenza. Se il tuo problema è in effetti leggermente diverso, spero che tu possa porre un'altra domanda con maggiori informazioni ... e vedremo se possiamo aiutarti. –

+0

@VikasGupta Sì, assolutamente metterò insieme una spiegazione di ciò che mi sono scoperto sul problema. Sfortunatamente non ci sono ancora riuscito. Ciò che ho fatto, però, è un promemoria per tornare ad esso, così non lo dimentico. – craftworkgames