Nota
O acesso a esta página requer autorização. Podes tentar iniciar sessão ou mudar de diretório.
O acesso a esta página requer autorização. Podes tentar mudar de diretório.
Observação
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.
Advertência
Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 10 deste artigo.
Por James Newton-King e Mark Rendle
O gRPC utiliza o Protobuf como sua Linguagem de Definição de Interface (IDL). O Protobuf IDL é um formato neutro em termos de linguagem para especificar as mensagens enviadas e recebidas pelos serviços gRPC. As mensagens protobuf são definidas em .proto ficheiros. Este documento explica como os conceitos de Protobuf correspondem ao .NET.
Mensagens Protobuf
As mensagens são o principal objeto de transferência de dados no Protobuf. São conceptualmente semelhantes às classes .NET.
syntax = "proto3";
option csharp_namespace = "Contoso.Messages";
message Person {
int32 id = 1;
string first_name = 2;
string last_name = 3;
}
A definição da mensagem anterior especifica três campos como pares nome-valor. Tal como nas propriedades dos tipos .NET, cada campo tem um nome e um tipo. O tipo de campo pode ser um valor escalar de Protobuf, por exemplo int32, ou outra mensagem.
O guia de estilo Protobuf recomenda usar underscore_separated_names para nomes de campos. Novas mensagens Protobuf criadas para aplicações .NET devem seguir as diretrizes de estilo Protobuf. As ferramentas .NET geram automaticamente tipos .NET que utilizam padrões de nomenclatura .NET. Por exemplo, um first_name campo Protobuf gera uma FirstName propriedade .NET.
Para além de um nome, cada campo na definição da mensagem tem um número único. Os números de campo são usados para identificar campos quando a mensagem é serializada para Protobuf. Serializar um pequeno número é mais rápido do que serializar todo o nome do campo. Como os números de campo identificam um campo, é importante ter cuidado ao alterá-los. Para mais informações sobre a alteração de mensagens Protobuf, consulte Versionamento dos serviços gRPC.
Quando uma aplicação é construída, a ferramenta Protobuf gera tipos .NET a partir de .proto ficheiros. A Person mensagem gera uma classe .NET:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Para mais informações sobre mensagens Protobuf, consulte o guia da língua Protobuf.
Tipos de Valores Escalares
O Protobuf suporta uma variedade de tipos nativos de valores escalares. A tabela seguinte lista-os todos com o seu equivalente tipo C#:
| Tipo Protobuf | Tipo C# |
|---|---|
double |
double |
float |
float |
int32 |
int |
int64 |
long |
uint32 |
uint |
uint64 |
ulong |
sint32 |
int |
sint64 |
long |
fixed32 |
uint |
fixed64 |
ulong |
sfixed32 |
int |
sfixed64 |
long |
bool |
bool |
string |
string |
bytes |
ByteString |
Os valores escalares têm sempre um valor padrão e não podem ser definidos para null. Esta restrição inclui string e ByteString que são classes C#.
string por padrão é um valor de string vazio e ByteString por padrão é um valor de bytes vazio. Tentar defini-los como null gera um erro.
Tipos de wrapper nullable podem ser usados para suportar valores nulos.
Datas e horários
Os tipos escalares nativos não fornecem suporte para valores de data e hora, equivalentes a .NET, DateTimeOffset, DateTime, e TimeSpan. Estes tipos podem ser especificados usando algumas das extensões Tipos Bem Conhecidos do Protobuf. Estas extensões fornecem geração de código e suporte em tempo de execução para tipos de campos complexos nas plataformas suportadas.
A tabela seguinte mostra os tipos de data e hora:
| Tipo .NET | Tipo Well-Known Protobuf |
|---|---|
DateTimeOffset |
google.protobuf.Timestamp |
DateTime |
google.protobuf.Timestamp |
TimeSpan |
google.protobuf.Duration |
syntax = "proto3";
import "google/protobuf/duration.proto";
import "google/protobuf/timestamp.proto";
message Meeting {
string subject = 1;
google.protobuf.Timestamp start = 2;
google.protobuf.Duration duration = 3;
}
As propriedades geradas na classe C# não são os tipos de data e hora .NET. As propriedades utilizam as Timestamp classes e Duration no Google.Protobuf.WellKnownTypes namespace. Estas classes fornecem métodos para converter para e a partir DateTimeOffsetde , DateTime, e TimeSpan.
// Create Timestamp and Duration from .NET DateTimeOffset and TimeSpan.
var meeting = new Meeting
{
Time = Timestamp.FromDateTimeOffset(meetingTime), // also FromDateTime()
Duration = Duration.FromTimeSpan(meetingLength)
};
// Convert Timestamp and Duration to .NET DateTimeOffset and TimeSpan.
var time = meeting.Time.ToDateTimeOffset();
var duration = meeting.Duration?.ToTimeSpan();
Observação
O Timestamp tipo funciona com os horários UTC.
DateTimeOffset Os valores têm sempre um deslocamento zero, e a DateTime.Kind propriedade é sempre DateTimeKind.Utc.
Tipos anuláveis
A geração de código Protobuf para C# usa os tipos nativos, como int para int32. Portanto, os valores estão sempre incluídos e não podem ser null.
Para valores que requerem valores explícitos null, como usar int? em código C#, os Tipos Bem-Conhecidos do Protobuf incluem wrappers que são compilados para tipos anuláveis em C#. Para os usar, importa wrappers.proto para o teu .proto ficheiro, como o seguinte código:
syntax = "proto3";
import "google/protobuf/wrappers.proto";
message Person {
// ...
google.protobuf.Int32Value age = 5;
}
wrappers.proto Os tipos não são visíveis em propriedades geradas. O Protobuf mapeia-os automaticamente para tipos .NET nulláveis apropriados nas mensagens C#. Por exemplo, um google.protobuf.Int32Value campo gera uma int? propriedade. Propriedades de tipos de referência como string e ByteString permanecem inalteradas, exceto null que podem ser atribuídas sem erro.
A tabela seguinte mostra a lista completa de tipos de wrapper com o seu equivalente tipo C#:
| Tipo C# | Well-Known Invólucro de tipos |
|---|---|
bool? |
google.protobuf.BoolValue |
double? |
google.protobuf.DoubleValue |
float? |
google.protobuf.FloatValue |
int? |
google.protobuf.Int32Value |
long? |
google.protobuf.Int64Value |
uint? |
google.protobuf.UInt32Value |
ulong? |
google.protobuf.UInt64Value |
string |
google.protobuf.StringValue |
ByteString |
google.protobuf.BytesValue |
Bytes
Cargas binárias são suportadas no Protobuf com o tipo de valor escalar bytes. Uma propriedade gerada em C# usa ByteString como tipo de propriedade.
Use ByteString.CopyFrom(byte[] data) para criar uma nova instância a partir de um array de bytes:
var data = await File.ReadAllBytesAsync(path);
var payload = new PayloadResponse();
payload.Data = ByteString.CopyFrom(data);
ByteString Os dados são acedidos diretamente usando ByteString.Span ou ByteString.Memory. Ou chamar ByteString.ToByteArray() para converter uma instância de volta num array de bytes:
var payload = await client.GetPayload(new PayloadRequest());
await File.WriteAllBytesAsync(path, payload.Data.ToByteArray());
Decimais
O Protobuf não suporta nativamente o tipo .NET decimal , apenas double e float. Há uma discussão em curso no projeto Protobuf sobre a possibilidade de adicionar um tipo decimal padrão aos Well-Known Tipos, com suporte da plataforma para linguagens e frameworks que o suportem. Nada foi implementado ainda.
É possível criar uma definição de mensagem para representar o decimal tipo que funciona para a serialização segura entre clientes e servidores .NET. Mas os programadores noutras plataformas teriam de compreender o formato utilizado e implementar a sua própria gestão para ele.
Criação de um tipo decimal personalizado para o Protobuf
package CustomTypes;
// Example: 12345.6789 -> { units = 12345, nanos = 678900000 }
message DecimalValue {
// Whole units part of the amount
int64 units = 1;
// Nano units of the amount (10^-9)
// Must be same sign as units
sfixed32 nanos = 2;
}
O nanos corpo representa valores de 0.999_999_999 até -0.999_999_999. Por exemplo, o decimal valor 1.5m seria representado como { units = 1, nanos = 500_000_000 }. É por isso que o nanos campo neste exemplo usa o sfixed32 tipo, que codifica de forma mais eficiente do que int32 para valores maiores. Se o units campo for negativo, o nanos campo também deve ser negativo.
Observação
Existem algoritmos adicionais disponíveis para codificar decimal valores como cadeias de bytes. O algoritmo usado por DecimalValue:
- É fácil de entender.
- Não é afetado por big-endian ou little-endian em plataformas diferentes.
- Suporta números decimais que variam de positivo
9,223,372,036,854,775,807.999999999a negativo9,223,372,036,854,775,808.999999999com uma precisão máxima de nove casas decimais, o que não corresponde ao intervalo completo de umdecimal.
A conversão entre este tipo e o tipo BCL decimal pode ser implementada em C# desta forma:
namespace CustomTypes
{
public partial class DecimalValue
{
private const decimal NanoFactor = 1_000_000_000;
public DecimalValue(long units, int nanos)
{
Units = units;
Nanos = nanos;
}
public static implicit operator decimal(CustomTypes.DecimalValue grpcDecimal)
{
return grpcDecimal.Units + grpcDecimal.Nanos / NanoFactor;
}
public static implicit operator CustomTypes.DecimalValue(decimal value)
{
var units = decimal.ToInt64(value);
var nanos = decimal.ToInt32((value - units) * NanoFactor);
return new CustomTypes.DecimalValue(units, nanos);
}
}
}
O código anterior:
- Adiciona uma classe parcial para
DecimalValue. A classe parcial é combinada com oDecimalValue, que é gerado a partir do ficheiro.proto. A classe gerada declara asUnitspropriedades eNanos. - Tem operadores implícitos para converter entre
DecimalValuee tipo BCLdecimal.
Collections
Lists
As listas em Protobuf são especificadas usando a repeated palavra-chave prefixo num campo. O exemplo seguinte mostra como criar uma lista:
message Person {
// ...
repeated string roles = 8;
}
No código gerado, os campos repeated são representados pelo tipo genérico Google.Protobuf.Collections.RepeatedField<T>.
public class Person
{
// ...
public RepeatedField<string> Roles { get; }
}
RepeatedField<T> implementa IList<T>. Assim, pode usar consultas LINQ ou converter isso para um array ou uma lista.
RepeatedField<T> As propriedades não têm um setter público. Os itens devem ser adicionados à coleção existente.
var person = new Person();
// Add one item.
person.Roles.Add("user");
// Add all items from another collection.
var roles = new [] { "admin", "manager" };
person.Roles.Add(roles);
Dicionários
O tipo .NET IDictionary<TKey,TValue> é representado em Protobuf usando map<key_type, value_type>.
message Person {
// ...
map<string, string> attributes = 9;
}
No código .NET gerado, os map campos são representados pelo Google.Protobuf.Collections.MapField<TKey, TValue> tipo genérico.
MapField<TKey, TValue> implementa IDictionary<TKey,TValue>. Tal como as repeated propriedades, map as propriedades não têm um setter público. Os itens devem ser adicionados à coleção existente.
var person = new Person();
// Add one item.
person.Attributes["created_by"] = "James";
// Add all items from another collection.
var attributes = new Dictionary<string, string>
{
["last_modified"] = DateTime.UtcNow.ToString()
};
person.Attributes.Add(attributes);
Mensagens não estruturadas e condicionais
O Protobuf é um formato de mensagens centrado no contrato. As mensagens de uma aplicação, incluindo os seus campos e tipos, devem ser especificadas em .proto ficheiros quando a aplicação é construída. O design contratual da Protobuf é excelente para impor o conteúdo das mensagens, mas pode limitar cenários em que um contrato estrito não é obrigatório:
- Mensagens com cargas úteis desconhecidas. Por exemplo, uma mensagem com um campo que pode conter qualquer mensagem.
- Mensagens condicionais. Por exemplo, uma mensagem devolvida de um serviço gRPC pode ser um resultado de sucesso ou um resultado de erro.
- Valores dinâmicos. Por exemplo, uma mensagem com um campo que contém uma coleção não estruturada de valores, semelhante ao JSON.
O Protobuf oferece funcionalidades e tipos de linguagem para suportar estes cenários.
Qualquer
O Any tipo permite usar mensagens como tipos embutidos sem ter a sua .proto definição. Para usar o Any tipo, importa any.proto.
import "google/protobuf/any.proto";
message Status {
string message = 1;
google.protobuf.Any detail = 2;
}
// Create a status with a Person message set to detail.
var status = new ErrorStatus();
status.Detail = Any.Pack(new Person { FirstName = "James" });
// Read Person message from detail.
if (status.Detail.Is(Person.Descriptor))
{
var person = status.Detail.Unpack<Person>();
// ...
}
Oneof
oneof os campos são uma característica da linguagem. O compilador trata da oneof palavra-chave quando gera a classe da mensagem. Usar oneof para especificar uma mensagem de resposta que poderia devolver um Person ou um Error pode parecer assim:
message Person {
// ...
}
message Error {
// ...
}
message ResponseMessage {
oneof result {
Error error = 1;
Person person = 2;
}
}
Os campos dentro do oneof conjunto devem ter números de campo únicos na declaração geral da mensagem.
Ao usar oneof, o código C# gerado inclui um enum que especifica qual dos campos foi definido. Podes testar o enum para ver qual campo está definido. Campos que não estão definidos retornam null ou o valor padrão, em vez de lançarem uma exceção.
var response = await client.GetPersonAsync(new RequestMessage());
switch (response.ResultCase)
{
case ResponseMessage.ResultOneofCase.Person:
HandlePerson(response.Person);
break;
case ResponseMessage.ResultOneofCase.Error:
HandleError(response.Error);
break;
default:
throw new ArgumentException("Unexpected result.");
}
Valor
O Value tipo representa um valor tipado dinamicamente. Pode ser , nullum número, uma cadeia, um booleano, um dicionário de valores (Struct), ou uma lista de valores (ValueList).
Value é um Tipo Bem Conhecido Protobuf que utiliza a função discutida anteriormente oneof. Para usar o Value tipo, importa struct.proto.
import "google/protobuf/struct.proto";
message Status {
// ...
google.protobuf.Value data = 3;
}
// Create dynamic values.
var status = new Status();
status.Data = Value.ForStruct(new Struct
{
Fields =
{
["enabled"] = Value.ForBool(true),
["metadata"] = Value.ForList(
Value.ForString("value1"),
Value.ForString("value2"))
}
});
// Read dynamic values.
switch (status.Data.KindCase)
{
case Value.KindOneofCase.StructValue:
foreach (var field in status.Data.StructValue.Fields)
{
// Read struct fields...
}
break;
// ...
}
Usar Value diretamente pode ser prolixo. Uma forma alternativa de utilização Value é o suporte incorporado do Protobuf para mapear mensagens para JSON. Os tipos JsonFormatter e JsonWriter do Protobuf podem ser usados com qualquer mensagem Protobuf.
Value é particularmente adequada para ser convertida para e a partir de JSON.
Este é o equivalente em JSON do código anterior:
// Create dynamic values from JSON.
var status = new Status();
status.Data = Value.Parser.ParseJson(@"{
""enabled"": true,
""metadata"": [ ""value1"", ""value2"" ]
}");
// Convert dynamic values to JSON.
// JSON can be read with a library like System.Text.Json or Newtonsoft.Json
var json = JsonFormatter.Default.Format(status.Data);
var document = JsonDocument.Parse(json);