.Net 6.0 İle Gelen Yenilikler

Selamlar,

Bu yazıda 9 Kasımda .Net 6.0 ile gelecek yeniliklerden bahsedeceğiz.

Öncelikle ilgili linkden, bu makalenin yazıldığı tarih itibari ile SDK 6.0.100-preview.7 versiyonu indirilir.

Daha sonra Visual Studio 2019 Preview versiyonu bu linkden indirilebilir.

“.Net 6.0 Feature” adında Console Application aşağıdaki gibi oluşturulur.

Solution üzerinde sağ click => Properties => Application sekmesinden “Target framework” “.Net 6.0” seçilir.

Ayrıca Net6.0 Feature solution Visual Studio üzerinden tıklanıp, açılan csproj dosyasında <PropertyGroup> altına “<LangVersion>preview</LangVersion> <Nullable>enable</Nullable>” eklenir.

.Net6.0 Feature.csproj:

async Main

Özellikle Microserviceslerde bolca kullanılan asenkron Main() methodu desteği ile, içinde asenkron servisler çağrılabilecektir.

System.Text.Json IAsyncEnumerable Desteği

Aslında, “IAsyncEnumerable<T>” .Net 3.0 ve C# 8 ile hayatımıza girmiş olan bir özelliktir. “IAsyncEnumerable<T>” nesneleri, System.Text.Json ile serializa ve deserialize edilebilmektedir. Ama maalesef tüm datanın gelmesi beklenmek zorunda idi. Yani stream şeklinde akan bir datanın, tamamının gelmesi bekliniyordu. Ama artık .Net 6.0 ile asenkron gelen stream datayı, beklemeye gerek yok. Geldiği kadarı anlık, parça parça Deserializa edilebilecek.

Streaming deserialization

Aşağıdaki örneklerde, dummy bir data akışı kullanılmıştır. İlgili kaynak, yerel bir makinedeki dosyalar, bir veritabanı sorgusundan dönen result ya da herhangi bir web hizmeti API çağrısının sonucu olabilir. StreamData aşağıdaki gibi tanımlanmıştır.

Gelin isterseniz ilkin, eski usul ile stream datayı Deserialize edelim. Stream’in deserialize edilmesi için, “IAsyncEnumrable<T>” tipinde geri dönen asenkron çalışan zaten var olan “JsonSerializer.DeserializeAsync()” methodunu kullanalım. Stream’den asenkron çekilen data beklenerek bir result’a atılmakta, daha sonra gelen data da asenkron olarak ekrana basılmaktadır. Buradaki esas trick, streamden çekilen datanın bir akış şeklinde ekrana basılamamasıdır. Yani non-streaming bir methoddur. Öncelikle Deserializer, tüm datayı IAsyncEnumerable şeklinde deserialize edip result’a atamadan önce, tamamını memory’e alması gerekmektedir. Yani akış bekletilmektedir. Örnek kod ve çıktısı aşağıdaki gibidir.

Aynı kodu bu sefer, “JsonSerializer.DeserializeAsyncEnumerable()” kullanarak tekrar yazalım. Aşağıda görüldüğü üzere, Deserialize işleminde ayrıca bir tip tanımlamaya gerek yoktur.

Stream akan datanın, tamamının çekilip bir result’a atanmasına gerek yoktur. Yani akış bekletilmeden, asenkron olarak ekrana basılabilmektedir.

Bu method, daha çok büyük veri akışlarında faydalıdır. Şu an için sadece JSON, dizileri desteklemektedir. Ama eminim ilerde bu destek, genişleyecektir.

Streaming serialization

Aşağıda görüldüğü gibi, System.Text.Json serialization’da “IAsyncEnumerable”‘ı desteklemektedir. IAsyncEnumerable kullanımı sadece asenkron serialize methodları desteklemektedir. “GetEvenNumbers()” asenkron methodu ile ilgili data üzerinde işlem yapılırken, gene asenkron şekilde gelen data SerializeAsync() methodu ile asenkron şekilde serialize edilmektedir.

Not:yield” sözcüğü ile geriye bulunan sonuç dönülse de, foreach döngüsünden çıkılmadan işleme devam edilmektedir.

Yukarıdaki örneğe dikkat ettiyseniz, Main methodu => “async Task Main()” şeklinde tanımlanabildiği için, içinde “JsonSerializer.SerializeAsync()” methodu çağrılabilmiştir.

System.Text.Json ile Propertylere Serialization Sırası Atama

