MissingMethodException with Newtonsoft.Json when u

2019-02-16 16:26发布

Is there a problem with using the TypeNameAssemblyFormat with PCLs? I have no problems using any other settings with Newtonsoft.Json except when I use this serialization setting.

Here is my Json-related code:

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;

When I make the call within the same library where it is declared, it is fine. However, when I make the call from a different PCL that calls the above code, I get the missing method exception. This only occurs when I use the TypeNameAssemblyFormat setting (i.e. if I didn't have to use that setting then I wouldn't be writing this post ;).

I am using PCL profile 7.

Exception (I didn't want to blah the entire stack trace, but I can if anyone thinks that would help):

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

2条回答
爷、活的狠高调
2楼-- · 2019-02-16 17:08

Although there isn't enough information in the question to confirm the root cause with 100% confidence.. Personally, after some experimentation I am positive that the only plausible explanation is as follows -

In short - In the test which fails, the correct (portable) version of Newtonsoft.Json.dll is not getting loaded.

In long - There are two tests being performed.

  1. Passes - I presume an exe, which calls into PCL1, which calls into portable version of NewtonSoft.Json.dll

  2. Fails - I presume another exe, which calls into PCL2, which calls into PCL1, which calls into a version (which version?) of NewtonSoft.Json.dll

The issue is not that PCL2 calls into PCL1 and somehow fails, due to indirect call being made into NewtonSoft.Json.dll. Rather, the issue is, as I am trying to highlight above, the second test happens to be setup in a way, that the wrong / non-portable version of NewtonSoft.Json.dll is available for PCL1 to consume.

In scenario which fails, imagine that the exe or any other non-portable assembly of that application also takes a dependency on (non-portable) version of NewtonSoft.Json.dll. In that case, in the output folder of the application / exe, there will be only one version of NewtonSoft.Json.dll, and if it is the non-portable one, then it will fail with above mentioned exception..

Further explanation - Why?

The type System.Runtime.Serialization.Formatters.FormatterAssemblyStyle is typically defined in mscorlib.dll. However, this type is not available for Portable class libraries to use (don't know about all profiles, but there are some profiles for sure, which do not have this type available). Hence the portable version of NewtonSoft.Json.dll, declares it itself, in it's own assembly.

Check the decompiled version of portable NewtonSoft.Json.dll in your favorite decompiler. Note line number 3 below.. following snippet is from 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,
  }
}

Now, when you compile code in a PCL, which references TypeNameAssemblyFormat property, presumeable of type System.Runtime.Serialization.Formatters.FormatterAssemblyStyle defined in Newtonsoft.Json.dll, following IL is generated (decompiled using 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)

Note how reference to the type is qualified with the assembly name [Newtonsoft.Json] (scroll to the right -----> to see it on the FormatterAssemblyStyle parameter being passed).

Now, if this portable version of Newtonsoft.Json.dll, gets replaced with non-portable version (because other parts of the project reference non-portable version), then at runtime, CLR will be unable to find method which matches the above signature (as seen in IL above).. and hence fail with System.MissingMethodException.

Unfortunately, the exception itself doesn't give enough information about the complete and exact signature of method it is looking for, including the assembly names.. The type name alone deceptively looks like something that would exist in one of the system dlls (mscorlib.dll in this case).. and not portable version of Newtonsoft.json.dll.

查看更多
迷人小祖宗
3楼-- · 2019-02-16 17:29

Ok, I've ended up completing the answer of my own question due to the clarity of Vikas' framing of the problem. And the resolution is the standard PCL approach with this type of problem: Create interface, configure container, use DI.

In this case, in my PCL, I created an INewtonsoftJsonSettingsProvider interface that has the two settings I use as properties as follows:

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

Then, in my PCL, I create a concrete implementation of this class as follows:

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

Note: I could easily skip the interface and just use this helper class alone, but I like to use interfaces when dealing with the container.

Then, in my PCL where my Newtonsoft serializer exists, I consume the settings from the container instead of directly creating those inside of the serialization methods. I'll go ahead and include that code here too (I abstracted serialization to an interface because of this problem, so I can swap out implementations):

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;
    }
}

Then, in my consuming non-PCL and non-Xamarin (may work in PCL but Xamarin has problem - see below), I configure the container with the proper System.Runtime.Serialization.Formatters.FormatterAssemblyStyle as explained in Vikas' answer:

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();
}

This executes without a problem in my .Net test projects. However, when I went to use this in Xamarin.Android project, I got an error stating that the FormatterAssemblyStyle exists in both Newtonsoft and in MonoAndroid mscorlib. Since Xamarin Studio doesn't seem to do extern aliases, I used reflection and dynamic as follows:

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();
}
查看更多
登录 后发表回答