Yazar: admin_gokhan

  • ASP.NET Core: Middleware Nedir ve Kullanım Örneği

    ASP.NET Core projelerimizde middleware tanımlamak önemli bir özellik bu özellik sayesinde http isteklerini filtreleyebilir, tüm isteklerden önce veya sonra kod çalıştırabiliriz.

    Middleware Nedir?

    Middleware, bir web uygulamasında isteklerin uygulamaya ulaşmadan önce veya sonra ele alınmasını ve gerekli durumlarda gerekli işlemlerin yapılmasını sağlayan sınıflardır. ASP.NET Core Web API veya MVC Rozor Pages projelerinde bir pipeline( işleme hattı ) ile işlenir ve middleware kullanımı çok kolaydır.

    Hangi Durumlarda Kullanılır?

    Middleware kullanım senaryolarından bazıları şu şekilde:

    Loglama: isteklerden önce veya sonra istek ve isteği oluşturan kullanıcı hakkında log kayıtları oluşturabilirsiniz.

    Exception Handling: Hataları yakalamak için yapılar kurabilirsiniz. İsteğe bağlı olarak loglama yada belirli hata mesajları gösterme veya yönlendirmeler yapabilirsiniz.

    CORS Ayarları: Tarayıcıdan gelen cross orgin yani farklı kaynaklardan gelen istekleri kısıtlayabilirsiniz.

    Yetki / Yetkilendirme Kontrolü: Authorizon veya Authentication yapıları kurarak kullanıcıyı ve yetkilerini kontrol edebilirsiniz.

    Performans Ölçümü: İsteğin öncesinde ve sonrasında kod çalıştırabildiğimizi söylemiştik, bu sayede uygulamamızın bir isteği ne kadar sürede işlediğini ve sonucu hazırladığını ölçebiliriz.

    Rate Limiting, IP veya Lokasyon Kısıtlama: Bir kullanıcının hangi sıklıkla istek atabileceğini limitleyebilir veya uygulamaya erişimini tamamen kısıtlayabilirsiniz.

    Liste daha uzar ama genel olarak ne yapılabileceğine değinmiş olduk. Şimdi örnek bir WEB API projesinde middleware ekleyelim.

    Basit Middleware Örneği

    Kullanıcının en fazla dakikada 100 kez istek atabilmesini sağlayalım.

    Middleware classlarına otomatik olarak bir parametre ekleniyor:
    RequestDelegate next

    middleware’i işleme alırken bu parametre üzerinden mevcut isteği tanıyabiliyoruz. Hemen örneğimize geçelim.

    Middlewares/RateLimitingMiddleware.cs

    C#
    // Middlewares/RateLimitingMiddleware.cs
    
    using Microsoft.AspNetCore.Http;
    using System.Collections.Concurrent;
    using System.Net;
    
    public class RateLimitingMiddleware
    {
        private readonly RequestDelegate _next;
        private static readonly ConcurrentDictionary<string, List<DateTime>> _requestLog = new();
    
        private const int LIMIT = 100; // max istek
        private static readonly TimeSpan WINDOW = TimeSpan.FromMinutes(1); // süre
    
        public RateLimitingMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public async Task InvokeAsync(HttpContext context)
        {
            string ip = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
    
            var now = DateTime.UtcNow;
    
            var requests = _requestLog.GetOrAdd(ip, _ => new List<DateTime>());
    
            lock (requests)
            {
                // Eski kayıtları temizle
                requests.RemoveAll(r => r < now - WINDOW);
    
                if (requests.Count >= LIMIT)
                {
                    context.Response.StatusCode = (int)HttpStatusCode.TooManyRequests;
                    context.Response.Headers["Retry-After"] = "60";
                    return;
                }
    
                requests.Add(now);
            }
    
            await _next(context);
        }
    }
    

    Bu örnekde dikkat etmemiz gereken bir nokta var o da next isimli bir sonraki request’i temsil eden ve context isimli mevcut request’in objenin sınıfımıza inject edilmiş olması.

    C#
    public RateLimitingMiddleware(RequestDelegate next)
    {
          _next = next;
    }
    

    Ve sonrasında InvokeAsync isimli fonksiyonumuzun içerisinde isteğimiz ile ilgili gerekli işlemleri yaptıkdan sonra context’i bir sonraki işleme göndermek.

    C#
    public async Task InvokeAsync(HttpContext context)
    {
        await _next(context);
    }

    Bu yapı bizim sistemimizin omurgası, bu yapı tüm pipeline da mevcut ve tüm middleware lar birbirine bu şekilde isteği iletiyor.

    Diğer kod kısımları örneğimize ait kısımlar.

    Şimdi sırada bu oluşturduğumuz middleware’i Program.cs dosyamızda sıraya almak var.

    Bunun için basitçe program.cs dosyamızı şu şekilde düzenliyoruz.

    C#
    // Program.cs
    
    using YourNamespace.Middlewares; // doğru namespace olduğuna dikkat et
    
    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    // Rate Limiting Middleware en başta olabilir
    app.UseMiddleware<RateLimitingMiddleware>();
    
    app.MapControllers();
    app.Run();

    Burada birden fazla middleware eklersek eğer bilmemiz gereken bu middleware’lerin sırasının önemli olduğudur.

    Birden fazla middleware olduğunu varsayalım bu durumda akış şu şekilde olacaktır.

    C#
    İstek Akışı
    [C öncesi]
      [B öncesi]
        [A öncesi]
          [KULLANICI → RESPONSE]
        [A sonrası]
      [B sonrası]
    [C sonrası]

    Bu yazıda middleware’nin ne olduğunu inceledik ve basit bir örnek yapmış olduk. Bir sonraki yazıda görüşmek üzere.

    Video Özet

  • ASP.NET Core Web API: Pagination, Filtering ve Sorting Yapısını En İyi Şekilde Kurmak

    Modern web API’lerinde kullanıcıya büyük veri kümeleri sunarken sayfalama (pagination), filtreleme (filtering) ve sıralama (sorting) işlemleri neredeyse kaçınılmaz hale gelmiştir. Özellikle veritabanı sorgularının verimli yönetilmesi, istemci tarafında performansın artırılması ve kullanıcı deneyiminin iyileştirilmesi açısından bu yapıların doğru şekilde uygulanması oldukça kritiktir.

    Bu yazıda, .NET Web API projelerinde pagination, filtering ve sorting işlemlerini en iyi uygulama prensipleriyle (best practices) nasıl hayata geçirebileceğimizi adım adım ele alacağız. Başlamadan önce bu kavramların ne anlama geldiğini ve neden bu kadar önemli olduklarını birlikte inceleyelim.

    Entity Framework

    ASP.NET Core Web API projelerinde kullandığımız veritabanı iletişimini sağlayan pakete verilen isimdir, bir çok ilişkisel veritabanı ile tam uyumludur fakat biz bu yazımızda özellikle MSSQL için kodlama yaparken kullanacağız.

    Pagination (Sayfalama)

    Pagination, bir web sayfasının daha hızlı açılması ve sunucuya aşırı yük bindirmemesi için bölümlere ayrılması ve kayıtların belirli limlitlerle sayfalara ayrılmış olarak görüntülenmesine verilen isimdir.

    Veritabanından veri çekerken bu limitin uygulanması için offset yani verinin başlangıç sırası ve limit yani kaç adet veri çekileceği belirtilerek bu işlem gerçekleştirilir.

    Filtering(Filtreleme)

    Filtreleme veritabanındaki kayıtları listelerken belirli kriterleri baz alarak eleme işlemine verilen isimdir. Örneğin 1995 ve üzeri model araçlar gibi filtreler veritabanı sorgularına eklenerek bu kriter ile uyumlu olmayan kayıtlar elenir.

    Sorting(Sıralama)

    Sıralam işlemi web api’lar da sıkça kullanılan bir yöntemdir, bu yöntem ile kayıtlar belli bir kritere göre sıralanır, bazen son eklenen önce bazen ise farklı sütunlara dayalı olarak bir sıralama gerçekleştirilir, örneğin yıl sutunu için azalandan artana şeklinde sorgular yazılır.

    Kavramlarımızdan sade bir şekilde bahsettiğimize göre şimdi sırada EF ile .NET Core Web API projesinde CarController içerisindeki GetAll endpoint’i kullanarak bu işlemleri nasıl gerçekleştirdiğimize bakalım.

    Bu yazımızın konusu olmadığı için sıfırdan veritabanı bağlantısı ve kontroller oluşturma ve endpoint ile map’leme işlemine odaklanmayacağım, direk controller üzerinden EF kullanarak bu işlemleri CarController üzerinden gerçekleştireceğim.

    Siz veritabanı bağlantısını vs zaten kurduysanız bu adımları atlayabilirsiniz.

    Veritabanı Bağlantısının Sağlanması

    Öncelikle veritabanınıza ait connection string’i projemize dahil editoruz.

    Data/AppDbContext.cs

    C#
    using Microsoft.EntityFrameworkCore;
    using CarAPI.Models;
    
    namespace CarAPI.Data
    {
        public class AppDbContext : DbContext
        {
            public AppDbContext(DbContextOptions<AppDbContext> options)
                : base(options)
            {
            }
    
            public DbSet<Car> Cars => Set<Car>();
        }
    }

    appsettings.json

    C#
    {
      "ConnectionStrings": {
        "DefaultConnection": "Server=.;Database=CarDb;Trusted_Connection=True;"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
    

    Sonrasında veritabanı context nesnesini Dependency Injection ile kullanabilmek için Program.cs dosyasına builder oluşturulduktan sonraki satıra veritabanı için DI yapımızı ekliyoruz. Buna ek olarak gerekli bazı using direktifleri de ekliyoruz.

    Program.cs

    C#
    using CarAPI.Data;
    using Microsoft.EntityFrameworkCore;
    
    // var builder = ile başlayan satırın hemen soonrasına
    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

    Proje Dosya Yapısına Genel Bir bakalım.

    /CarAPI
    
    ├── Controllers
    │   └── CarController.cs
    
    ├── DTOs
    │   └── CarQueryParameters.cs
    
    ├── Data
    │   └── AppDbContext.cs
    
    ├── Models
    │   └── Car.cs
    
    ├── Program.cs
    ├── CarAPI.csproj
    └── appsettings.json
    

    Controller

    Dependency injection ile veritabanı bağlantı nesnemizi inject ediyoruz.

    Belirli konulara odaklanacağımız için Repository – Service pattern’i kullanmayacağım.

    Sorgulara pagination, sorting, filtering eklemek için kullandığımız başlıca EF metodları var

    • .AsQueryable daha sonra build etmek için query başlatıyoruz.
    • .Where ile herhangi bir sütun için filtreleme ekleyebiliyoruz.
    • OrderBy ile sorgumuza istediğimiz şekilde sıralama ekleyebiliyoruz
    • CountAsync ile toplam kayıt sayısını alıyoruz, bu işlem sayfalama için gerekli oluyor.

    Temelde yapmak istediğimiz işlemlere bu dördü yetiyor.

    Controllers/CarController.cs

    C#
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using CarAPI.Data;
    using CarAPI.Models;
    using CarAPI.DTOs;
    
    namespace CarAPI.Controllers
    {
        [ApiController]
        [Route("api/[controller]")]
        public class CarController : ControllerBase
        {
            private readonly AppDbContext _context;
    
            public CarController(AppDbContext context)
            {
                _context = context;
            }
    
            /*
             * Tüm araçları listeleyen endpoint
             * Bu metod, filtreleme, sıralama ve sayfalama işlemlerini destekler
             */
            [HttpGet]
            public async Task<IActionResult> GetAll([FromQuery] CarQueryParameters queryParams)
            {
                // Veritabanındaki Cars tablosu üzerinden sorguya başlanır
                var query = _context.Cars.AsQueryable();
    
                // FILTRELEME ADIMI - MinYear değeri varsa, bu yıldan küçük olanlar elenir
                if (queryParams.MinYear.HasValue)
                    query = query.Where(c => c.Year >= queryParams.MinYear.Value);
    
                // FILTRELEME ADIMI - MaxYear değeri varsa, bu yıldan büyük olanlar elenir
                if (queryParams.MaxYear.HasValue)
                    query = query.Where(c => c.Year <= queryParams.MaxYear.Value);
    
                // SIRALAMA ADIMI - Kullanıcı hangi kolona göre sıralama istediğini belirtmişse, ona göre sıralanır
                if (!string.IsNullOrEmpty(queryParams.SortBy))
                {
                    // IsDescending true ise azalan, değilse artan sıralama yapılır
                    query = queryParams.IsDescending
                        ? query.OrderByDescending(e => EF.Property<object>(e, queryParams.SortBy))
                        : query.OrderBy(e => EF.Property<object>(e, queryParams.SortBy));
                }
    
                // TOPLAM KAYIT SAYISI - Sayfalama için kaç toplam kayıt olduğunu hesaplıyoruz
                var totalItems = await query.CountAsync();
    
                // SAYFALAMA ADIMI - Sayfa numarasına ve sayfa boyutuna göre veriler bölünür
                var cars = await query
                    .Skip((queryParams.Page - 1) * queryParams.ValidPageSize) // Kaç kayıt atlanacak
                    .Take(queryParams.ValidPageSize) // Kaç kayıt alınacak
                    .ToListAsync();
    
                // SAYFALAMA METADATASI - frontend’e bilgi vermek için header üzerinden metadata gönderiyoruz
                var metadata = new
                {
                    queryParams.Page,
                    PageSize = queryParams.ValidPageSize,
                    TotalItems = totalItems,
                    TotalPages = (int)Math.Ceiling(totalItems / (double)queryParams.ValidPageSize)
                };
    
                // Header'a özel bilgi ekleniyor, genelde frontend tarafında pagination bilgisi burada okunur
                Response.Headers.Add("X-Pagination", System.Text.Json.JsonSerializer.Serialize(metadata));
    
                // İstenilen filtre/sıralama/sayfalama işlemlerine göre sonuçlar döndürülür
                return Ok(cars);
            }
        }
    }
    


    Model

    Models/Car.cs

    C#
    namespace CarAPI.Models
    {
        public class Car
        {
            public int Id { get; set; }
            public string Brand { get; set; } = string.Empty;
            public string Model { get; set; } = string.Empty;
            public int Year { get; set; }
            public bool IsDeleted { get; set; } = false;
        }
    }

    DTO

    DTOs/CarQueryParameters.cs

    C#
    namespace CarAPI.DTOs
    {
        public class CarQueryParameters
        {
            public int? MinYear { get; set; }
            public int? MaxYear { get; set; }
            public string? SortBy { get; set; } = "Id";
            public bool IsDescending { get; set; } = false;
            public int Page { get; set; } = 1;
            public int PageSize { get; set; } = 10;
    
            private const int MaxPageSize = 50;
    
            public int ValidPageSize => (PageSize > MaxPageSize) ? MaxPageSize : PageSize;
        }
    }
  • Merhaba Dünya

    Merhaba Dünya, adetini bozmadan bu blog sitemde neler olacak sizlerle paylaşmak istiyorum.

    Websitemin amacı bildiklerimi ve öğrendiklerimi hem bana hem de size kaynak olacak şekilde özetleyerek saklamak.

    Bir çok chatgpt sohbeti ve internet araştırmasında edindiğim bilgileri parça parça bir yerlere not ediyorum, bu blog sitesi ile birlikte artık bu parça parça metinleri birleştirip bir başvuru kaynağı oluşturmaya çalışacağım.

    Websitem ile ilgilendiğiniz ve yazımı okuduğunuz için teşekkür ederim.