Aşağıda görüldüğü gibi, sınıfın serialization sırası belirlenmektedir. Eskiden serialization sırası, reflection ile herhangi bir dizilime bakılmadan sıralanıyordu. Böylece, çekilen product datası istenen sırada alınıp, herhangi bir kaynağı ayrıca üzerinde işlem yapılmadan verilebilecektir.

[ModuleInitializer] Attribute

Aşağıda görüldüğü gibi, ilgili method üzerine [ModuleInitializer] tanımlaması yapılır ise, çalıştırma anında ilk çalıştırılacak method belirlenmiş olur. Örneğin aşağıda Init() methodu, Main() methodundan önce çalıştırılarak Data’nın dolması sağlanmıştır.

Index ve Range Parametreleri için Enumerable Desteği

Aşağıdaki örneğin çıktısı “8” dir. “^3” sondan 3. elemanın alınması anlamına gelmektedir.

Aşağıda görüldüğü gibi yeni syntax şekli gösterilmektedir. Benim fikrimi soracak olursanız, okunaklık RegEx’den bir tık daha iyi ama çok da anlaşılır olduğunu düşünmüyorum.

  • “..3” : Bastan baslıyarak 3 eleman al kısıtlaması.
  • “3..” : Bastan 3 atla kısıtlaması.
  • “3..5” : Bastan 3 atla 5. elemana kadar al.
  • “^3..” : Son 3 elemanı al.
  • “..^3” : Son 3 elemanı atla.
  • “^7..^3” : Sondan 3 atla basa doğru 7. elemana kadar al.

UnionBy, DistinctBy, MaxBy

Aşağıda görüldüğü gibi “UnionBy()” methodu ile farklı Plakalara ait araç listeleri birleştirilmiştir. “MaxBy()” methodu ile de, en büyük plakalı araç seçilmiştir.

DistinctBy()” methodu ile yukarıdaki sonucun aynısı alınır. Önce first ve second dizileri koşulsuz birleştirilmiş, daha sonra da DistinctBy() methodu ile  İl’e göre tekilleştirilmiştir.

FirstOrDefault/LastOrDefault/SingleOrDefault artık default parametre alabiliyor.

Aşağıda görüldüğü gibi FirstOrDefault(“Yok”), eğer kayıt yok ise default bir değer artık atanabilmektedir. Aşağıdaki 0-9 arası sayıların girildiği dictionary listede 13 ve 2 değerlerinin karşılığı çekilmeye çalışılmış ve 13. kayıt bulunamayınca “Yok” default string’i geriye dönülmüştür.

Zip()

Aşağıda görüldüğü gibi üç farklı liste, aynı anda Zip() methodu sayesinde gezilerek ekrana basılmaktadır. Geriye, tuple şeklinde bir sonuç dönülmektedir.

  • xs ilk dizide: 1-10 arası sayı kümesi alınmıştır.
  • ys ikinci dizi: Sayıların yazı karşılıklarını, “NumberList[]” içerisinden almaktadır.
  • zs üçüncü disi : İlgili sayının, çift olup olmadığının sonucunu dönmektedir.

minimal API

Aşağıdaki komut çalıştırılarak miniMal api için Web template tipinde proje ayağı kaldırılır.

Program.cs: Default gelen örnek template, aşağıdaki gibidir. Görüldüğü üzere, herşey çok sade bir şekle bürünmüş ve bir builder yapısı oluşturularak servisler bu yapı üzerinden ayağı kaldırılmıştır. Amaç, kodda ve routingde basitliktir. Örneğin default sayfa olarak “/” routing’i ile ekrana “Hello World!” yazılmıştır. Bence bu hali ile NodeJs’e baya benzer bir yapı oluşturulmuştur.

Sawagger

