与PCL一起使用TypeNameAssemblyFormat时,使用Newtonsoft.Json时出现MissingMethodException

时间:2014-11-22 17:35:00

标签: json serialization json.net portable-class-library missingmethodexception

将TypeNameAssemblyFormat与PCL一起使用是否有问题?使用Newtonsoft.Json时,我没有任何问题,除非我使用此序列化设置。

这是我与Json相关的代码:

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;

当我在声明它的同一个库中进行调用时,它很好。但是,当我从调用上述代码的其他PCL进行调用时,我得到了缺少的方法异常。这只发生在我使用TypeNameAssemblyFormat设置时(即如果我没有必须使用该设置,那么我就不会写这篇文章了;)。

我正在使用PCL个人资料7.

异常(我不想让整个堆栈跟踪,但我可以,如果有人认为这会有所帮助):

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

2 个答案:

答案 0 :(得分:5)

虽然问题中没有足够的信息来确认100%信心的根本原因。就个人而言,经过一些实验,我肯定只有似乎合理的 解释如下 -

简而言之 - 在失败的测试中,Newtonsoft.Json.dll的正确(可移植)版本 未加载。

长期 - 正在执行两项测试。

  1. 通过 - 我假设一个exe,它调用PCL1,调用可移植版本的NewtonSoft.Json.dll

  2. 失败 - 我假设 另一个 exe,它调用PCL2,调用PCL1,调用PCL1版本( 版本? NewtonSoft.Json.dll

  3. 由于 间接 调用NewtonSoft.Json.dll,因此PCL2调用PCL1并以某种方式失败,问题是。相反,问题是,正如我在上面强调的那样, 第二 测试恰好以某种方式设置,错误/非便携< / strong>版本NewtonSoft.Json.dll可供PCL1使用。

    在失败的场景中,假设该应用程序的exe或任何其他非可移植程序集也依赖于NewtonSoft.Json.dll的(非可移植)版本。在这种情况下,在application / exe的输出文件夹中,只有一个版本的NewtonSoft.Json.dll,如果它是非便携式,那么它将失败,提到例外..

    进一步说明 - 为什么?

    System.Runtime.Serialization.Formatters.FormatterAssemblyStyle类型通常在mscorlib.dll中定义。但是,此类型不适用于可移植类库(不了解所有配置文件,但有一些配置文件可以肯定,但没有此类型可用)。因此NewtonSoft.Json.dll可移植版本, 在其自己的程序集 中声明了它。

    检查您最喜欢的反编译器中 portable NewtonSoft.Json.dll的反编译版本。 请注意以下第3行以下代码段来自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,
      }
    }
    

    现在,当您在PCL中编译代码(引用TypeNameAssemblyFormat属性,可以在 System.Runtime.Serialization.Formatters.FormatterAssemblyStyle中定义类型Newtonsoft.Json.dll )时,生成以下IL(使用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)
    

    注意如何使用程序集名称[Newtonsoft.Json]限定对类型的引用(向右滚动-----&gt; 以在FormatterAssemblyStyle参数上查看它被通过了。

    现在,如果这个Newtonsoft.Json.dll的可移植版本被替换为非可移植版本(因为项目的其他部分引用了非可移植版本),那么在运行时,CLR将无法找到匹配的方法上面的签名(见上面的IL )..因此System.MissingMethodException失败。

    不幸的是,异常本身并未提供有关其正在寻找的方法的完整和准确签名的足够信息,包括程序集名称 ..单独的类型名称欺骗性地看起来像某个系统dll(在这种情况下为mscorlib.dll)中存在的东西...而不是Newtonsoft.json.dll的可移植版本。

答案 1 :(得分:3)

好吧,由于Vikas明确了解问题,我最终完成了自己问题的答案。解决方案是具有此类问题的标准PCL方法:创建接口,配置容器,使用DI。

在这种情况下,在我的PCL 中,我创建了一个INewtonsoftJsonSettingsProvider接口,它具有我用作属性的两个设置,如下所示:

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

然后,在我的PCL中 ,我按如下方式创建了这个类的具体实现:

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

注意:我可以轻松跳过界面,只使用这个帮助程序类,但我喜欢在处理容器时使用接口。

然后,在我的PCL 中存在我的Newtonsoft序列化程序的,我使用容器中的设置而不是直接在序列化方法中创建它们。我将继续在这里包含该代码(由于这个问题我将序列化抽象到接口,所以我可以换出实现):

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

然后,在我的消费非PCL和非Xamarin (可能在PCL中工作但Xamarin有问题 - 见下文),我按照正确的System.Runtime.Serialization.Formatters.FormatterAssemblyStyle配置容器在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();
}

这在我的.Net测试项目中没有问题。但是,当我在Xamarin.Android项目中使用它时,我收到一个错误,指出在Newtonsoft和MonoAndroid mscorlib中都存在FormatterAssemblyStyle。由于Xamarin Studio似乎没有做外部别名,我使用反射和动态如下:

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