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

9 serialization json missingmethodexception json.net portable-class-library

将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;
Run Code Online (Sandbox Code Playgroud)

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

我正在使用PCL简介7.

例外(我不想吹嘘整个堆栈跟踪,但我可以,如果有人认为这会有所帮助):

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

Vik*_*pta 5

虽然在问题中没有足够的信息来确认100%信心的根本原因.就个人而言,经过一些实验,我很肯定,唯一可信的解释如下 -

简而言之 -在该测试失败,正确的(便携式)的版本Newtonsoft.Json.dll得到加载.

总之 - 正在进行两项测试.

  1. 通过 - 我假设一个exe,它调用PCL1,调用便携版本NewtonSoft.Json.dll

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

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

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

进一步解释 - 为什么?

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

检查的反编译版便携式 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,
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,当您在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)
Run Code Online (Sandbox Code Playgroud)

注意如何使用程序集名称限定对类型的引用[Newtonsoft.Json](向右滚动----->FormatterAssemblyStyle在传递的参数上查看它).

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

不幸的是,除了本身并没有提供有关它正在寻找方法的完整和详细的签名足够的信息,包括程序集名称.该类型名称单独迷惑的样子,将存在于系统DLL的一个东西(mscorlib.dll在这案例)..而不是便携版Newtonsoft.json.dll.

  • +1当我有一个多项目的解决方案时,我发现了这种情况,其中引用了"Newtonsoft.Json.dll"的方式不一致.在我的例子中,所有项目都是net45类库(加上一个net45控制台应用程序) - 但是,由于项目的一个子集引用了`Newtonsoft.Json.dll`的可移植版本(通过`.csproj`和`packages.config `),编译器选择包含可移植版本,然后给我这个运行时错误.在清理不一致之后,它起作用了.感谢您在描述您的发现方面做得很好:) (2认同)

小智 5

好的,由于 Vikas 对问题的框架清晰,我最终完成了我自己问题的答案。解决方案是解决此类问题的标准 PCL 方法:创建接口、配置容器、使用 DI。

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

public interface INewtonsoftJsonSettingsProvider
{
    JsonSerializerSettings Default { get; set; }
    JsonSerializerSettings Concrete { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

然后,在我的 PCL 中,我创建了此类的具体实现,如下所示:

public class NewtonsoftJsonSettingsProvider : Interfaces.INewtonsoftJsonSettingsProvider
{
    public JsonSerializerSettings Default { get; set; }
    public JsonSerializerSettings Concrete { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

注意:我可以很容易地跳过接口,只使用这个辅助类,但我喜欢在处理容器时使用接口。

然后,在我的Newtonsoft 序列化器所在的PCL中,我使用容器中的设置,而不是直接在序列化方法内部创建那些设置。我将继续并在此处包含该代码(由于此问题,我将序列化抽象为接口,因此我可以交换实现):

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,在我使用的非 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();
}
Run Code Online (Sandbox Code Playgroud)

这在我的 .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();
}
Run Code Online (Sandbox Code Playgroud)