Vytváření zpráv Protobuf pro aplikace .NET
Poznámka:
Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Upozorňující
Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v tématu .NET a .NET Core Zásady podpory. Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
Důležité
Tyto informace se týkají předběžného vydání produktu, který může být podstatně změněn před komerčním vydáním. Microsoft neposkytuje žádné záruky, výslovné ani předpokládané, týkající se zde uváděných informací.
Aktuální verzi najdete ve verzi .NET 8 tohoto článku.
James Newton-King a Mark Rendle
gRPC používá Protobuf jako svůj jazyk IDL (Interface Definition Language). Protobuf IDL je jazykově neutrální formát pro určení zpráv odeslaných a přijatých službami gRPC. Zprávy protobuf jsou definovány v .proto
souborech. Tento dokument vysvětluje, jak se koncepty Protobuf mapují na .NET.
Zprávy ve formátu protobuf
Zprávy jsou hlavním objektem přenosu dat v Protobuf. Jsou koncepčně podobné třídám .NET.
syntax = "proto3";
option csharp_namespace = "Contoso.Messages";
message Person {
int32 id = 1;
string first_name = 2;
string last_name = 3;
}
Předchozí definice zprávy určuje tři pole jako páry název-hodnota. Podobně jako vlastnosti u typů .NET má každé pole název a typ. Typ pole může být skalárním typem protobuf, například int32
nebo jinou zprávou.
Průvodce stylem Protobuf doporučuje použít underscore_separated_names
názvy polí. Nové zprávy Protobuf vytvořené pro aplikace .NET by měly postupovat podle pokynů pro styl Protobuf. Nástroje .NET automaticky generují typy .NET, které používají standardy pojmenování .NET. Například first_name
pole Protobuf generuje FirstName
vlastnost .NET.
Kromě názvu má každé pole v definici zprávy jedinečné číslo. Čísla polí se používají k identifikaci polí při serializaci zprávy do Protobuf. Serializace malého čísla je rychlejší než serializace celého názvu pole. Vzhledem k tomu, že čísla polí identifikují pole, je důležité se při jejich změně postarat. Další informace o změně zpráv Protobuf naleznete v tématu Služby správy verzí gRPC.
Když je aplikace sestavena nástroj Protobuf generuje typy .NET ze .proto
souborů. Zpráva Person
vygeneruje třídu .NET:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Další informace o zprávách Protobuf naleznete v průvodci jazykem Protobuf.
Skalární typy hodnot
Protobuf podporuje rozsah nativních skalárních hodnot. V následující tabulce jsou uvedeny všechny s odpovídajícím typem jazyka C#:
Protobuf – typ | Typ jazyka 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 |
Skalární hodnoty mají vždy výchozí hodnotu a nelze je nastavit na null
. Toto omezení zahrnuje string
a ByteString
které jsou třídy jazyka C#. string
výchozí hodnota prázdné řetězcové hodnoty a ByteString
výchozí hodnota je prázdná bajtová hodnota. Pokus o nastavení null
vyvolá chybu.
K podpoře hodnot null lze použít typy obálky s možnou hodnotou Null.
Datové typy pro datum a čas
Nativní skalární typy neposkytují hodnoty data a času, které jsou ekvivalentní hodnotě . NET , DateTimeOffsetDateTimea TimeSpan. Tyto typy lze určit pomocí některých rozšíření Protobuf Well-Known Types . Tato rozšíření poskytují podporu generování kódu a modulu runtime pro komplexní typy polí napříč podporovanými platformami.
Následující tabulka uvádí typy data a času:
Typ .NET | Známý typ 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;
}
Vygenerované vlastnosti ve třídě C# nejsou typy data a času rozhraní .NET. Vlastnosti používají třídy Timestamp
Duration
v Google.Protobuf.WellKnownTypes
oboru názvů. Tyto třídy poskytují metody pro převod na a z DateTimeOffset
, DateTime
a 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();
Poznámka:
Typ Timestamp
funguje s časy UTC. DateTimeOffset
hodnoty vždy mají posun nuly a DateTime.Kind
vlastnost je vždy DateTimeKind.Utc
.
Typy s povolenou hodnotou Null
Generování kódu Protobuf pro jazyk C# používá nativní typy, například int
pro int32
. Proto jsou hodnoty vždy zahrnuté a nemohou být null
.
Pro hodnoty, které vyžadují explicitní null
, například použití int?
v kódu jazyka C#, Protobuf dobře známé typy obsahují obálky, které jsou kompilovány do typů C# s možnou hodnotou null. Pokud je chcete použít, naimportujte wrappers.proto
je do souboru .proto
, například do následujícího kódu:
syntax = "proto3";
import "google/protobuf/wrappers.proto";
message Person {
// ...
google.protobuf.Int32Value age = 5;
}
wrappers.proto
typy nejsou vystaveny ve generovaných vlastnostech. Protobuf je automaticky mapuje na odpovídající typy s možnou hodnotou null .NET ve zprávách jazyka C#. Například google.protobuf.Int32Value
pole generuje int?
vlastnost. Vlastnosti typu odkazu jako string
a ByteString
beze změny se dají přiřadit s výjimkou null
případů bez chyby.
Následující tabulka uvádí úplný seznam typů obálky s odpovídajícím typem jazyka C#:
Typ jazyka C# | Dobře známá obálka typu |
---|---|
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 |
Přijaté
Binární datové části jsou podporovány v Protobuf s typem bytes
skalární hodnoty. Vygenerovaná vlastnost v jazyce C# se používá ByteString
jako typ vlastnosti.
Slouží ByteString.CopyFrom(byte[] data)
k vytvoření nové instance z pole bajtů:
var data = await File.ReadAllBytesAsync(path);
var payload = new PayloadResponse();
payload.Data = ByteString.CopyFrom(data);
ByteString
k datům se přistupuje přímo pomocí ByteString.Span
nebo ByteString.Memory
. Nebo volání ByteString.ToByteArray()
převodu instance zpět na bajtové pole:
var payload = await client.GetPayload(new PayloadRequest());
await File.WriteAllBytesAsync(path, payload.Data.ToByteArray());
Desetinná místa
Protobuf nativně nepodporuje typ .NET decimal
, pouze double
a float
. V projektu Protobuf probíhá diskuze o možnosti přidání standardního desítkového typu do známých typů s podporou platformy pro jazyky a architektury, které ho podporují. Zatím se nic neimplementovalo.
Je možné vytvořit definici zprávy, která představuje decimal
typ, který funguje pro bezpečné serializace mezi klienty .NET a servery. Vývojáři na jiných platformách by ale museli porozumět použitému formátu a implementovat pro něj vlastní zpracování.
Vytvoření vlastního desítkového typu pro 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;
}
Pole nanos
představuje hodnoty od 0.999_999_999
do -0.999_999_999
. Například decimal
hodnota 1.5m
by byla reprezentována jako { units = 1, nanos = 500_000_000 }
. Toto je důvod, proč nanos
pole v tomto příkladu sfixed32
používá typ, který kóduje efektivněji než int32
pro větší hodnoty. units
Pokud je pole záporné, nanos
mělo by být také záporné.
Poznámka:
Další algoritmy jsou k dispozici pro kódování decimal
hodnot jako řetězce bajtů. Algoritmus používaný:DecimalValue
- Je snadno pochopitelný.
- Není ovlivněný big-endianem nebo malým endianem na různých platformách.
- Podporuje desetinná čísla od kladných
9,223,372,036,854,775,807.999999999
po záporná9,223,372,036,854,775,808.999999999
s maximální přesností na devět desetinných míst, což není úplný rozsahdecimal
.
Převod mezi tímto typem a typem seznamu BCL decimal
může být implementován v jazyce C#takto:
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);
}
}
}
Předchozí kód:
- Přidá částečnou třídu pro
DecimalValue
. Částečná třída se zkombinuje sDecimalValue
vygenerovaným ze.proto
souboru. Vygenerovaná třída deklarujeUnits
aNanos
vlastnosti. - Obsahuje implicitní operátory pro převod mezi
DecimalValue
a typem seznamu BCLdecimal
.
Kolekce
Seznamy
Seznamy v Protobuf jsou určeny pomocí klíčového repeated
slova předpony v poli. Následující příklad ukazuje, jak vytvořit seznam:
message Person {
// ...
repeated string roles = 8;
}
Ve vygenerovaném kódu repeated
jsou pole reprezentována obecným typem Google.Protobuf.Collections.RepeatedField<T>
.
public class Person
{
// ...
public RepeatedField<string> Roles { get; }
}
RepeatedField<T>
implementuje IList<T>. Proto můžete použít dotazy LINQ nebo je převést na pole nebo seznam. RepeatedField<T>
vlastnosti nemají veřejnou setter. Položky by se měly přidat do existující kolekce.
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);
Slovníky
Typ .NET IDictionary<TKey,TValue> je reprezentován v Protobuf pomocí map<key_type, value_type>
.
message Person {
// ...
map<string, string> attributes = 9;
}
Ve vygenerovaném kódu map
.NET jsou pole reprezentována obecným typem Google.Protobuf.Collections.MapField<TKey, TValue>
. MapField<TKey, TValue>
implementuje IDictionary<TKey,TValue>. Vlastnosti, jako repeated
jsou vlastnosti, map
nemají veřejnou setter. Položky by se měly přidat do existující kolekce.
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);
Nestrukturované a podmíněné zprávy
Protobuf je formát zasílání zpráv na základě kontraktu. Zprávy aplikace, včetně polí a typů, musí být při vytváření aplikace zadány v .proto
souborech. Návrh prvního kontraktu protobuf je skvělý při vynucování obsahu zpráv, ale může omezit scénáře, kdy není vyžadován striktní kontrakt:
- Zprávy s neznámými datovými částmi Například zpráva s polem, které může obsahovat jakoukoli zprávu.
- Podmíněné zprávy. Například zpráva vrácená ze služby gRPC může být výsledkem úspěchu nebo chybovým výsledkem.
- Dynamické hodnoty. Například zpráva s polem, které obsahuje nestrukturovanou kolekci hodnot, podobně jako JSON.
Protobuf nabízí jazykové funkce a typy pro podporu těchto scénářů.
Všechny
Tento Any
typ umožňuje používat zprávy jako vložené typy bez jejich .proto
definice. Chcete-li použít Any
typ, importujte 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
pole jsou funkce jazyka. Kompilátor zpracovává oneof
klíčové slovo při generování třídy zprávy. Použití oneof
k zadání zprávy odpovědi, která by mohla vrátit Person
odpověď nebo Error
může vypadat takto:
message Person {
// ...
}
message Error {
// ...
}
message ResponseMessage {
oneof result {
Error error = 1;
Person person = 2;
}
}
Pole v oneof
sadě musí mít jedinečná čísla polí v celkové deklaraci zprávy.
Při použití oneof
obsahuje vygenerovaný kód jazyka C# výčet, který určuje, která pole byla nastavena. Výčet můžete otestovat a zjistit, které pole je nastavené. Pole, která nejsou nastavena jako návratová null
nebo výchozí hodnota, místo vyvolání výjimky.
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.");
}
Hodnota
Typ Value
představuje dynamicky zadaná hodnota. Může to být buď null
, číslo, řetězec, logická hodnota, slovník hodnot (Struct
) nebo seznam hodnot (ValueList
). Value
je známý typ Protobuf, který používá dříve diskutovanou oneof
funkci. Chcete-li použít Value
typ, importujte 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;
// ...
}
Použití Value
přímo může být podrobné. Alternativní způsob použití Value
je s integrovanou podporou Protobuf pro mapování zpráv na JSON. Protobuf JsonFormatter
a JsonWriter
typy lze použít s libovolnou zprávou Protobuf. Value
je zvláště vhodný pro převod do a z JSON.
Toto je ekvivalent JSON předchozího kódu:
// 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);