10

将 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)'
4

2 回答 2

6

尽管问题中没有足够的信息以 100% 的信心确认根本原因。就个人而言,经过一些实验,我很肯定唯一合理的解释如下 -

简而言之- 在失败的测试中,没有加载正确的(便携式)版本。Newtonsoft.Json.dll

- 有两个测试正在执行。

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

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

问题在于 PCL2 调用 PCL1 并以某种方式失败,因为间接调用NewtonSoft.Json.dll. 相反,问题在于,正如我在上面试图强调的那样,第二个测试恰好以某种方式设置,PCL1 可以使用错误/不可移植的版本。NewtonSoft.Json.dll

在失败的情况下,假设该应用程序的 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,
  }
}

现在,当您在 PCL中编译代码时,该 PCL 引用TypeNameAssemblyFormatSystem.Runtime.Serialization.Formatters.FormatterAssemblyStyle Newtonsoft.Json.dllIldasm

  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

于 2015-01-04T01:03:16.787 回答
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; }
}

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

然后,在我的 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;
    }
}

然后,在我使用的非 PCL 和非 XamarinSystem.Runtime.Serialization.Formatters.FormatterAssemblyStyle中(可能在 PCL 中工作,但 Xamarin 有问题 - 见下文),我按照 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();
}
于 2015-01-09T14:42:41.150 回答