将 WCF 服务迁移到 gRPC 服务时出错

问题描述

我有一个 WCF 服务,我想将其重写为 gRPC 服务。有一个特定的端点给我带来了一些麻烦。现在这方法看起来像这样:

public List<Dictionary<string,string> GetData(GetDataRequest request)
{
    List<Dictionary<string,string>> results = new List<Dictionary<string,string>>();
    /// ...
    /// Code that populates the results list
    /// ...
    return results;
}

我已经像这样编写了 proto 文件

message GetDataRequest {
    string code = 1;
}

message GetDataResponse {
    message keyvaluePair {
        map<string,string> pairs = 1;
    }
    repeated keyvaluePair results= 1;
}

service Demo {
    rpc GetData(GetDataRequest) returns (GetDataResponse);
}

以及服务实现:

public class DemoService : Demo.DemoBase
{
    public override async Task<GetDataResponse> GetData(GetDataRequest request,ServerCallContext context)
    {
        List<Dictionary<string,string>>();

        /// ...
        /// Code that populates the results list
        /// ...

        return await Task.Fromresult(new GetDataResponse
        {
            Results = results
        });
    }
}

我的问题是当我尝试返回字典列表时出现此错误

enter image description here

我需要做哪些更改才能正确返回响应? 我使用 Visual Studio 2019 gRPC 服务模板。

这是 protobuf 编译器生成的 GetDataResponse 代码

