Grp c .Core.RpcException «Не удалось десериализовать ответное сообщение ..» или «InvalidOperationException: Несоответствие длины» - PullRequest
2 голосов
/ 04 августа 2020

Я использую службу gRP C на. NET Core 3.1 и пытаюсь выполнять вызовы из клиента. NET Framework 4.7.2. Я использую protobuf- net для повторного использования существующих контрактов данных WCF. Сегодня я заметил следующее неожиданное поведение на стороне клиента, когда одно из полей объекта ответа не равно NULL.

Grpc.Core.RpcException: 'Status(StatusCode="Internal", Detail="Failed to deserialize response message.")

  • Пакеты : protobuf- net v2.4.4 , Grp c v2.30.0, protobuf- net .Grp c v1.0.90, protobuf- net .Grp c .Native v1.0.90, Google.Protobuf v3.12.2

Вот пример, который иллюстрирует общую структуру контрактов данных - в данном случае Response<PersonData> - это ответ а PersonDataList - ненулевое поле.

   [DataContract]  
   public class Response<TValue>{
         [DataMember(Order = 1)]
         public TValue Value; 
    }

   [DataContract]  
   public class PersonData : Data {
         [DataMember(Order = 1)]
         public IList<PersonDataItem> PersonDataList; 
    }
    
    [DataContract]
    public PersonDataItem {
         [DataMember(Order = 1)] 
         public PersonDataType Type {get; private set;}
    
         [DataMember(Order = 2)] 
         public DateTime? Time {get; private set;}
    
         ....
         [DataContract]
         public enum PersonDataType : int {
    
               [EnumMember]
               Child = 1, 
               [EnumMember]
               Adult = 2
         }
    }
    
    [DataContract]
    [ProtoInclude(1, typeof(PersonData)]
    public class Data {
         [DataMember(Order = 1)]
         public string Name
    }

Меня удивляет то, что я использую аналогичный или такой же шаблон в других контрактах данных, которые не вызывают исключений при десериализации ответа. Я немного поискал и обнаружил эту проблему из 2019 , которая указывает на различные версии Google.Protobuf как возможный источник ошибки (но, похоже, здесь это не так).

Кто-нибудь видел раньше это исключение? Я не уверен, что это проблема с моими контрактами на данные или, возможно, с несоответствием версии пакета. Мы очень ценим любые идеи или предложения!

Я также попытался выполнить обновление до protobuf- net v3.0.0 , но получал новое исключение на стороне клиента для каждого клиентского вызова: Grpc.Core.RpcException: 'Status(StatusCode="Unknown", Detail="Exception was thrown by handler. InvalidOperationException: Length mismatch; calculated '63', actual '58'"...) Кажется, это другая проблема, и я предполагаю, что это может быть связано с критическими изменениями. Re: Dynami c набирает protobuf- net v3 .

1 Ответ

1 голос
/ 04 августа 2020

Честно говоря, это звучит как ошибка в protobuf- net, из-за которой вы должны войти на GitHub, в идеале с репро , в котором также отображается контракт на обслуживание (интерфейс).

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

Рад помочь разобраться в этом вопросе (я автор), но: это больше похоже на GitHub.

Я подозреваю , что разница в длине связана с тем, что Data не является протоконтрактом (поэтому сомнительно, что соблюдается; это подкрепляется переменной var, что в противном случае были бы повторяющиеся поля 1), но правильное воспроизведение действительно поможет.

изменения в вашем коде заключаются в том, чтобы убедиться, что Data помечено как ProtoContract, использовать другой номер для «включаемой» части и добавить метод Create к PersonDataItem для использования тестовой оснастки; Трудно понять, какая часть проблемы связана с этим без репродукции.

using ProtoBuf;
using ProtoBuf.Meta;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;

[DataContract]
public class Response<TValue>
{
    [DataMember(Order = 1)]
    public TValue Value;
}

[DataContract]
public class PersonData : Data
{
    [DataMember(Order = 1)]
    public IList<PersonDataItem> PersonDataList;
}

[DataContract]
public class PersonDataItem
{
    public static PersonDataItem Create(PersonDataType type, DateTime? time)
        => new PersonDataItem { Type = type, Time = time };
    [DataMember(Order = 1)]
    public PersonDataType Type { get; private set; }

    [DataMember(Order = 2)]
    public DateTime? Time { get; private set; }


    [DataContract]
    public enum PersonDataType : int
    {

        [EnumMember]
        Child = 1,
        [EnumMember]
        Adult = 2
    }
}

[ProtoContract, DataContract]
[ProtoInclude(10, typeof(PersonData))]
public class Data
{
    [ProtoMember(1), DataMember(Order = 1)]
    public string Name;
}

static class P
{
    static void Main()
    {
        var resp = new Response<PersonData>
        {
            Value = new PersonData
            {
                Name = "abc",
                PersonDataList = new List<PersonDataItem>
                {
                    PersonDataItem.Create(PersonDataItem.PersonDataType.Adult, DateTime.Now),
                }
            }
        };
        var clone = RoundTrip(resp);
        Console.WriteLine(clone.Value.Name);
        var item = clone.Value.PersonDataList.Single();
        Console.WriteLine(item.Time);
        Console.WriteLine(item.Type);
    }
    static T RoundTrip<T>(T value)
    {
        var model = RuntimeTypeModel.Default;
        using var state = model.Measure<T>(value);
        using var ms = new MemoryStream();
        state.Serialize(ms); // we expect this to explode if there was a length mismatch
        ms.Position = 0;
        return model.Deserialize<T>(ms);
    }
}
...