Как правильно отправлять данные между приложениями. NET Core и. NET Framework, сериализуя с Json. NET? - PullRequest
0 голосов
/ 30 января 2020

Я обмениваюсь данными между приложением. NET Core 3.1 (+. NET Standard 2.1) и приложением. NET Framework 4.6, которое я полностью контролирую. . NET Core запускает процесс. NET Framework, и эти два процесса взаимодействуют друг с другом через перенаправленный stdout / stdin.

My. 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[], происходит десериализация.

Это происходит потому, что Поле $type, которое Json. NET сохраняет, содержит сборку System.Private.CoreLib, к которой нет доступа из FW.

Я попытался обойти эту проблему, перенаправив имя какой-либо сборки с помощью Json. NET s 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";
        }
    }
}

Это немного глупо, и я ' Я предпочел бы не делать этого, но он отлично работает для более сложных типов, поэтому я могу сделать 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
}

Десериализация 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);

Кажется, это работает нормально, но я бы предпочел метод, который не предусматривает двойной сериализации каждого аргумента в строку. Есть ли такой метод?

Последнее, что я пытался, это не сохранять $type информацию для моих аргументов:

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

Затем при десериализации аргументы становятся JObjects, JArrays, или JValues ​​(все типы JTokens) согласно разделу «Нетипизированные объекты» здесь .

А затем, когда я десериализуюсь и готовлюсь к вызову метода, я конвертирую свои аргументы следующим образом:

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 для чего-то подобного?

Ответы [ 3 ]

1 голос
/ 30 января 2020

Кажется, что JSON. NET игнорирует атрибуты [TypeForwardedFrom] перемещенных типов, что именно по причине совместимости, когда типы сериализуются. В. NET Core каждый классифицируемый тип платформы classi c имеет этот атрибут со старой идентификацией платформы. Например: 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, вы можете попробовать this XmlSerializer, который по умолчанию игнорирует имена сборок, но если используется со сборкой квалифицированные имена, то он может рассмотреть TypeForwardedFromAttribute ( nuget )

0 голосов
/ 07 мая 2020

Я закончил двойную сериализацию по аргументам, которые я упомянул в своем вопросе. Не нашел другого надежного способа справиться с этим:

. NET Ядро:

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
}

. NET Framework:

// ... 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);
0 голосов
/ 30 января 2020

Я не могу помочь с самой сериализацией, только с возможностью обойти необходимость в ней.

Поскольку у нас есть случай, когда один процесс (Core) запускает другой (FW), они могут фактически взаимодействовать с консолью. Перенаправление ввода. Если вы запускаете программу через класс Process (который в любом случае является основным способом), а другая программа является консолью или, по крайней мере, проверяет потоки, вы можете заставить их общаться таким образом: https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.process.standardinput#remarks

Хотя это проще, чем Serialization, оно может быть не таким «будущим», как оно. Позже вы можете захотеть, чтобы что-то еще могло вызывать приложение FrameWork. Или вы обычно хотите, чтобы он мог общаться через сеть.

...