public sealed partial class GetDataResponse : pb::IMessage<GetDataResponse>
  #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE,pb::IBufferMessage
  #endif
  {
    private static readonly pb::MessageParser<GetDataResponse> _parser = new pb::MessageParser<GetDataResponse>(() => new GetDataResponse());
    private pb::UnkNownFieldSet _unkNownFields;
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public static pb::MessageParser<GetDataResponse> Parser { get { return _parser; } }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public static pbr::MessageDescriptor Descriptor {
      get { return global::GrpcService.Protos.DemoReflection.Descriptor.MessageTypes[1]; }
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    pbr::MessageDescriptor pb::IMessage.Descriptor {
      get { return Descriptor; }
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public GetDataResponse() {
      OnConstruction();
    }

    partial void OnConstruction();

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public GetDataResponse(GetDataResponse other) : this() {
      results_ = other.results_.Clone();
      _unkNownFields = pb::UnkNownFieldSet.Clone(other._unkNownFields);
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public GetDataResponse Clone() {
      return new GetDataResponse(this);
    }

    /// <summary>Field number for the "results" field.</summary>
    public const int ResultsFieldNumber = 1;
    private static readonly pb::FieldCodec<global::GrpcService.Protos.GetDataResponse.Types.keyvaluePair> _repeated_results_codec
        = pb::FieldCodec.ForMessage(10,global::GrpcService.Protos.GetDataResponse.Types.keyvaluePair.Parser);
    private readonly pbc::RepeatedField<global::GrpcService.Protos.GetDataResponse.Types.keyvaluePair> results_ = new pbc::RepeatedField<global::GrpcService.Protos.GetDataResponse.Types.keyvaluePair>();
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public pbc::RepeatedField<global::GrpcService.Protos.GetDataResponse.Types.keyvaluePair> Results {
      get { return results_; }
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public override bool Equals(object other) {
      return Equals(other as GetDataResponse);
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public bool Equals(GetDataResponse other) {
      if (ReferenceEquals(other,null)) {
        return false;
      }
      if (ReferenceEquals(other,this)) {
        return true;
      }
      if(!results_.Equals(other.results_)) return false;
      return Equals(_unkNownFields,other._unkNownFields);
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public override int GetHashCode() {
      int hash = 1;
      hash ^= results_.GetHashCode();
      if (_unkNownFields != null) {
        hash ^= _unkNownFields.GetHashCode();
      }
      return hash;
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public override string ToString() {
      return pb::JsonFormatter.ToDiagnosticString(this);
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public void Writeto(pb::CodedOutputStream output) {
    #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
      output.WriterawMessage(this);
    #else
      results_.Writeto(output,_repeated_results_codec);
      if (_unkNownFields != null) {
        _unkNownFields.Writeto(output);
      }
    #endif
    }

    #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    void pb::IBufferMessage.InternalWriteto(ref pb::WriteContext output) {
      results_.Writeto(ref output,_repeated_results_codec);
      if (_unkNownFields != null) {
        _unkNownFields.Writeto(ref output);
      }
    }
    #endif

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public int CalculateSize() {
      int size = 0;
      size += results_.CalculateSize(_repeated_results_codec);
      if (_unkNownFields != null) {
        size += _unkNownFields.CalculateSize();
      }
      return size;
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public void MergeFrom(GetDataResponse other) {
      if (other == null) {
        return;
      }
      results_.Add(other.results_);
      _unkNownFields = pb::UnkNownFieldSet.MergeFrom(_unkNownFields,other._unkNownFields);
    }

    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public void MergeFrom(pb::CodedInputStream input) {
    #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
      input.ReadRawMessage(this);
    #else
      uint tag;
      while ((tag = input.ReadTag()) != 0) {
        switch(tag) {
          default:
            _unkNownFields = pb::UnkNownFieldSet.MergeFieldFrom(_unkNownFields,input);
            break;
          case 10: {
            results_.AddEntriesFrom(input,_repeated_results_codec);
            break;
          }
        }
      }
    #endif
    }

    #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
      uint tag;
      while ((tag = input.ReadTag()) != 0) {
        switch(tag) {
          default:
            _unkNownFields = pb::UnkNownFieldSet.MergeFieldFrom(_unkNownFields,ref input);
            break;
          case 10: {
            results_.AddEntriesFrom(ref input,_repeated_results_codec);
            break;
          }
        }
      }
    }
    #endif

    #region nested types
    /// <summary>Container for nested types declared in the GetDataResponse message type.</summary>
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
    public static partial class Types {
      public sealed partial class keyvaluePair : pb::IMessage<keyvaluePair>
      #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE,pb::IBufferMessage
      #endif
      {
        private static readonly pb::MessageParser<keyvaluePair> _parser = new pb::MessageParser<keyvaluePair>(() => new keyvaluePair());
        private pb::UnkNownFieldSet _unkNownFields;
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public static pb::MessageParser<keyvaluePair> Parser { get { return _parser; } }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public static pbr::MessageDescriptor Descriptor {
          get { return global::GrpcService.Protos.GetDataResponse.Descriptor.nestedTypes[0]; }
        }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        pbr::MessageDescriptor pb::IMessage.Descriptor {
          get { return Descriptor; }
        }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public keyvaluePair() {
          OnConstruction();
        }

        partial void OnConstruction();

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public keyvaluePair(keyvaluePair other) : this() {
          pairs_ = other.pairs_.Clone();
          _unkNownFields = pb::UnkNownFieldSet.Clone(other._unkNownFields);
        }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public keyvaluePair Clone() {
          return new keyvaluePair(this);
        }

        /// <summary>Field number for the "pairs" field.</summary>
        public const int PairsFieldNumber = 1;
        private static readonly pbc::MapField<string,string>.Codec _map_pairs_codec
            = new pbc::MapField<string,string>.Codec(pb::FieldCodec.ForString(10,""),pb::FieldCodec.ForString(18,10);
        private readonly pbc::MapField<string,string> pairs_ = new pbc::MapField<string,string>();
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public pbc::MapField<string,string> Pairs {
          get { return pairs_; }
        }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public override bool Equals(object other) {
          return Equals(other as keyvaluePair);
        }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public bool Equals(keyvaluePair other) {
          if (ReferenceEquals(other,null)) {
            return false;
          }
          if (ReferenceEquals(other,this)) {
            return true;
          }
          if (!Pairs.Equals(other.Pairs)) return false;
          return Equals(_unkNownFields,other._unkNownFields);
        }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public override int GetHashCode() {
          int hash = 1;
          hash ^= Pairs.GetHashCode();
          if (_unkNownFields != null) {
            hash ^= _unkNownFields.GetHashCode();
          }
          return hash;
        }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public override string ToString() {
          return pb::JsonFormatter.ToDiagnosticString(this);
        }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public void Writeto(pb::CodedOutputStream output) {
        #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
          output.WriterawMessage(this);
        #else
          pairs_.Writeto(output,_map_pairs_codec);
          if (_unkNownFields != null) {
            _unkNownFields.Writeto(output);
          }
        #endif
        }

        #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        void pb::IBufferMessage.InternalWriteto(ref pb::WriteContext output) {
          pairs_.Writeto(ref output,_map_pairs_codec);
          if (_unkNownFields != null) {
            _unkNownFields.Writeto(ref output);
          }
        }
        #endif

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public int CalculateSize() {
          int size = 0;
          size += pairs_.CalculateSize(_map_pairs_codec);
          if (_unkNownFields != null) {
            size += _unkNownFields.CalculateSize();
          }
          return size;
        }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public void MergeFrom(keyvaluePair other) {
          if (other == null) {
            return;
          }
          pairs_.Add(other.pairs_);
          _unkNownFields = pb::UnkNownFieldSet.MergeFrom(_unkNownFields,other._unkNownFields);
        }

        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        public void MergeFrom(pb::CodedInputStream input) {
        #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
          input.ReadRawMessage(this);
        #else
          uint tag;
          while ((tag = input.ReadTag()) != 0) {
            switch(tag) {
              default:
                _unkNownFields = pb::UnkNownFieldSet.MergeFieldFrom(_unkNownFields,input);
                break;
              case 10: {
                pairs_.AddEntriesFrom(input,_map_pairs_codec);
                break;
              }
            }
          }
        #endif
        }

        #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
        [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
        void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
          uint tag;
          while ((tag = input.ReadTag()) != 0) {
            switch(tag) {
              default:
                _unkNownFields = pb::UnkNownFieldSet.MergeFieldFrom(_unkNownFields,ref input);
                break;
              case 10: {
                pairs_.AddEntriesFrom(ref input,_map_pairs_codec);
                break;
              }
            }
          }
        }
        #endif

      }

    }
    #endregion

  }

解决方法

ASP.NET Core gRPC for WCF developers 中,Repeated fields for lists and arrays 部分解释了

重复字段由 Google.Protobuf.Collections.RepeatedField 类型的只读属性表示,而不是任何内置的 .NET 集合类型。此类型实现所有标准的 .NET 集合接口,例如 IList 和 IEnumerable。因此,您可以使用 LINQ 查询或轻松地将其转换为数组或列表。

您必须使用 Add/AddRange 向该属性添加项目,例如:

var response=new GetDataResponse();
response.AddRange(results);
return response;

您不需要将 response 包装在 Task 中以返回它,因为您的方法具有 async 签名,因此它已经包装了返回值。无论如何,await Task.FromResult(x) 只返回 x