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
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
{
"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
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
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
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
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;
}
}
Bir yanıt yazın