.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:
|
1 2 3 4 5 6 7 8 9 10 11 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <RootNamespace>Net6._0_Feature</RootNamespace> <LangVersion>preview</LangVersion> <Nullable>enable</Nullable> </PropertyGroup> </Project> |
async Main
Özellikle Microserviceslerde bolca kullanılan asenkron Main() methodu desteği ile, içinde asenkron servisler çağrılabilecektir.
|
1 |
static async Task Main(string[] args) |
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.
|
1 2 3 4 |
public class StreamData { public IAsyncEnumerable<int> Data { get; set; } } |
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.
|
1 2 3 4 5 6 7 8 |
using System.Text.Json; var stream = new MemoryStream(Encoding.UTF8.GetBytes(@"{""Data"":[5,13,59,48,12]}")); var result = await JsonSerializer.DeserializeAsync<StreamData>(stream); await foreach (int item in result.Data) { Console.WriteLine(item); } |
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.
|
1 2 3 4 5 6 7 |
using System.Text.Json; var stream = new MemoryStream(Encoding.UTF8.GetBytes("[5,13,59,48,12]")); await foreach (int item in JsonSerializer.DeserializeAsyncEnumerable<int>(stream)) { Console.WriteLine(item); } |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
using System.Text.Json; namespace Net6._0_Feature { class Program { static async Task Main(string[] args) { using Stream stream = Console.OpenStandardOutput(); var data = new { Data = GetEvenNumbers(new List<int>() { 1, 8, 13, 16, 22, 48, 9 }) }; await JsonSerializer.SerializeAsync(stream, data); Console.WriteLine(data); } static async IAsyncEnumerable<int> GetEvenNumbers(List<int> list) { foreach (int i in list) { if (i % 2 == 0) yield return i; } } } } |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class Product { public string Code { get; set; } // Sıra tanımlanmamıştır (default sırası 0 dır.) [JsonPropertyOrder(1)] // Diğer propertylerden sonra serialize olur. public string Name { get; set; } [JsonPropertyOrder(2)] // Name'den sora serialize olur public string Barcode { get; set; } [JsonPropertyOrder(-1)] // Sırası tanımlanan diğer propertylerden önce serialize olur. public int Id { get; set; } } |
[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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Program { static async Task Main(string[] args) { Console.WriteLine(Data); } public static string Data; [ModuleInitializer] public static void Init() { Data = "Bu static method, module içindeki diğer methodlardan önce çalıştırılmış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.
|
1 2 |
int number = Enumerable.Range(1, 10).ElementAt(^3); Console.WriteLine($"Number:{number}"); |
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.
|
1 2 3 4 5 6 7 |
var list = Enumerable.Range(1, 10); Console.WriteLine($"İlkUc:{string.Join(",",list.Take(..3))}"); Console.WriteLine($"UcAtla:{string.Join(",",list.Take(3..))}"); Console.WriteLine($"UcAtlaBesekadar:{string.Join(",", list.Take(3..5))}"); Console.WriteLine($"Son3:{string.Join(",", list.Take(^3..))}"); Console.WriteLine($"Son3uAtla:{string.Join(",", list.Take(..^3))}"); Console.WriteLine($"Son3uAtla7Yekadar:{string.Join(",", list.Take(^7..^3))}"); |
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.
|
1 2 3 4 5 6 7 8 9 10 11 |
var first = new (string CarName, int City)[] { ("Honda", 34), ("Opel", 06), ("Bmw", 40) }; var second = new (string CarName, int City)[] { ("Mercedes", 06), ("Peugeot", 06), ("Skoda", 33) }; var ListCar = first.UnionBy(second, car => car.City); var ListCar2 = first.MaxBy(car=>car.City); foreach (var car in ListCar) { Console.WriteLine($"Name:{car.CarName} Age:{car.City}"); } Console.WriteLine($"Enbüyük il Nolu Araç:{ListCar2.CarName} İl:{ListCar2.City}"); |
“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.
|
1 2 3 4 5 |
var ListCar3 = first.Union(second).DistinctBy(car => car.City); foreach (var car in ListCar3) { Console.WriteLine($"Name:{car.CarName} Age:{car.City}"); } |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
Dictionary<int, string> VsList = new(); Enumerable.Range(1, 9).ToList().ForEach(x => { VsList.Add(x, $"VS200{x}"); }); var VslistResult = VsList.Where(x => x.Key == 13).Select(x => x.Value).FirstOrDefault("Yok"); var VslistResult2 = VsList.Where(x => x.Key == 2).Select(x => x.Value).FirstOrDefault("Yok"); Console.WriteLine($"13 Listede Karşılığı:{VslistResult}"); Console.WriteLine($"2 Listede Karşılığı:{VslistResult2}"); foreach (var item in VsList) { Console.WriteLine($"VsList : {item.Value}"); } |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
var xs = Enumerable.Range(1, 10); var ys = xs.Select(x => NumberList[x]); var zs = xs.Select(x => x % 2 == 0); foreach ((int x, string y, bool z) in Enumerable.Zip(xs, ys, zs)) { Console.WriteLine($"X:{x}, Y:{y}, Z:{z}"); } public static Dictionary<int, string> NumberList = new(); [ModuleInitializer] public static void Init() { foreach (var number in Enumerable.Range(1, 10)) { switch (number) { case 1: NumberList.Add(1, "Bir"); break; case 2: NumberList.Add(2, "İki"); break; case 3: NumberList.Add(3, "Üç"); break; case 4: NumberList.Add(4, "Dört"); break; case 5: NumberList.Add(5, "Beş"); break; case 6: NumberList.Add(6, "Altı"); break; case 7: NumberList.Add(7, "Yedi"); break; case 8: NumberList.Add(8, "Sekiz"); break; case 9: NumberList.Add(9, "Dokuz"); break; case 10: NumberList.Add(10, "On"); break; default: break; } } } |
minimal API
Aşağıdaki komut çalıştırılarak miniMal api için Web template tipinde proje ayağı kaldırılır.
|
1 |
dotnet new web -o Net6.0WebApi |
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.
|
1 2 3 4 5 6 7 8 9 10 11 |
var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.MapGet("/", () => "Hello World!"); app.Run(); |
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:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Api", Version = "v1" }); }); var app = builder.Build(); if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Api v1")); } app.MapGet("/", () => "Hello World!"); app.MapGet("/GetAllUsers", (Func<List<User>>)(() => new() { new("Bora", "Kasmer", 1), new("Engin", "Polat", 2), new User("Burak", "Selim", 3) })); app.MapGet("/AsyncMessage", async httpContext => { await httpContext.Response.WriteAsync("Asenkron Yazilan Mesaj"); }); //app.Run(); await app.RunAsync(); public record User(string Name, string Surname, int Id) { public string FullName() { return this.Name + " " + Surname; } } |
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.
-
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
using FluentValidation; using FluentValidation.Results; using Microsoft.OpenApi.Models; . . . builder.Services.AddValidatorsFromAssemblyContaining<User>(lifetime: ServiceLifetime.Scoped); var app = builder.Build(); app.MapPost("SaveUserWithValidation", (IValidator<User> validator, User user) => { ValidationResult validationResult = validator.Validate(user); if (!validationResult.IsValid) { return Results.ValidationProblem(validationResult.ToDictionary()); } return Results.Ok($"User Saved After Validate Some Custom Rules:{ user.FullName()}"); }); await app.RunAsync(); public record User(string Name, string Surname, int Id) { public string FullName() { return this.Name + " " + Surname; } public class Validator : AbstractValidator<User> { public Validator() { RuleFor(x => x.Surname).NotNull().WithMessage("Soyad alanı zorunlu!"); RuleFor(x => x.Name).MinimumLength(3).WithMessage("İsim alanı 3 karakterden az olamaz!"); } } } public static class ValidationExtensions { public static IDictionary<string, string[]> ToDictionary(this ValidationResult validationResult) => validationResult.Errors .GroupBy(x => x.PropertyName) .ToDictionary( g => g.Key, g => g.Select(x => x.ErrorMessage).ToArray() ); } |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
public class AbysProdDbContext : DbContext { public AbysProdDbContext(DbContextOptions<AbysProdDbContext> options) : base(options) { } protected AbysProdDbContext() { } public virtual DbSet<Roles> Roles { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var configuration = new ConfigurationBuilder() .SetBasePath(Directory.GetCurrentDirectory()) .AddJsonFile("appsettings.json") .Build(); var connectionString = configuration.GetConnectionString("SQLDBConnection"); optionsBuilder.UseSqlServer(connectionString); } } public partial class Roles { public int Id { get; set; } public long? RoleId { get; set; } public string RoleName { get; set; } public int? GroupId { get; set; } public bool? isDeleted { get; set; } } |
Ş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.
|
1 2 3 |
"ConnectionStrings": { "SQLDBConnection": "Data Source=.;initial catalog=ABYS_PROD;Trusted_Connection=True;" }, |
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.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
using Microsoft.EntityFrameworkCore; var connectionString = builder.Configuration.GetConnectionString("SQLDBConnection"); builder.Services.AddDbContext<AbysProdDbContext>(x => x.UseSqlServer(connectionString)); app.MapGet("/GetRolesFromDB", async (http) => { var dbContext = http.RequestServices.GetService<AbysProdDbContext>(); var roles = await dbContext.Roles.ToListAsync(); await http.Response.WriteAsJsonAsync(roles); }); |
Örnek sonuç ekranı.
Aşağıda benzer bir örnek olarak GetRolesFromDBByRoleName(), belirlenen bir roleName’e göre ilgili rollerin çekilmesi sağlanmıştır.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
app.MapGet("/GetRolesFromDBByRoleName/{roleName}", async (http) => { if (!http.Request.RouteValues.TryGetValue("roleName", out var roleName)) { http.Response.StatusCode = 400; return; } var dbContext = http.RequestServices.GetService<AbysProdDbContext>(); var roles = await dbContext.Roles.Where(r=>r.RoleName.Contains(roleName.ToString())).ToListAsync(); await http.Response.WriteAsJsonAsync(roles); }); |
Ö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:
- https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-4/
- https://devblogs.microsoft.com/dotnet/announcing-net-6-preview-7/
- https://devblogs.microsoft.com/aspnet/asp-net-core-updates-in-net-6-preview-4/
- https://dotnetcoretutorials.com/2021/07/16/building-minimal-apis-in-net-6/
- https://benfoster.io/blog/mvc-to-minimal-apis-aspnet-6/
- https://dotnetthoughts.net/minimal-api-in-aspnet-core-mvc6/
- https://devblogs.microsoft.com/dotnet/file-io-improvements-in-dotnet-6/
- https://medium.com/devopsturkiye/net-6-ve-yenilikleri-1-preview-1-preview-2-6fd1b7667843


























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.
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.
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 :)
Selam hocam, async methodlar swagger da gösteriliyor. Örnek kullanım;
app.MapGet(“api/lookups/{scope}/{name}”, async ([FromRoute] string scope,[FromRoute] string name ,ILookupManager lookupManager, HttpContext context) =>
{
var result = await lookupManager.GetAsync(name, scope);
if (result == null)
{
return Results.NotFound();
}
return Results.Ok(await lookupManager.GetAsync(name, scope));
});
app.MapGet(“api/lookups/{scope}/{name}”, async ([FromRoute] string scope,[FromRoute] string name ,ILookupManager lookupManager, HttpContext context) =>
{
var result = await lookupManager.GetAsync(name, scope);
if (result == null)
{
return Results.NotFound();
}
return Results.Ok(result);
});
Selam hocam, async methodlar swagger da gösteriliyor. Örnek kullanım;
app.MapGet(“api/lookups/{scope}/{name}”, async ([FromRoute] string scope,[FromRoute] string name ,ILookupManager lookupManager, HttpContext context) =>
{
var result = await lookupManager.GetAsync(name, scope);
if (result == null)
{
return Results.NotFound();
}
return Results.Ok(result);
});
Çok güzel bir yazı olmuş ellerinize sağlık hocam. Gelişmeleri sizden öğrenmek ayrıca sevindirici :)
Çok teşekkürler Emre..
Elinize sağlık hocam
Yazı oldukça bilgilendirici ellerinize sağlık, soracağım 1-2 konu var. Teşekkürler.
1) Fluent validation tarafında .NET 6 ile gelen özelliği anlayamadım. Sanırım minimal api ile alakalı kullanımından dolayı .NET 6 dan bahsedilmiş.
2) Zip metodunda dizilerin eleman sayısı aynı olması zorunluluk değil, eleman sayısı az olana göre çalıştığını farkettim. Gerçek bir projede kullanılabilecek örnek senaryo var mıdır?