首页 > 解决方案 > 如何在 .NET Core 和 .NET Framework 应用程序之间干净地发送数据,使用 Json.NET 进行序列化?

问题描述

我在 .NET Core 3.1 (+ .NET Standard 2.1) 应用程序和 .NET Framework 4.6 应用程序之间进行通信,我可以完全控制这两个应用程序。.NET Core 启动 .NET Framework 进程,这两个进程通过重定向的 stdout/stdin 相互通信。

我的 .NET Core 应用程序 (Core) 想要告诉我的 .NET Framework 应用程序 (FW) 在给定一些数据的情况下运行方法。

它使用的 API 类似于SendMessage("FW_MethodName", new List<object> { param1, param2 });(此操作需要在 FW 进程中发生,并且 FW 不知道要执行此操作,直到 Core 告诉它执行此操作)。

我使用 Json.NET 进行序列化并将上述消息序列化,如下所示:

public class ATypeOfMessage : BaseMessage 
{
    public string Name { get; set; }
    public List<object> Args { get; set; }

    public Message(string name, List<object> args) 
    {
        Name = name;
        Args = args;
    }
}

ATypeOfMessage message = new ATypeOfMessage("FW_MethodName", new List<object> { param1, param2 });
JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandline = TypeNameHandling.All };
string serializedMessage = JsonConvert.SerializeObject(message, typeof(BaseMessage), settings);
// the idea being that I have multiple types of messages, so I serialize/deserialize BaseMessages and
// save off $type metadata, so I can deserialize the string into the correct derived class (ATypeOfMessage)
myFWProcess.StandardInput.WriteLine(serializedMessage);

FW 然后读取消息:

string serializedMessage = Console.In.ReadLine();
JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandline = TypeNameHandling.All };
BaseMessage message = JsonConvert.DeserializeObject<BaseMessage>(serializedMessage, settings);

switch (message) 
{
    case ATypeOfMessage myMessage:
        // ATypeOfMessage is doubly-defined in FW and Core
        // get the methodInfo for method myMessage.Name and do...
        object answer = methodInfo.Invoke(this, myMessage.Args.ToArray());
        break;
}

这适用于简单类型(例如 int、string),但是当我传入更复杂的东西byte[]时,反序列化会崩溃。

发生这种情况是因为$typeJson.NET 保存的字段包含程序集System.Private.CoreLib,无法从 FW 访问。

我尝试通过使用 Json.NET 进行一些程序集名称重定向来解决此问题SerializationBinder

JsonSerializerSettings settings = new JsonSerializerSettings { TypeNameHandline = TypeNameHandling.All, SerializationBinder = new MySerializationBinder() };

class MySerializationBinder : DefaultSerializationBinder 
{
    public override void BindToName(Type serializedType, out string assemblyName, out string typeName) 
    {
        base.BindToName(serializedType, out assemblyName, out typeName);
        if (assemblyName.StartsWith("System.Private.CoreLib")) 
        {
            assemblyName = "mscorlib";
        }
    }
}

这有点hacky,我不想这样做,但它适用于更复杂的类型,所以我可以SendMessage("FW_MethodName", new List<object> { byteArray, stringArray });

然而,更复杂的类型,如Dictionary<string, List<string>>break。保存的$type数据是:

\"$type\":\"System.Collections.Generic.Dictionary`2[[System.String, System.Private.CoreLib],[System.Collections.Generic.List`1[[System.String, System.Private.CoreLib]], System.Private.CoreLib]], mscorlib\",\"k1\":[\"a\",\"b\",\"c\"],\"k2\":[\"a\",\"b\",\"c\"],\"k3\":[\"a\",\"b\",\"c\"]

其中再次包含 CoreLib,因此 FW 无法反序列化它。

我尝试过的另一件事是修改ATypeOfMessage接受字符串列表而不是对象,然后我进行双重序列化。

核心序列化:

public SendMessage(string methodName, List<object> args) 
{
    List<string> serializedArgs = new List<string>();
    foreach (object arg in args) 
    {
        serializedArgs.Add(JsonConvert.SerializeObject(arg);
    }

    // ... same logic as before to create ATypeOfMessage, serialize the whole thing, and send it to FW
}

固件反序列化:

// ... deserializes the object as before, but before invoking the method, we deserialize the specific args
MethodInfo methodInfo = typeof(ClassWithMethods).GetMethod(myMessage.Name);
ParameterInfo[] paramInfo = methodInfo.GetParameters();
object[] finalParams = new object[myMessage.Args.Count];
for (int i = 0 ; i < myMessage.Args.Count; i++) 
{
    object arg = myMessage.Args[i];
    finalParameters[i] = JsonConvert.DeserializeObject(arg, paramInfo[i].ParameterType);
}

object answer = methodInfo.Invoke(this, finalParameters);

这似乎工作正常,但我更喜欢不涉及将每个 arg 双重序列化为字符串的方法。有没有这样的方法?

我尝试的最后一件事是不$type为我的 args 保存信息:

[JsonProperty(ItemTypeNameHandling = TypeNameHandling.None)] 
public List<object> Args { get; set; }

然后在反序列化时,根据此处的“无类型对象”部分,参数变为 JObject、JArray 或 JValue(所有类型的 JToken)。

然后当我反序列化并准备调用该方法时,我像这样转换我的参数:

for (int i = 0 ; i < myMessage.Args.Count; i++) 
{
    object arg = msg.Args[i];
    if (arg is JToken)
    {
        arg = (arg as JToken).ToObject(paramInfo[i].ParameterType);
    }
    finalParameters[i] = arg;
}

现在,我遇到了与以前相反的问题。复杂类型 ( string[], Dictionary<string, List<string>>) 可以正确反序列化,但类似的类型byte[]不会,因为 Json.NET 将字节数组转换为 base64 编码的字符串,因此当 FW 获取它时,它不知道该字符串是否真的应该是一个字节数组。

如何在 Core 和 FW 之间干净地发送这样的数据?也许我需要使用JsonConverters这样的东西?

标签: c#.netserialization.net-corejson.net

解决方案


JSON.NET 似乎忽略了重[TypeForwardedFrom]定位类型的属性,这正是在序列化类型时出于兼容性原因。在 .NET Core 中,每个经典的可序列化框架类型都具有带有旧框架标识的此属性。例如:https ://source.dot.net/#System.Private.CoreLib/Dictionary.cs,35

解决方案1:

在您的MySerializationBinder覆盖BindToName方法中:

public override void BindToName(Type serializedType, out string assemblyName, out string typeName)
{
    if (Attribute.GetCustomAttribute(serializedType, typeof(TypeForwardedFromAttribute), false) is TypeForwardedFromAttribute attr)
       assemblyName = attr.AssemblyFullName;

     // ...
}

使用旧标识解析类型Type.GetType也应该在 .NET Core/Standard 中自动工作

解决方案2:

如果您在 .NET 应用程序之间进行通信并且未强制使用 JSON 序列化,您可以尝试这个XmlSerializer,默认情况下会忽略程序集名称,但如果与程序集限定名称一起使用,则可以考虑TypeForwardedFromAttribute( nuget )


推荐阅读