Aracılığıyla paylaş


.NET uygulamaları için Protobuf iletileri oluşturma

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 nullayarlanamaz. 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 , DateTimeve TimeSpankaynaklarından DateTimeOffsetdö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();

Dekont

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 intint32yerel türleri kullanır. Bu nedenle değerler her zaman dahil edilir ve olamaz null.

C# kodunda kullanma int? gibi açık nullgerektiren 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);

ByteStringverilerine doğrudan veya ByteString.Memorykullanı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());

On -da -lık

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_999olan 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.

Dekont

Değerleri bayt dizeleri olarak kodlamak decimal için ek algoritmalar kullanılabilir. tarafından DecimalValuekullanı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 negatife 9,223,372,036,854,775,808.999999999 kadar değişen ondalık sayıları, tam aralığı decimalolmayan 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);
        }
    }
}

Önceki kod:

  • için DecimalValuekısmi bir sınıf ekler. Kısmi sınıf dosyasından .proto oluşturulan ile DecimalValue birleştirilir. Oluşturulan sınıf ve Nanos özelliklerini bildirirUnits.
  • ile BCL decimal türü arasında DecimalValue 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, on gibi JSyapı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 oneofoluş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 ON'a ve ON'dan JSdö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