.NET uygulamaları için Protobuf iletileri oluşturma
Uyarı
ASP.NET Core'un bu sürümü artık desteklenmiyor. Daha fazla bilgi için bkz . .NET ve .NET Core Destek İlkesi. Geçerli sürüm için bu makalenin .NET 8 sürümüne bakın.
Tarafından James Newton-King ve Mark Rendle
gRPC, Arabirim Tanımlama Dili (IDL) olarak Protobuf kullanır. Protobuf IDL, gRPC hizmetleri tarafından gönderilen ve alınan iletileri belirtmek için dilden bağımsız bir biçimdir. Protobuf iletileri dosyalarda .proto
tanımlanır. Bu belgede Protobuf kavramlarının .NET ile nasıl eş olduğu açıklanmaktadır.
Protobuf iletileri
İletiler, Protobuf'taki ana veri aktarım nesnesidir. Kavramsal olarak .NET sınıflarına benzerler.
syntax = "proto3";
option csharp_namespace = "Contoso.Messages";
message Person {
int32 id = 1;
string first_name = 2;
string last_name = 3;
}
Yukarıdaki ileti tanımı, ad-değer çiftleri olarak üç alan belirtir. .NET türlerinde özellikler gibi, her alanın bir adı ve türü vardır. Alan türü bir Protobuf skaler değer türü (örneğin int32
, ) veya başka bir ileti olabilir.
Protobuf stil kılavuzu, alan adları için kullanılmasını underscore_separated_names
önerir. .NET uygulamaları için oluşturulan yeni Protobuf iletileri, Protobuf stil yönergelerini izlemelidir. .NET araçları otomatik olarak .NET adlandırma standartlarını kullanan .NET türleri oluşturur. Örneğin, bir first_name
Protobuf alanı bir FirstName
.NET özelliği oluşturur.
Bir ada ek olarak, ileti tanımındaki her alanın benzersiz bir numarası vardır. Alan numaraları, ileti Protobuf'a seri hale getirildiğinde alanları tanımlamak için kullanılır. Küçük bir sayıyı seri hale getirme, alan adının tamamını seri hale getirmekten daha hızlıdır. Alan numaraları bir alanı tanımladığından, bunları değiştirirken dikkatli olmak önemlidir. Protobuf iletilerini değiştirme hakkında daha fazla bilgi için bkz . Sürüm oluşturma gRPC hizmetleri.
Bir uygulama oluşturulduğunda, Protobuf araçları dosyalardan .proto
.NET türleri oluşturur. İleti Person
bir .NET sınıfı oluşturur:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Protobuf iletileri hakkında daha fazla bilgi için Protobuf dil kılavuzuna bakın.
Skaler Değer Türleri
Protobuf bir dizi yerel skaler değer türünü destekler. Aşağıdaki tabloda bunların tümü eşdeğer C# türüyle listelanmaktadır:
Protobuf türü | C# türü |
---|---|
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 |
Skaler değerler her zaman varsayılan bir değere sahiptir ve olarak null
ayarlanamaz. Bu kısıtlama C# sınıflarını içerir string
ve ByteString
içerir. string
varsayılan olarak boş bir dize değerine ve ByteString
varsayılan olarak boş bayt değerine sahip olur. Bunları hata oluşturacak şekilde ayarlamaya null
çalışmak.
Null değer atanabilir sarmalayıcı türleri null değerleri desteklemek için kullanılabilir.
Tarihler ve saatler
Yerel skaler türler, ile eşdeğer tarih ve saat değerleri sağlamaz. NET'in DateTimeOffset, DateTimeve TimeSpan. Bu türler, Protobuf'un İyi Bilinen Türler uzantılarından bazıları kullanılarak belirtilebilir. Bu uzantılar, desteklenen platformlarda karmaşık alan türleri için kod oluşturma ve çalışma zamanı desteği sağlar.
Aşağıdaki tabloda tarih ve saat türleri gösterilmektedir:
.NET türü | Protobuf İyi Bilinen Tür |
---|---|
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;
}
C# sınıfında oluşturulan özellikler .NET tarih ve saat türleri değildir. Özellikler, ad alanında ve Duration
sınıflarını Google.Protobuf.WellKnownTypes
kullanırTimestamp
. Bu sınıflar , DateTime
ve TimeSpan
kaynaklarından DateTimeOffset
dönüştürme yöntemleri sağlar.
// 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();
Not
Türü UTC Timestamp
saatleriyle çalışır. DateTimeOffset
değerlerin her zaman sıfır uzaklığı vardır ve DateTime.Kind
özelliği her zaman DateTimeKind.Utc
şeklindedir.
Boş değer atanabilir tipler
C# için Protobuf kod oluşturma işlemi için gibi int
int32
yerel türleri kullanır. Bu nedenle değerler her zaman dahil edilir ve olamaz null
.
C# kodunda kullanma int?
gibi açık null
gerektiren değerler için, Protobuf'un Bilinen Türleri null atanabilir C# türlerine derlenmiş sarmalayıcıları içerir. Bunları kullanmak için aşağıdaki kod gibi dosyanıza .proto
aktarınwrappers.proto
:
syntax = "proto3";
import "google/protobuf/wrappers.proto";
message Person {
// ...
google.protobuf.Int32Value age = 5;
}
wrappers.proto
türleri oluşturulan özelliklerde gösterilmez. Protobuf bunları otomatik olarak C# iletilerindeki uygun .NET null atanabilir türlere eşler. Örneğin, bir google.protobuf.Int32Value
alan bir int?
özellik oluşturur. ve ByteString
gibi string
başvuru türü özellikleri değiştirilmez, ancak null
bunlara hatasız olarak atanabilir.
Aşağıdaki tabloda, eşdeğer C# türüne sahip sarmalayıcı türlerinin tam listesi gösterilmektedir:
C# türü | İyi Bilinen Tür sarmalayıcı |
---|---|
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 |
Bayt
İkili yükleri, skaler değer türüyle bytes
Protobuf'ta desteklenir. C# dilinde oluşturulan bir özellik, özellik türü olarak kullanır ByteString
.
Bayt dizisinden yeni bir örnek oluşturmak için kullanın ByteString.CopyFrom(byte[] data)
:
var data = await File.ReadAllBytesAsync(path);
var payload = new PayloadResponse();
payload.Data = ByteString.CopyFrom(data);
ByteString
verilerine doğrudan veya ByteString.Memory
kullanılarak ByteString.Span
erişilir. Veya bir örneği yeniden bayt dizisine dönüştürmek için çağrısı ByteString.ToByteArray()
:
var payload = await client.GetPayload(new PayloadRequest());
await File.WriteAllBytesAsync(path, payload.Data.ToByteArray());
Ondalıklar
Protobuf, yalnızca double
ve float
.NET decimal
türünü yerel olarak desteklemez. Protobuf projesinde, Iyi Bilinen Türler'e standart bir ondalık tür ekleme olasılığı ve bunu destekleyen diller ve çerçeveler için platform desteğiyle devam eden bir tartışma vardır. Henüz hiçbir şey uygulanmadı.
.NET istemcileri ve sunucuları arasında güvenli serileştirme için çalışan türü temsil decimal
eden bir ileti tanımı oluşturmak mümkündür. Ancak diğer platformlardaki geliştiricilerin kullanılan biçimi anlaması ve bu biçim için kendi işlemelerini uygulaması gerekir.
Protobuf için özel ondalık tür oluşturma
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;
}
alanı nanos
ile 0.999_999_999
-0.999_999_999
olan değerleri temsil eder. Örneğin, decimal
değer 1.5m
olarak { units = 1, nanos = 500_000_000 }
temsil edilir. Bu örnekteki nanos
alanın türünü kullanmasının sfixed32
nedeni budur ve bu tür daha büyük değerlere göre int32
daha verimli bir şekilde kodlanır. units
Alan negatifse, nanos
alan da negatif olmalıdır.
Not
Değerleri bayt dizeleri olarak kodlamak decimal
için ek algoritmalar kullanılabilir. tarafından DecimalValue
kullanılan algoritma:
- Anlaşılması kolaydır.
- farklı platformlardaki büyük endian veya küçük endianlardan etkilenmez.
- Pozitiften
9,223,372,036,854,775,807.999999999
negatife9,223,372,036,854,775,808.999999999
kadar değişen ondalık sayıları, tam aralığıdecimal
olmayan dokuz ondalık basamaklık maksimum duyarlıkla destekler.
Bu tür ile BCL decimal
türü arasındaki dönüştürme C# dilinde şu şekilde uygulanabilir:
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);
}
}
}
Yukarıdaki kod:
- için
DecimalValue
kısmi bir sınıf ekler. Kısmi sınıf dosyasından.proto
oluşturulan ileDecimalValue
birleştirilir. Oluşturulan sınıf veNanos
özelliklerini bildirirUnits
. - ile BCL
decimal
türü arasındaDecimalValue
dönüştürme için örtük işleçlere sahiptir.
Koleksiyonlar
Listeler
Protobuf'taki listeler bir alanda ön ek anahtar sözcüğü kullanılarak repeated
belirtilir. Aşağıdaki örnekte liste oluşturma gösterilmektedir:
message Person {
// ...
repeated string roles = 8;
}
Oluşturulan kodda alanlar genel repeated
türle Google.Protobuf.Collections.RepeatedField<T>
temsil edilir.
public class Person
{
// ...
public RepeatedField<string> Roles { get; }
}
RepeatedField<T>
uygular IList<T>. Böylece LINQ sorgularını kullanabilir veya bir diziye veya listeye dönüştürebilirsiniz. RepeatedField<T>
özelliklerin genel ayarlayıcısı yoktur. Öğeler mevcut koleksiyona eklenmelidir.
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);
Sözlükler
.NET IDictionary<TKey,TValue> türü kullanılarak Protobuf'ta map<key_type, value_type>
temsil edilir.
message Person {
// ...
map<string, string> attributes = 9;
}
Oluşturulan .NET kodunda alanlar genel map
türle Google.Protobuf.Collections.MapField<TKey, TValue>
temsil edilir. MapField<TKey, TValue>
uygular IDictionary<TKey,TValue>. Özellikler gibi repeated
özelliklerin map
de ortak ayarlayıcısı yoktur. Öğeler mevcut koleksiyona eklenmelidir.
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);
Yapılandırılmamış ve koşullu iletiler
Protobuf, sözleşme öncelikli bir mesajlaşma biçimidir. Bir uygulamanın alanları ve türleri de dahil olmak üzere iletileri, uygulama oluşturulduğunda dosyalarda .proto
belirtilmelidir. Protobuf'un sözleşme öncelikli tasarımı, ileti içeriğini zorunlu tutma konusunda harikadır ancak katı bir sözleşmenin gerekli olmadığı senaryoları sınırlayabilir:
- Bilinmeyen yükleri olan iletiler. Örneğin, herhangi bir ileti içerebilen bir alanı olan bir ileti.
- Koşullu iletiler. Örneğin, gRPC hizmetinden döndürülen bir ileti başarılı bir sonuç veya hata sonucu olabilir.
- Dinamik değerler. Örneğin, JSON'a benzer şekilde yapılandırılmamış bir değer koleksiyonu içeren bir alan içeren bir ileti.
Protobuf, bu senaryoları desteklemek için dil özellikleri ve türleri sunar.
Tümü
türü, Any
tanımlarına .proto
sahip olmadan ekli türler olarak iletileri kullanmanıza olanak tanır. türünü kullanmak Any
için içeri aktarın 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
alanları bir dil özelliğidir. Derleyici, ileti sınıfını oneof
oluşturduğunda anahtar sözcüğünü işler. şunun gibi görünebilecek veya Error
döndürebilecek bir Person
yanıt iletisi belirtmek için komutunu kullanınoneof
:
message Person {
// ...
}
message Error {
// ...
}
message ResponseMessage {
oneof result {
Error error = 1;
Person person = 2;
}
}
Küme içindeki oneof
alanların, genel ileti bildiriminde benzersiz alan numaraları olmalıdır.
kullanılırken oneof
oluşturulan C# kodu, hangi alanların ayarlandığını belirten bir numaralandırma içerir. Hangi alanın ayarlandığını bulmak için sabit listesini test edebilirsiniz. Ayarlanmamış alanlar, özel durum oluşturma yerine dönüş null
veya varsayılan değerdir.
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.");
}
Değer
türü Value
dinamik olarak yazılan bir değeri temsil eder. Bir sayı, dize, boole, değer sözlüğü (Struct
) veya değer listesi (ValueList
) olabilirnull
. Value
, daha önce açıklanan oneof
özelliği kullanan iyi bilinen bir Protobuf türüdür. türünü kullanmak Value
için içeri aktarın 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;
// ...
}
Doğrudan kullanmak Value
ayrıntılı olabilir. Protobuf'un iletileri JSON'a eşlemeye yönelik yerleşik desteği, kullanmanın Value
alternatif bir yoludur. Protobuf'lar JsonFormatter
ve JsonWriter
türleri herhangi bir Protobuf iletisiyle kullanılabilir. Value
özellikle JSON'a ve JSON'dan dönüştürülmeye çok uygundur.
Bu, önceki kodun JSON eşdeğeridir:
// 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);
Ek kaynaklar
ASP.NET Core