Membuat pesan Protobuf untuk aplikasi .NET
Catatan
Ini bukan versi terbaru dari artikel ini. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Peringatan
Versi ASP.NET Core ini tidak lagi didukung. Untuk informasi selengkapnya, lihat Kebijakan Dukungan .NET dan .NET Core. Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Penting
Informasi ini berkaitan dengan produk pra-rilis yang mungkin dimodifikasi secara substansial sebelum dirilis secara komersial. Microsoft tidak memberikan jaminan, tersirat maupun tersurat, sehubungan dengan informasi yang diberikan di sini.
Untuk rilis saat ini, lihat versi .NET 8 dari artikel ini.
Oleh James Newton-King dan Mark Rendle
gRPC menggunakan Protobuf sebagai Bahasa Definisi Antarmuka (IDL). Protobuf IDL adalah format netral bahasa untuk menentukan pesan yang dikirim dan diterima oleh layanan gRPC. Pesan Protobuf didefinisikan dalam .proto
file. Dokumen ini menjelaskan bagaimana konsep Protobuf memetakan ke .NET.
Pesan protobuf
Pesan adalah objek transfer data utama di Protobuf. Mereka secara konseptual mirip dengan kelas .NET.
syntax = "proto3";
option csharp_namespace = "Contoso.Messages";
message Person {
int32 id = 1;
string first_name = 2;
string last_name = 3;
}
Definisi pesan sebelumnya menentukan tiga bidang sebagai pasangan nama-nilai. Seperti properti pada jenis .NET, setiap bidang memiliki nama dan jenis. Jenis bidang dapat berupa jenis nilai skalar Protobuf, misalnya int32
, atau pesan lain.
Panduan gaya Protobuf merekomendasikan penggunaan underscore_separated_names
untuk nama bidang. Pesan Protobuf baru yang dibuat untuk aplikasi .NET harus mengikuti panduan gaya Protobuf. Alat .NET secara otomatis menghasilkan jenis .NET yang menggunakan standar penamaan .NET. Misalnya, first_name
bidang Protobuf menghasilkan FirstName
properti .NET.
Selain nama, setiap bidang dalam definisi pesan memiliki angka unik. Nomor bidang digunakan untuk mengidentifikasi bidang saat pesan diserialisasikan ke Protobuf. Menserialisasikan angka kecil lebih cepat daripada menserialisasikan seluruh nama bidang. Karena nomor bidang mengidentifikasi bidang, penting untuk berhati-hati saat mengubahnya. Untuk informasi selengkapnya tentang mengubah pesan Protobuf, lihat Penerapan versi layanan gRPC.
Saat aplikasi dibangun, alat Protobuf menghasilkan jenis .NET dari .proto
file. Pesan Person
menghasilkan kelas .NET:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Untuk informasi selengkapnya tentang pesan Protobuf, lihat panduan bahasa Protobuf.
Jenis Nilai Skalar
Protobuf mendukung berbagai jenis nilai skalar asli. Tabel berikut mencantumkan semuanya dengan jenis C# yang setara:
Jenis Protobuf | Jenis 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 |
Nilai skalar selalu memiliki nilai default dan tidak dapat diatur ke null
. Batasan ini termasuk string
dan ByteString
yang merupakan kelas C#. string
default ke nilai string kosong dan ByteString
default ke nilai byte kosong. Mencoba mengaturnya untuk null
melemparkan kesalahan.
Jenis pembungkus null dapat digunakan untuk mendukung nilai null.
Tanggal dan waktu
Jenis skalar asli tidak menyediakan nilai tanggal dan waktu, setara dengan . NET, DateTimeOffset, DateTimedan TimeSpan. Jenis ini dapat ditentukan dengan menggunakan beberapa ekstensi Jenis Terkenal Protobuf. Ekstensi ini menyediakan pembuatan kode dan dukungan runtime untuk jenis bidang yang kompleks di seluruh platform yang didukung.
Tabel berikut ini memperlihatkan jenis tanggal dan waktu:
Jenis .NET | Tipe Terkenal 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;
}
Properti yang dihasilkan di kelas C# bukan jenis tanggal dan waktu .NET. Properti menggunakan kelas Timestamp
dan Duration
di namespace layanan Google.Protobuf.WellKnownTypes
. Kelas-kelas ini menyediakan metode untuk mengonversi ke dan dari DateTimeOffset
, DateTime
, dan 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();
Catatan
Jenis Timestamp
berfungsi dengan waktu UTC. Nilai DateTimeOffset
selalu memiliki offset nol, dan properti DateTime.Kind
selalu DateTimeKind.Utc
.
Jenis yang dapat diubah ke null
Pembuatan kode Protobuf untuk C# menggunakan jenis asli, seperti int
untuk int32
. Jadi nilai selalu disertakan dan tidak boleh null
.
Untuk nilai yang memerlukan eksplisit null
, seperti menggunakan int?
dalam kode C#, Jenis Terkenal Protobuf menyertakan pembungkus yang dikompilasi ke jenis C# nullable. Untuk menggunakannya, impor wrappers.proto
ke file Anda .proto
, seperti kode berikut:
syntax = "proto3";
import "google/protobuf/wrappers.proto";
message Person {
// ...
google.protobuf.Int32Value age = 5;
}
wrappers.proto
jenis tidak diekspos dalam properti yang dihasilkan. Protobuf secara otomatis memetakannya ke jenis .NET nullable yang sesuai dalam pesan C#. Misalnya, google.protobuf.Int32Value
bidang menghasilkan int?
properti. Properti jenis referensi seperti string
dan ByteString
tidak berubah kecuali null
dapat ditetapkan kepada mereka tanpa kesalahan.
Tabel berikut ini memperlihatkan daftar lengkap jenis pembungkus dengan jenis C# yang setara:
Jenis C# | Pembungkus Jenis Terkenal |
---|---|
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 |
Byte
Payload biner didukung di Protobuf dengan bytes
jenis nilai skalar. Properti yang dihasilkan di C# menggunakan ByteString
sebagai jenis properti.
Gunakan ByteString.CopyFrom(byte[] data)
untuk membuat instans baru dari array byte:
var data = await File.ReadAllBytesAsync(path);
var payload = new PayloadResponse();
payload.Data = ByteString.CopyFrom(data);
ByteString
data diakses langsung menggunakan ByteString.Span
atau ByteString.Memory
. Atau panggil ByteString.ToByteArray()
untuk mengonversi instans kembali menjadi array byte:
var payload = await client.GetPayload(new PayloadRequest());
await File.WriteAllBytesAsync(path, payload.Data.ToByteArray());
Desimal
Protobuf tidak secara asli mendukung jenis .NET decimal
, hanya double
dan float
. Ada diskusi berkelanjutan dalam proyek Protobuf tentang kemungkinan menambahkan jenis desimal standar ke Jenis Terkenal, dengan dukungan platform untuk bahasa dan kerangka kerja yang mendukungnya. Belum ada yang diimplementasikan.
Dimungkinkan untuk membuat definisi pesan untuk mewakili decimal
jenis yang berfungsi untuk serialisasi yang aman antara klien .NET dan server. Tetapi pengembang di platform lain harus memahami format yang digunakan dan menerapkan penanganan mereka sendiri untuk itu.
Membuat jenis desimal kustom untuk 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;
}
Bidang nanos
mewakili nilai dari 0.999_999_999
ke -0.999_999_999
. Misalnya, nilai decimal
1.5m
akan diwakili sebagai { units = 1, nanos = 500_000_000 }
. Inilah sebabnya mengapa bidang nanos
dalam contoh ini menggunakan jenis sfixed32
, yang dikodekan lebih efisien daripada int32
untuk nilai yang lebih besar. Jika bidang units
negatif, bidang nanos
juga harus negatif.
Catatan
Algoritma tambahan tersedia untuk mengodekan decimal
nilai sebagai string byte. Algoritma yang digunakan oleh DecimalValue
:
- Mudah dimengerti.
- Tidak terpengaruh oleh big-endian atau little-endian pada platform yang berbeda.
- Mendukung angka desimal mulai dari positif
9,223,372,036,854,775,807.999999999
hingga negatif9,223,372,036,854,775,808.999999999
dengan presisi maksimum sembilan tempat desimal, yang bukan rentang penuh daridecimal
.
Konversi antara jenis ini dan jenis decimal
BCL mungkin diimplementasikan dalam C# seperti ini:
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);
}
}
}
Kode sebelumnya:
- Menambahkan kelas parsial untuk
DecimalValue
. Kelas parsial dikombinasikan denganDecimalValue
yang dihasilkan dari.proto
file. Kelas yang dihasilkan mendeklarasikanUnits
properti danNanos
. - Memiliki operator implisit untuk mengonversi antara
DecimalValue
dan jenis BCLdecimal
.
Koleksi
Daftar
Daftar dalam Protobuf ditentukan dengan menggunakan kata kunci awalan repeated
pada bidang. Contoh berikut ini memperlihatkan cara membuat daftar:
message Person {
// ...
repeated string roles = 8;
}
Dalam kode yang dihasilkan, repeated
bidang diwakili oleh Google.Protobuf.Collections.RepeatedField<T>
jenis generik.
public class Person
{
// ...
public RepeatedField<string> Roles { get; }
}
RepeatedField<T>
penerapan IList<T>. Jadi Anda dapat menggunakan kueri LINQ atau mengonversinya menjadi array atau daftar. RepeatedField<T>
properti tidak memiliki setter publik. Item harus ditambahkan ke koleksi yang ada.
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);
Kamus
Jenis .NET IDictionary<TKey,TValue> diwakili dalam Protobuf menggunakan map<key_type, value_type>
.
message Person {
// ...
map<string, string> attributes = 9;
}
Dalam kode .NET yang dihasilkan, map
bidang diwakili oleh Google.Protobuf.Collections.MapField<TKey, TValue>
jenis generik. MapField<TKey, TValue>
penerapan IDictionary<TKey,TValue>. Seperti repeated
properti, map
properti tidak memiliki setter publik. Item harus ditambahkan ke koleksi yang ada.
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);
Pesan tidak terstruktur dan bersyarat
Protobuf adalah format olahpesan pertama kontrak. Pesan aplikasi, termasuk bidang dan jenisnya, harus ditentukan dalam .proto
file saat aplikasi dibuat. Desain pertama kontrak Protobuf sangat bagus dalam memberlakukan konten pesan tetapi dapat membatasi skenario di mana kontrak yang ketat tidak diperlukan:
- Pesan dengan payload yang tidak diketahui. Misalnya, pesan dengan bidang yang dapat berisi pesan apa pun.
- Pesan bersyar. Misalnya, pesan yang dikembalikan dari layanan gRPC mungkin merupakan hasil yang berhasil atau hasil kesalahan.
- Nilai dinamis. Misalnya, pesan dengan bidang yang berisi kumpulan nilai yang tidak terstruktur, mirip dengan JSON.
Protobuf menawarkan fitur dan jenis bahasa untuk mendukung skenario ini.
Mana pun
Jenis ini Any
memungkinkan Anda menggunakan pesan sebagai jenis yang disematkan tanpa memiliki definisinya .proto
. Untuk menggunakan jenis , Any
impor 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
bidang adalah fitur bahasa. Pengkompilasi menangani oneof
kata kunci saat menghasilkan kelas pesan. Menggunakan oneof
untuk menentukan pesan respons yang dapat mengembalikan Person
atau Error
mungkin terlihat seperti ini:
message Person {
// ...
}
message Error {
// ...
}
message ResponseMessage {
oneof result {
Error error = 1;
Person person = 2;
}
}
Bidang dalam set oneof
harus memiliki nomor bidang unik dalam keseluruhan deklarasi pesan.
Saat menggunakan oneof
, kode C# yang dihasilkan menyertakan enum yang menentukan bidang mana yang telah diatur. Anda dapat menguji enum untuk menemukan bidang mana yang diatur. Bidang yang tidak diatur mengembalikan null
atau nilai default, daripada melemparkan pengecualian.
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.");
}
Nilai
Jenis mewakili Value
nilai yang di ketik secara dinamis. Ini bisa berupa null
, angka, string, boolean, kamus nilai (Struct
), atau daftar nilai (ValueList
). Value
adalah Jenis Terkenal Protobuf yang menggunakan fitur yang dibahas oneof
sebelumnya. Untuk menggunakan jenis , Value
impor 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;
// ...
}
Menggunakan Value
secara langsung bisa verbose. Cara alternatif untuk digunakan Value
adalah dengan dukungan bawaan Protobuf untuk memetakan pesan ke JSON. Jenis dan JsonWriter
Protobuf JsonFormatter
dapat digunakan dengan pesan Protobuf apa pun. Value
sangat cocok untuk dikonversi ke dan dari JSON.
Ini adalah JSON yang setara dengan kode sebelumnya:
// 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);
Sumber Daya Tambahan:
ASP.NET Core