Şimdi sıra ile bazı servisleri ve Swagger eklentisini ekleyelim. Öncelikle Nuget’den aşağıdaki package indirilir.

  • using Microsoft.OpenApi.Models: Swagger için kullanılacak pakettir.
  • builder.Services.AddEndpointsApiExplorer(): Service’e bu Api eklenmedikçe, swagger çalışmamaktadır.
  • AddSwaggerGen: Swagger’ın çağrılma anında oluşturulacak dokümanın özellikleri, burada tanımlanır.
  • app.UseSwaggerUI(c => c.SwaggerEndpoint(“/swagger/v1/swagger.json”, “Api v1”)): Swagger’a erişilecek routing, burada tanımlanır.
  • app.MapGet(“/”, () => “Hello World!”)“: Test amaçlı çağrılacak default servis. Olmaz ise olmaz :)

  • app.MapGet(“/GetAllUsers”, (Func<List<User>>)(() => new() { new(“Bora”, “Kasmer”, 1), new(“Engin”, “Polat”, 2), new User(“Burak”, “Selim”, 3) }))“: GetAllUsers servisinin, routing çağrısıdır. Geriye, “List<User>” dönmektedir. Sade “new()” kullnımı, .Net5.0 ile gelen bir yeniliktir. Bu örnekde, test amaçlı üç User geriye dönülmektedir.

  • public record User(string Name, string Surname, int Id) {“: Gene .Net 5.0 ile gelen Recordlar, default Constructor örneği ile karşımıza çıkmaktadır.
  • app.MapGet(“/AsyncMessage”, async httpContext => await httpContext.Response.WriteAsync(“Asenkron Yazilan Mesaj”)” .Net 6.0 ile asenkron servislere örnek amaçlı yazılmış bir koddur. “await” ile ilgili sonuç arkada beklenmektedir.

Program.cs:

Not: Asenkron methodlar genelde swagger’da gözükmemektedir.

Validation

Ben validation olarak, FluentValidation’ı tercih ediyorum. Bu neden ile, öncelikle aşağıdaki kütüphane NuGet’den yüklenir.

FluentValidation’ın Dependency Injection olarak eklenebilmesi için, aşağıdaki kütüphanenin NuGet’den indirilmesi gerekir.

Program.cs: Aşağıdaki örnekde, kodun sadece validation kısımları yazılmıştır.

  • builder.Services.AddValidatorsFromAssemblyContaining<User>(lifetime: ServiceLifetime.Scoped)”: Dependency Injection ile “User” record’una Validate işlemi yapılacağı belirtilmiştir.
  • Aşağıda görüldüğü gibi, User record’u içinde Validator class’ı yazılmıştır. İlgili alanlar “RuleFor()” methodu ile validate edilmiş ve “WithMessage()” extension methodu ile, verilecek hata mesajı tanımlanmıştı. Surname alanı zorunlu ve Ad alanı en az 3 karakter olmak koşulu ile kontrol edilmektedir.

  • ValidationExtensions/ToDictionary()“: Aşağıda görüldüğü gibi hatalı propertylerin adına göre guruplanıp, bir dictionary list’in içine hata mesajları ile doldurulan ve geri dönülen bir Extension Method görülmektedir. Amaç, hatalı alanların topluca dönülmesidir.

  • app.MapPost(“SaveUserWithValidation”, (IValidator<User> validator, User user) =>“: SaveUserWithValidation() servisi parametre olarak IValidotor class’ı ve kaydedilecek User modeli parametre olarak geçilmiştir.

    • ValidationResult validationResult = validator.Validate(user)“: Validate işlemi, kaydedilecek user model için yapılır.

      • Validationdan geçemeyen modelin hatalı propertyleri, ToDictionary() Extension Methodu ile bir List olarak geri dönülmektedir. Eğer tüm propertyler validation’ı geçer ise, geriye Kaydedildi mesajı dönülmektedir.

Aşağıda görüldüğü kaydedilmek üzere girilen User modeli, validation’ı geçememiş ve hatalı propertyler, bir liste halinde json olarak geri dönülmüştür.

EntityFrameworkCore

Öncelikle, aşağıdaki kütüphaneler NuGet’den indirilir.

Program.cs / DBContext: Aşağıda görüldüğü gibi AbysProdDbContext adında bir DBContext yaratılmıştır. Entity olarak bu örnekde yukarıda görüldüğü gibi, sadece Roles tablosu alınmıştır.

  • public AbysProdDbContext(DbContextOptions<AbysProdDbContext> options) : base(options)“: AbysProdDbContext, Sql üzerindeki ABYS_PROD database’ine karşılık gelmektedir.
  • public virtual DbSet<Roles> Roles { get; set; }“: ABYS_PROD databaseindeki Roles tablosu, ilgili context’e DbSet şeklinde atanmaktadır. Aşağıda tanımlanan Roles class’ının propertyleri, DB’deki Roles tablosundaki kolonlar ile birebir örtüşmektedir.
  • protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)“: DBContext’in DB’ye bağlanacağı connection’ın, tanımlandığı methoddur.

Şimdi gelin isterseniz DB’den, Rolelerin bir listesini .NET 6.0 ile minimal Api sayesinde asenkron olarak çekelim.

Öncelikle appsettings.json‘a aşağıdaki gibi bir ConnectionStrings tanımlanır.

program.cs(DBContext): Aşağıda görüldüğü gibi öncelikle appsetting.json’daki “SQLDBConnection” string alınmıştır. Daha sonra “AbysProdDbContext” dependency injection ile ilgili connectionstring kullanılarak ayağı kaldırılmıştır.

  • app.MapGet(“/GetRolesFromDB”, async (http) =>” Asenkron olarak GetRolesFromDB methodu parametre olarak (http) almaktadır.
  • “var dbContext = http.RequestServices.GetService<AbysProdDbContext>()”: DBContext ,ilgili http üzerinden service olarak çekilmektedir.
  • “var roles = await dbContext.Roles.ToListAsync()”: Tüm roller, Roles entity’sinden asenkron olarak çekilir.
  • “await http.Response.WriteAsJsonAsync(roles)”: Geriye Roller, Json Result olarak dönülür.

Örnek sonuç ekranı.

Aşağıda benzer bir örnek olarak GetRolesFromDBByRoleName(), belirlenen bir roleName’e göre ilgili rollerin çekilmesi sağlanmıştır.

Örnek Sonuç Ekranı:

Nedense benim yazdığım örneklerde, Asenkron methodların çoğu(GetRolesFromDBByRoleName(), GetRolesFromDB(), AsyncMessage()), swagger’da gözükmemektedir.

Ayrıca EntityCore üzerinde önceden çözülmeyen .Net 3.0 ile hayatımıza giren “System.Text.Json” => “ReferenceHandler.IgnoreCycles” sorunu .Net 6 Preview 2 sürümü ile çözülmüştür. Bununla ilgili tweet’ime buradan erişebilirsiniz.

Aslında .Net 6.0 ile daha anlatılacak çok konu var. Ama ben biraz özellikle bekleyip .Net 6.0.100-preview.7 ile sonlara doğru karşımıza çıkma ihtimali yüksek olan, birçok yenilikden mümkün olduğunca bahsetmek istedim. Makale içinde C# 10 ile gelen dil yeniliklerinden ve olmaz ise olmaz .Net 6.0 ile minimal Api’den derinlemesine bahsettik. Performans üzerine özellikle EntityCore 6.0’ın 5.0’dan %70 daha hızlı olduğu, %43 daha az memoryde yer kapladığı söyleniyor. Ayrıca .Net 6.0 ile FileStream ile dosya okuma performansı, aşağıda görüldüğü muzzam bir oranda arttırılmıştır.

Geldik bir makalenin daha sonuna. .Net üzerinde tamamen cross platform çalışan bu versiyon ile, büyük yeniliklerin geldiği aşikar. Eğer ilerde güncel dil ile yazılan kodları anlamak ve onlara uyum sağlamak istiyorsanız bu treni kaçırmamanızı şiddet ile tavsiye ederim. Sadece EntityCore 5.0 kütüphanseni => 6.0’a çevirerek bile, nerde ise %70 performans kazanmanız içten bile değil. Tabi bu esnada oluşabilecek breaking changeslere dikkat etmenizde yarar var. Son olarak, dikkat ettiyseniz tüm kodları tek bir dosya altına koymamalısınız. Yoksa, büyük projelerde işin içinden çıkamassınız. Aynı NodeJs, Python veya Go dillerinde olduğu gibi, kendine özgü işleri yapan kodları, farklı dosyalarda tutun. Ben, anlaşılması ve kodların tekbir “gist” üzerinde sizler ile paylaşılabilmesi adına, aynı dosya üzerinde tuttum. Yeni bir makalede görüşmek üzere hepinize hoşçakalın.

NuGet Package’dan indirilen tüm paketler aşağıdaki gibidir.

Source Code (Program.cs):


Kaynaklar:

Herkes Görsün:

Bunlar da hoşunuza gidebilir...

3 Cevaplar

  1. Kadir İlhan dedi ki:

    Hocam; dolu dolu bir makale olmuş elinize, emeğinize sağlık. Beklediğimize değdi hakikatten. Yalnız .Net 6 Preview VS2019’da desteklenmiyor sanırım. Yazmışsınız ama resmi sitede de sadece VS2022 olarak gözükmekte, bilginiz olsun.

    • borsoft dedi ki:

      Teşekkürler Kadir,

      Destekliyor. VS2019 Preview’da nasıl destekleyeceğin, makalenin başında anlattım. Hatta bende VS2022 henüz yok :)

      İyi çalışmalar.

  2. Erdem ÖZKARA dedi ki:

    FirstOrDefault’ta varsayılan değer verememek hep canımı sıkıyordu. Bu eksiğin giderilmesi çok güzel olmuş. DefaultIfEmpty kullanmaya nedense elim hiç gitmiyordu.
    Zip() metodu ise al beni Enum extension yap diyor resmen. FieldName, Description ve Value üçlemesine yakışır :)

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir