.Net 5 ve Yenilikleri #2
Aylar önce .Net 5 ile ilgili gelen yenilikleri kaleme almıştım. O zamanlar sadece iki adet preview yayınlanmıştı. 🤔 Zaman ne kadar hızlı değil mi? 🤔
Geçtiğimiz günlerde .Net 5.0 yayınlandı ve büyük release gerçekleşti. 😀
Bu yazıda, yayınlanan bu sürümdeki gelen yenilikleri, performans iyileştirmelerini ve gelecek sürümler için nasıl planlamalar yapıldığını inceleyeceğiz.
Serinin bir önceki yazısını sizler için buraya bırakıyorum.
.Net 5 ile birlikte ASP.NET Core, Entity Framework Core, C# 9, gRPC ve SignalR gibi teklonojiler de güncellemeler aldı. Evet biraz uzun bir yazı olacak. 😉 Hadi başlayalım. 🛫
ASP.NET Core 5.0
Eklenen yeni özelliklerden bahsederken aşağıda listeleyeceğim adımları uygulamaya özen göstereceğim.
- Tanım?
- Neden ihtiyaç duyuldu?
- Bu özellik eklenmeden önce geliştiriciler bu işlemleri nasıl gerçekleştiriyordu?
- Eklenen bu özellik nasıl kullanılır?
DateTime Model Binding (MVC and Razor)
Model Binding
artık DateTime
için UTC
zaman dilimlerini destekliyor. Gelen request içerisinde UTC
zaman dilimi içeriyorsa Model Binding
onu ilgili UTC
aralığına bağlayacaktır.
ASP.NET Core 5.0 sürümünden önce; kullanıcı request içerisinde UTC
zaman dilimini gönderdiğinde Model Binding
bunu UTC DateTime
nesnesi yerine Local bir DateTime
nesnesine dönüştürüyordu.
Geliştiriciler kullanıcının request içerisinde gönderdiği UTC
zaman dilimine ulaşmak için Custom Model Binder
yazarak bu problemi çözüyorlardı.
Biraz kod yazalım. ⌨
Request atalım ve sonucu görelim.
https://localhost:44393/time?time=2020-11-17T02:30:04.0576719Z
Current culture: en-US
Raw querystring value is: 2020-11-17T02:30:04.0576719Z
Model-bound value is: 2020-11-17T19:30:04.0576719-07:00, Kind is: Local
Custom Model Binder
yazdıktan sonra tekrar request atıp sonucu izleyelim.
Current culture: en-US
Raw querystring value is: 2020-11-17T02:30:04.0576719Z
Model-bound value is: 2020-11-17T02:30:04.0576719Z, Kind is: UTC
ASP.NET Core 5.0 ile Custom Model Binder
yazmadan UTC DateTime
Model Binder
tarafından desteklenir. Artık bu kodları projenizden kaldırabilirsiniz🔥
Model Binding with Record Type(Mvc and Razor, C# 9)
Record
türlerini C# 9 kısmında anlatmayı düşünüyordum ancak Model Binding
desteği olduğu için kısa bir tanımını bu bölümde yapacağım.
Syntax olarak sınıflara oldukça benzer gözüken bu yapı aslında temelde çok farklı bir felsefe barındırıyor. Record yapısını sınıflardan ayıran en önemli özelliği tanımlandığı nesneyiValue Type
ve Immutable
yapmasıdır.
Record
türlerini aşağıdaki gibi kullanarak Model Binding
ve Validation
işlemlerini gerçekleştirebilirsiniz.
Open API Supports by Default (Web API)
Geliştirdiğimiz servislerde olmazsa olmaz özelliklerden biri olan Open API
desteğini ASP.NET Core 5.0 sürümünden önce kendimiz uygulamak zorundaydık. ASP.NET Core 5.0 ile default olarak Open API
desteği eklendi. Böylece projemizin ilk aşamasında daha öğretici bir şablon ile karşılaşıyoruz.
Projenizi açtığınızda Swashbuckle.AspNetCore
paketinin uygulamanıza eklendiğini göreceksiniz.
<ItemGroup>
<PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
</ItemGroup>
Ayrıca Startup.cs
içerisinde Open API
desteğinin nasıl konfigüre edildiğini görebilirsiniz.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApp1", Version = "v1" });
});
}public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json",
"WebApp1 v1"));
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Eğer oluşturduğunuz projelerde Open API
desteğinin default olarak eklenmesini istemiyorsanız aşağıda CLI komutunu kullanabilirsiniz.
dotnet new webapi --no-openapi true
Blazor WebAssembly Performans İyileştirmeleri
UI Rendering
ve Json Serialization
aşamalarında, farklı senaryolarda ortalama 3 kat daha performanslı çalışabilecek performans iyileştirmeleri yer alıyor.
Blazor File Input
Blazor File Input
ile dosya yükleme işlemleri gerçekleştirebilirsiniz. HTML tarafında alışık olduğumuz file
etiketini render eden bu component tarayıcıda bulunan .Net kodunu çalıştırır ve OnChange
olayını tetikler.
<InputFile OnChange="OnInputFileChange" multiple />
<div class="image-list">
@foreach (var imageDataUrl in imageDataUrls)
{
<img src="@imageDataUrl" />
}
</div>
@code {
IList<string> imageDataUrls = new List<string>();
async Task OnInputFileChange(InputFileChangeEventArgs e)
{
var imageFiles = e.GetMultipleFiles();
var format = "image/png";
foreach (var imageFile in imageFiles)
{
var resizedImageFile = await imageFile.RequestImageFileAsync(format, 100, 100);
var buffer = new byte[resizedImageFile.Size];
await resizedImageFile.OpenReadStream().ReadAsync(buffer);
var imageDataUrl = $"data:{format};base64,{Convert.ToBase64String(buffer)}";
imageDataUrls.Add(imageDataUrl);
}
}
}
gRPC
.Net dünyasında .Net Core 3.0 versiyonundan itibaren gittikçe popülerliğini artıran gRPC, .Net 5 ile birlikte hayatımızdan çıkan teklonojiler arasında yer alan WCFin yerini dolduruyor.
gRPC teklonojisi ile ilgili sade bir başlangıç yapmak istiyorsanız hemen tıklayın. 😉
gRPC ile yaptığım çalışmaları ve Microservice mimarisinde haberleşme tekniği olarak nasıl kullandığımı merak ediyorsanız, aylar önce yazdığım bu yazıyı okumanızı tavsiye ederim.
.Net 5 ile birlikte gRPC için oldukça büyük performans iyileştirmeleri yapıldı.
SignalR Paralel Hub
ASP.NET Core 5.0 ile birlikte SignalR
artık paralel hub çağrılarına izin verilir. Startup.cs içerisinde yapacağınız değişiklikle default olarak istemcinin birden fazla hub çağırmasına izin verebilirsiniz.
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR(options =>
{
options.MaximumParallelInvocationsPerClient = 5;
});
}
Custom Endpoint Authentication
Belirli bir deseni olan veya istediğiniz herhangi bir endpointi gelen requestlerin authentication aşamasına uğramadan geçmesi için ne yaparsanız?
Akla gelen ilk yöntem Controller üzerine [AllowAnonymous]
eklemektir. ASP.NET Core 5.0 sürümü ile Startup.cs
içerisinde istediğiniz endpointi AllowAnonymous
yapabilirsiniz.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync("Hello World!");
})
.AllowAnonymous();
});
}
Dotnet Watch
ASP.NET Core 5.0 sürümü ile komut satırında dotnet watch
komutunu çalıştırdığınızda varsayılan tarayıcı üzerinde projeniz açılır ve kodda yaptığınız değişiklikler otomatik olarak tarayıcınıza yansır böylece daha hızlı bir geliştirme ortamına sahip olursunuz.
Entity Framework Core 5.0
.NET 5 ile birlikte EF Core 5.0 sürümü de yayınlandı. Yazının bu aşamasında gelen yeni özellikleri ve bu özelliklerin bizlere sağladı avantajlar üzerinde konuşacağız.
Many-to-Many Mapping
İlişkisel veri tabanları için One-to-One
One-to-Many
Many-to-Many
gibi farklı ilişki yöntemleri vardır. Bu ilişkiler sayesinde varlıklar arasında nasıl bir iletişim olduğunu belirleriz. Eğer bu ilişkiler iyi bir şekilde belirlenmezse projelerimiz kısa zamanda gelen yeni isteklere cevap veremez hale gelebiliyor.
Eğer yeni bir projeye başlıyorsak ve mevcut bir veri tabanımız yoksa genellikle CodeFirst felsefesi ile veri tabanı modellerimizi belirleyip DbContext
içerisinde bu modellerin birbirleri ile olan ilişkilerini tanımlıyoruz.
Gelen bu özellik sayesinde Many-to-Many
ilişkilerinde DbContext
içerisinde detaylı tanımlama yapmamıza gerek kalmıyor.
public class Post
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Tag> Tags { get; set; }
}
public class Tag
{
public int Id { get; set; }
public string Text { get; set; }
public ICollection<Post> Posts { get; set; }
}
Post ve Tag varlıkları arasında Many-to-Many
ilişkisi olduğunu görüyoruz. DbContext
içerisinde modellerimizi tanımlayalım.
public class BlogContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
}
Normalde bu adımdan sonra yapılması gereken OnModelCreating
içerisinde bu iki model arasındaki ilişkiyi tanımlamaktı. Ancak yeni sürüm ile bu tanımlamayı yapmadan migration oluşturabilirsiniz.
Index Attribute
Sorgularınızin response süresinden memnun değil misiniz? O zaman üreteceğiniz çözümler arasında ilgili tabloya atılan sorguları analiz edip gerekli alanlar için index tanımlamak olacaktır. EF Core 5.0 sürümünden önce DbContext
içerisinde index tanımlamaları yapabiliyorduk. Daha kolay bir yöntem olan IndexAttribute
aşağıdaki gibi kullanılıyor.
[Index(nameof(FirstName), nameof(LastName), IsUnique = true)]
public class User
{
public int Id { get; set; }
[MaxLength(64)]
public string FirstName { get; set; }
[MaxLength(64)]
public string LastName { get; set; }
}
Filtered Include
EF Core 5.0 Preview 3 sürümünde aramıza katılan bu özellik zamanında ilgimi çekmişti ve bu özelliği içeren bir performans testi gerçekleştirmiştim. Merak ettiniz mi? O zaman buraya tıklayabilirsin. 🖱
EF Core 5.0 sürümü ile artık Include
içerisine query yazabilirsiniz.
var blogs = context.Blogs
.Include(e => e.Posts.Where(p => p.Title.Contains("Cheese")))
.ToList();
Simple Logging
Uygulamaları geliştirirken alınan hataları, uyarıları kaydetme ve sonrasında kaydettiğimiz bu kayıtlardan sonuçlar çıkartırız. Evet kısaca log tutarız. 😊
EF Core 5.0 ile birlikte üretilen SQL sorgularını, hataları görmek daha kolay. Bu sürümden önce mecburen Microsoft.Extensions.Logging.Debug
paketini indirip aşağıdaki konfigürasyonları yapmak gerekiyordu.
EF Core 5.0 sürümü ile;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
Daha hassas veriler için;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.LogTo(Console.WriteLine)
.EnableSensitiveDataLogging();
LogLevel seçebilirsiniz;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information);
Exclude Tables from Migration
Bazı senaryolarda belirleyeceğiniz modellerin Migration
aşamasına uğramamasını isteyebilirsiniz.
public class AuthorizationContext : DbContext
{
public DbSet<User> Users { get; set; }
}
public class ReportingContext : DbContext
{
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<User>().ToTable("Users", t => t.ExcludeFromMigrations());
}
}
Split Query
Benim için en önemli özelliklerden biri Split Query
EF Core 3.0 sürümü ile karşılaştıralım. EF Core 3.0 sürümünde her bir LINQ sorgusu için tek SQL sorgusu oluşturur ancak EF Core 5.0 sürümünde her bir LINQ sorgusu için birden fazla SQL sorgusu üretibilir. Peki birden fazla SQL sorgusunun üretilmesi nasıl bir avantaj sağlayacak?🤔
Ağır sorgularda özellikle Join
kullandığımız sorgularda kaç tane Join
komutunuz varsa her biri farklı bir çekirdekte çalışmak isteyecektir. Kaynaklar o an yetersiz ise sorgunuzun response süresi yükselecektir. Ancak tek bir SQL sorgusu yerine birden fazla SQL sorgusu üretip ayrı ayrı çalıştırdıktan sonra alınan cevapları merge etmek şüphesiz ağır sorgularda avantajlı olacaktır.
Basit bir Include
işlemi ile üretilen SQL sorgularını karşılaştıralım.
var artists = context.Artists
.Include(e => e.Albums).ThenInclude(e => e.Tags)
.ToList();--------------------------------------------------------------------SELECT "a"."Id", "a"."Name", "t0"."Id", "t0"."ArtistId", "t0"."Title", "t0"."Id0", "t0"."AlbumId", "t0"."Name"
FROM "Artists" AS "a"
LEFT JOIN (
SELECT "a0"."Id", "a0"."ArtistId", "a0"."Title", "t"."Id" AS "Id0", "t"."AlbumId", "t"."Name"
FROM "Album" AS "a0"
LEFT JOIN "Tag" AS "t" ON "a0"."Id" = "t"."AlbumId"
) AS "t0" ON "a"."Id" = "t0"."ArtistId"
ORDER BY "a"."Id", "t0"."Id", "t0"."Id0"
AsSplitQuery
kullanarak Split Query
özelliğini kullanabilirsiniz.
var artists = context.Artists
.AsSplitQuery()
.Include(e => e.Albums).ThenInclude(e => e.Tags)
.ToList();
--------------------------------------------------------------------
SELECT "a"."Id", "a"."Name"
FROM "Artists" AS "a"
ORDER BY "a"."Id"
SELECT "a0"."Id", "a0"."ArtistId", "a0"."Title", "a"."Id"
FROM "Artists" AS "a"
INNER JOIN "Album" AS "a0" ON "a"."Id" = "a0"."ArtistId"
ORDER BY "a"."Id", "a0"."Id"
SELECT "t"."Id", "t"."AlbumId", "t"."Name", "a"."Id", "a0"."Id"
FROM "Artists" AS "a"
INNER JOIN "Album" AS "a0" ON "a"."Id" = "a0"."ArtistId"
INNER JOIN "Tag" AS "t" ON "a0"."Id" = "t"."AlbumId"
ORDER BY "a"."Id", "a0"."Id"
SaveChanges Interception
SaveChanges metodu track edilen her varlığın durumunu günceller. EF Core 5.0 sürümünden önce DbCommandInterceptor
yazabiliyorduk. EF Core 5.0 sürümü ile artık SaveChanges için Interceptor yazabilirsiniz. Interceptor sayesinde gerçekleşen olaylara müdahele edebilirsiniz. Örneğin; SaveChanges için bir interceptor yazarak küçük bir AuditLog özelliği geliştirebilirsiniz.
public class MySaveChangesInterceptor : SaveChangesInterceptor
{
public override InterceptionResult<int> SavingChanges(
DbContextEventData eventData,
InterceptionResult<int> result)
{
Console.WriteLine($"Saving changes for {eventData.Context.Database.GetConnectionString()}");
return result;
}
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = new CancellationToken())
{
Console.WriteLine($"Saving changes asynchronously for {eventData.Context.Database.GetConnectionString()}");
return new ValueTask<InterceptionResult<int>>(result);
}
}
DbContext
initilaze edilirken AddInterceptor
ile eklemeyi unutmayın.
services.AddDbContext(b => b.UseSqlServer(connectionString) .AddInterceptors(new MySaveChangesInterceptor()));
ChangeTracker.Clear
Bir önceki maddede SaveChanges özelliğini anlatırken modellerin track edildiğinden bahsetmiştik. ChangeTracker.Clear
ile track edilen tüm modellerin durumunu temizleyebilirsiniz.
📝 Her taraf sorgularla doldu.😊 Peki bu sorguların response süreleri ne kadar?🤔 Sistem üzerinde en yavaş endpoint kim?🤔 Merak ediyorsanız sizler için bir kütüphane geliştirdim. Bu soruların hepsinin cevabı burada. 🛫
C# 9
.NET 5 ile birlikte C# yeni sürümüne ulaştı. Yazının bu aşamasında C# 9 ile birlikte hayatımıza giren özellikleri inceleyeceğiz.
Init-Only Properties
Bazı senaryolarda nesneleriniz yaratılırken minimum parametrelere ihtiyaç duyarsınız. Yani nesneniz oluşturulurken o parametrelerin kesinlikle bilinmesi gerekiyordur. Init
ile işaretlenmiş property readonly
olur.
public class Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
--------------------------------------------------------------------
Person person = new Person(){FirstName="Furkan", LastName="Gungor"};
Target-typed new
expressions
Tip belli ise neden new
sonrasında tipini tekrar yazalım?🤔Point p = new (3, 5);
Records
En çok ses getiren özelliklerden biri.
Tanım : Record içerisinde data saklayan bir yapıdır. Record syntax açısından class ile oldukça benzer ancak en temelde çok farklı özelliklere sahip olan recordlar, data yönetimi ile classlardan ayrılıyor.
Eğer bir nesnenizi record
olarak tanımlarsanız o nesneyi value type yapmış olursunuz. Yani iki record arasında eşitlik gibi işlemler yapmaya çalıştığınızda nesnelerin referanslarına değil, içerdiği datalara göre bakılacaktır.
With Expressions
Bir nesnenin bazı parametrelerini değiştirip kalan kısmını kopyalayarak yeniden oluşturmanızı sağlayan bir keyword.
public record Person
{
public string FirstName { get; init; }
public string LastName { get; init; }
}
--------------------------------------------------------------------
Person person = new Person(){FirstName="Furkan", LastName="Gungor"};
Person person2 = person with {FirstName="Ali"};Console.WriteLine($"{person2.FirstName}\t{person2.LastName}");Result;
Ali Gungor
Parameter Null Checking
Parametrenin null olması.😢 Sürekli o verinin null olup olmadığı kontrol edilip sonra işlemleri yapmak gerek. C# 9 ile bu işi biraz daha basit hale getirdiler.
public void SetCustomerName(string customerName)
{
if(string.IsNullOrEmpty(customerName))
throw new ArgumentNullException(nameof(Customer));
/// Other codes.
}With C# 9
public void SetCustomerName(string customerName!) => throw new ArgumentNullException(nameof(Customer));
{
/// Other codes.
}
.NET 5 ile hayatımıza giren yenilikleri anlatmak benim için öğretici oldu. Tekrar araştırıp bilgilerimi tazeledim ve ortaya bu yazı çıktı.✌
Hayatınız boyunca refactor etmeye doyamayacağınız sistemler üzerinde çalışmanız dileğiyle…
Happy Coding. 😃