在使用TypeNameAssemblyFormat和PCLs时,使用Newtonsoft.Json出现了MissingMethodException异常。

10

在使用PCLs时,使用TypeNameAssemblyFormat是否存在问题?我在使用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)'

在你的测试中,当你遇到上述异常时,Newtonsoft.Json 只是从第一个使用它的 PCL 引用吗?还是有其他非-PCL 程序集(或者甚至具有不同配置文件的 PCL 程序集)也引用了 Newtonsoft.Json?换句话说,当这段代码不起作用时,有哪些程序集引用了 Newtonsoft.Json,它们的配置文件是什么?相反,当它起作用时,Newtonsoft.Json 是否只从一个包含上述代码的 PCL 程序集中引用? - Vikas Gupta
Newtonsoft被PCL1、PCL2、Test1和Test2(.net 4.5)引用。在引用了PCL1的Test1中通过,在引用了PCL1、PCL2的Test2中失败。如果每个项目没有引用Newtonsoft,我认为该项目将无法编译。 - user4275029
划掉上面的评论:Newtonsoft被引用自PCL1、PCL2、Test1和Test2(.net 4.5)。在Test1和Test2上都失败了。我在进行了几天的故障排查后,不记得在什么时候提出这个SO问题,也不记得成功/失败的具体情况。今天早上重新测试后,Test1和Test2都失败了。 - user4275029
说句实话:在我的Windows Phone 8.1工程中(没有显式使用JsonSerializerSettings),当我使用新通信8.0.3时,也会出现同样的缺失方法异常。虽然这个版本据说对PCL兼容,但它引用了适用于PCL或Windows Phone都不可用的mscorlib。我将NuGet引用回滚到8.0.2,重新构建后,问题消失了。 - Zenilogix
2个回答

6
尽管问题中没有足够的信息以百分之百的把握确认根本原因...但在一些实验后,我个人确信唯一可行的解释如下 -
简而言之 - 在失败的测试中,未正确(便携式)加载Newtonsoft.Json.dll的版本。
详细来说 - 进行了两个测试。
通过 - 我假设是一个调用PCL1的exe,该PCL1调用NewtonSoft.Json.dll的便携式版本。
失败 - 我假设是另一个调用PCL2的exe,该PCL2调用PCL1,然后调用NewtonSoft.Json.dll的版本(哪个版本?)。
问题不是PCL2调用PCL1并以某种方式失败,因为由NewtonSoft.Json.dll进行间接调用。相反,问题在于,正如我试图强调的那样,第二个测试被设置为以错误/非便携式版本的NewtonSoft.Json.dll供PCL1使用。
在失败的情况下,想象一下exe或该应用程序的任何其他非便携式程序集也对(非便携式)NewtonSoft.Json.dll具有依赖性。在这种情况下,在应用程序/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,
  }
}

现在,在编译引用TypeNameAssemblyFormat属性的PCL代码时,可以假定该属性类型为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] 限定的(向右滚动 -----> 可以在传递的 FormatterAssemblyStyle 参数上看到它)。
现在,如果这个便携式版本的 Newtonsoft.Json.dll 被非便携式版本替换(因为项目的其他部分引用了非便携式版本),那么在运行时,CLR 将无法找到与上面签名匹配的方法(如上所示在 IL 中),因此会失败并显示 System.MissingMethodException。
不幸的是,异常本身没有提供足够的信息来确定它正在查找的方法的完整和确切签名,包括程序集名称。单独的类型名称看起来会误导人认为它存在于一个系统 dll 中(在这种情况下是 mscorlib.dll),而不是 Newtonsoft.json.dll 的便携式版本。

我之前放弃了这个问题。今天我会再次查看并尝试根据您的答案提供信息。 - user4275029
然而,从我的记忆中,我可以告诉你,在成功的情况下,执行的是.Tests单元测试项目,并且它的目标是.Net 4.5。在一个失败的案例中,.Tests单元测试项目也是针对.Net 4.5进行定位的。唯一的区别是第二个单元测试项目引用了两个PCLs,这两个PCLs具有相同的配置文件,当在Xamarin Studio中加载时,显示配置文件7(但是对于这些项目,我是在VS中开发的。然而,它没有配置文件指示器。在VS中,我在两个PCLs的项目属性中看到了.Net 4.5、Win 8、Xamarin.Android/iOS/iOS(Classic))。 - user4275029
经过重新测试,发现我的记忆出了问题。它在Test1和Test2单元测试项目中都失败了。(为自己辩护,这是一个半月前的问题!)输出目录中确实只有一个Newtonsoft dll版本。经过审查您的答案,我同意您对情况的评估。但问题仍然存在。有没有办法让它工作?顺便说一句,我会将您的回复标记为答案,因为它清楚地确定了情况。对于其他有兴趣的人,我现在正在使用ServiceStack.Text,到目前为止它一直在工作。 - user4275029
我已经完成了下面的答案。它包含了太多的文本/代码,无法在评论中包含。 - user4275029
1
感谢您对这个答案所做的努力。我不认为这正是我所经历的,但这是一个很好的理论,无论如何都非常有用。 - craftworkgames
2
我曾经遇到过这种情况,当你的解决方案有多个项目时,Newtonsoft.Json.dll 的引用方式存在不一致性。在我的情况下,所有项目都是 net45 类库(加上一个单独的 net45 控制台应用程序)- 但由于一部分项目通过 .csprojpackages.config 引用了可移植版本的 Newtonsoft.Json.dll,编译器选择包含可移植版本,然后就出现了运行时错误。清理不一致性后,问题得以解决。感谢您对自己发现的问题进行了精彩的描述 :) - Lasse Christiansen

5

好的,由于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 https://dev59.com/114d5IYBdhLWcg3wLf_P
    /// </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有问题 - 请参见下文),我按照Vikas的答案所述,使用正确的System.Runtime.Serialization.Formatters.FormatterAssemblyStyle配置容器:

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项目中使用时,出现了错误,指出FormatterAssemblyStyle同时存在于Newtonsoft和MonoAndroid mscorlib中。由于Xamarin Studio似乎不支持extern别名,因此我使用了反射和动态方法,如下所示:
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();
}

我认为问题的根源可能是Json.NET或NuGet中的一个或两个都存在bug。我授予您赏金,因为它确实解决了问题,而且我已经没有时间再授予其他人赏金了。感谢您所付出的努力。 - craftworkgames
1
@craftworkgames 我认为这不是Json.Net或Nuget的错误。也许我们可以在聊天中讨论一下,因为我认为这个问题的本质存在着变化,你也会在其他地方看到它。对我来说,最好的类比是DLL地狱,即版本冲突。我很高兴你把赏金授予了ibgib,因为DI似乎是实际问题的一个非常好的解决方案,但我无法以100%的信心确认。如果您的问题确实略有不同,希望您能提出另一个带有更多信息的问题,我们将看看是否能够提供帮助。 - Vikas Gupta
@VikasGupta 是的,我一定会整理出我自己对这个问题发现的解释。不幸的是,我还没有抽出时间来做这件事。但我已经设置了一个提醒,以便回来继续处理,以免忘记。 - craftworkgames
感觉有点像是一个错误或者设计不良的决定,但也许这只是必要的恶……这还延伸到了另一个问题:我的答案适用于 .Net 测试项目,但当我在我的 Xamarin.Android 项目中使用这种方法时,它会给出错误,说该类型在 Newtonsoft 和 MonoAndroid 的 mscorlib 中都有定义。我仍在努力解决这个问题(我不知道如何将其限定为特定的一个)。 - user4275029
我已经编辑了我的答案,使其适用于Xamarin.Android。 - user4275029

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接