.NET uygulamaları için Protobuf iletileri oluşturma
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şlenmiş olduğu açıkmektedir.
Protobuf iletileri
İletiler, Protobuf'ta 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ı, üç alanı ad-değer çifti olarak belirtir. .NET türlerinde özellikler gibi her alanın bir adı ve türü vardır. Alan türü Protobuf skalerdeğer türü ( gibi) veya başka bir int32 ileti olabilir.
Protobuf stil kılavuzu, alan adları underscore_separated_names için kullanılması önerilir. .NET uygulamaları için oluşturulan yeni Protobuf iletileri Protobuf stili yönergelerine uygun olmalıdır. .NET aracı otomatik olarak .NET adlandırma standartlarını kullanan .NET türleri üretir. Örneğin, first_name bir Protobuf alanı bir FirstName .NET özelliği üretir.
Bir ada ek olarak, ileti tanımında yer alan her alanın benzersiz bir numarası vardır. Alan numaraları, ileti Protobuf'a seri hale getirilecek alanları tanımlamak için kullanılır. Küçük bir sayına seri hale getirme, alan adının tamamını seri hale getirmeden daha hızlıdır. Alan numaraları bir alanı tanımlayca, bunları değiştirirken dikkat etmek önemlidir. Protobuf iletilerini değiştirme hakkında daha fazla bilgi için bkz. gRPC hizmetlerinin sürümünü oluşturma .
Bir uygulama yerleşik olduğunda Protobuf aracı dosyalardan .NET türleri .proto üretir. İleti Person bir .NET sınıfı üretir:
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ürlerini destekler. Aşağıdaki tabloda bunların hepsi eşdeğer C# türüyle listelemektedir:
| 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 değere sahiptir ve olarak ayar değerli null değildir. Bu kısıtlama string ByteString C# sınıflarını içerir. string varsayılan olarak boş bir dize değeri, ByteString varsayılan olarak da boş bir bayt değeri kullanılır. Bunları bir hataya neden null olacak şekilde ayarlamaya çalışıldı.
Null değere sahip 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 , DateTime ve TimeSpan . Bu türler Protobuf'un Bilinen Türler uzantılarının 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österir:
| .NET türü | Protobuf Well-Known 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ı Timestamp içinde Duration ve sınıflarını Google.Protobuf.WellKnownTypes kullanır. Bu sınıflar , ve sınıflarından ve sınıflarından dönüştürme DateTimeOffset DateTime yöntemleri TimeSpan 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ü Timestamp UTC saatleriyle çalışır. DateTimeOffset değerler her zaman sıfır uzaklığına sahiptir ve özelliği DateTime.Kind her zaman DateTimeKind.Utc olur.
Null atanabilir türler
C# için Protobuf kod oluşturma, için gibi yerel türleri int int32 kullanır. Bu nedenle değerler her zaman dahil edilir ve olarak dahil null değildir.
C# kodunda kullanma gibi açık gerektiren değerler null için, Protobuf'un Well-Known Türleri null değere sahip C# türlerine derlenmiş int? sarmalayıcıları içerir. Bunları kullanmak için aşağıdaki wrappers.proto kod .proto gibi dosyanıza aktarın:
syntax = "proto3"
import "google/protobuf/wrappers.proto"
message Person {
// ...
google.protobuf.Int32Value age = 5;
}
wrappers.proto türleri, oluşturulan özelliklerde açığa çıkar. Protobuf bunları C# iletilerinde uygun .NET null atılabilir türlerine otomatik olarak eşler. Örneğin, bir google.protobuf.Int32Value alan bir özellik int? üretir. ve gibi başvuru string ByteString türü özellikleri değiştirilmez ancak hata olmadan bu null özelliklere atanabilir.
Aşağıdaki tabloda eşdeğer C# türüne sahip sarmalayıcı türlerinin tam listesi yer alır:
| C# türü | Well-Known 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ük, skaler değer türüyle bytes Protobuf'ta değerlidir. C# içinde oluşturulan bir özellik, ByteString özellik türü olarak kullanır.
Bir ByteString.CopyFrom(byte[] data) byte diziden yeni bir örnek oluşturmak için kullanın:
var data = await File.ReadAllBytesAsync(path);
var payload = new PayloadResponse();
payload.Data = ByteString.CopyFrom(data);
ByteString verilerine doğrudan veya kullanılarak ByteString.Span ByteString.Memory erişilir. Veya bir ByteString.ToByteArray() örneği bir byte dizisine geri dönüştürmek için çağrısı da kullanılabilir:
var payload = await client.GetPayload(new PayloadRequest());
await File.WriteAllBytesAsync(path, payload.Data.ToByteArray());
Ondalıklar
Protobuf yalnızca ve .NET türünü decimal yerel olarak double float desteklemez. Protobuf projesinde, destekleyen diller ve çerçeveler için platform desteğiyle birlikte, Well-Known Türleri'ne standart ondalık tür ekleme olasılığı hakkında 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 eden decimal bir ileti tanımı oluşturmak mümkündür. Ancak diğer platformlarda yer alan geliştiricilerin kullanılan biçimi anları ve bunun için kendi işlemelerini uygulamaları 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 değerlerini temsil 0.999_999_999 -0.999_999_999 eder. Örneğin, decimal değeri 1.5m olarak temsil { units = 1, nanos = 500_000_000 } edilen. Bu örnekteki nanos alan, daha büyük değerlere göre daha verimli bir şekilde sfixed32 kodlayan int32 türünü kullanmanın nedenidir. Alan units negatifse alanın nanos da negatif olması gerekir.
Not
Değerleri bayt dizeleri olarak kodlamak için birden fazla algoritma daha vardır, ancak bu ileti, herhangi decimal bir iletiden daha kolay anlaşılır. Değerler, farklı platformlarda big-endian veya little-endian tarafından etkilenmez.
Bu tür ile BCL türü arasındaki decimal dönüştürme C# içinde şu şekilde uygulanarak dönüştürülebilir:
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 long Units { get; }
public int Nanos { get; }
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);
}
}
}
Koleksiyonlar
Listeler
Protobuf'daki listeler, bir alanda repeated prefix anahtar sözcüğü kullanılarak belirtilir. Aşağıdaki örnekte, bir listenin nasıl oluşturularak ilgili bilgiler ve bilgiler ve bilgiler ve bilgiler yer alelade bir şekilde liste oluşturularak ekleyebilirsiniz:
message Person {
// ...
repeated string roles = 8;
}
Oluşturulan kodda alanlar repeated genel türle Google.Protobuf.Collections.RepeatedField<T> temsil edildi.
public class Person
{
// ...
public RepeatedField<string> Roles { get; }
}
RepeatedField<T> , 'i uygulamaya IList<T> almaktadır. Bu nedenle LINQ sorgularını kullanabilir veya bunu bir diziye veya listeye dönüştürebilirsiniz. RepeatedField<T> özelliklerin ortak ayarıcısı yok. Öğeler mevcut koleksiyona eklenmiştir.
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 temsil map<key_type, value_type> edildi.
message Person {
// ...
map<string, string> attributes = 9;
}
Oluşturulan .NET kodunda map alanlar genel türle temsil Google.Protobuf.Collections.MapField<TKey, TValue> edildi. MapField<TKey, TValue> , 'i uygulamaya IDictionary<TKey,TValue> almaktadır. Özellikler repeated gibi özelliklerin de ortak map ayarıcısı yok. Öğeler mevcut koleksiyona eklenmiştir.
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şmenin ilk mesajlaşma biçimidir. Bir uygulamanın alanları ve türleri de dahil olmak üzere iletileri, uygulama .proto yerleşik olduğunda dosyalarda belirtilmelidir. Protobuf'un sözleşmenin ilk tasarımı ileti içeriğinin zorlanma konusunda harikadır ancak katı bir sözleşmenin gerekli olduğu senaryoları sınırlandırabilirsiniz:
- Bilinmeyen yüklere sahip iletiler. Örneğin, herhangi bir ileti içerebilen bir alan içeren bir ileti.
- Koşullu iletiler. Örneğin, bir gRPC hizmetinden döndürülen bir ileti, başarılı bir sonuç veya hata sonucu olabilir.
- Dinamik değerler. Örneğin, JSON ile benzer şekilde yapılandırılmamış değer koleksiyonu içeren bir alan içeren bir ileti.
Prototip, bu senaryoları desteklemek için dil özellikleri ve türleri sunar.
Herhangi biri
AnyTürü, kendi tanımına sahip olmayan iletileri katıştırılmış tür olarak kullanmanıza olanak tanır .proto . Türünü kullanmak için Any 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, oneof ileti sınıfını oluşturduğunda anahtar sözcüğünü işler. oneofYa da döndüren bir yanıt iletisi belirtmek için kullanarak Person veya Error şöyle görünebilir:
message Person {
// ...
}
message Error {
// ...
}
message ResponseMessage {
oneof result {
Error error = 1;
Person person = 2;
}
}
Küme içindeki alanlar, oneof genel ileti bildiriminde benzersiz alan numaralarına sahip olmalıdır.
Kullanırken oneof , oluşturulan C# kodu, alanların hangisinin ayarlandığını belirten bir sabit listesi içerir. Hangi alanın ayarlandığını bulmak için sabit listesini test edebilirsiniz. Ayarlı olmayan alanlar null , özel durum oluşturmak yerine varsayılan değer döndürür.
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
ValueTür, dinamik olarak yazılmış bir değeri temsil eder. Bu, null bir sayı, bir dize, Boole değeri, değerlerin bir sözlüğü ( Struct ) veya bir değerler listesi ( ValueList ) olabilir. Value , daha önce tartışılan özelliği kullanan bir prototip Well-Known türüdür oneof . Türünü kullanmak için Value 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.FromStruct(new Struct
{
Fields =
{
["enabled"] = Value.ForBoolean(true),
["metadata"] = Value.ForList(
Value.FromString("value1"),
Value.FromString("value2"))
}
});
// Read dynamic values.
switch (status.Data.KindCase)
{
case Value.KindOneofCase.StructValue:
foreach (var field in status.Data.StructValue.Fields)
{
// Read struct fields...
}
break;
// ...
}
ValueDoğrudan kullanımı ayrıntılı olabilir. Kullanmanın alternatif bir yolu Value , protoda ILETILERI JSON ile eşlemek için yerleşik destek sağlar. Prototip JsonFormatter ve JsonWriter türler herhangi bir prototiple ileti ile kullanılabilir. Value JSON 'dan ve bu sunucudan dönüştürülmek için özellikle idealdir.
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);