Sem Göksu
Sem Göksu
Yazılım · Yolculuk · Fenerbahçe
React Native

Fullstack Mobil — Bölüm 2: Backend API ve Veritabanı

ASP.NET Core 9 + EF Core ile blog uygulamamızın backend'ini yazıyoruz. Entity modelleri, DbContext, migrations, DTO pattern, tam CRUD controller ve seed data — production-hazır bir API iskeleti.

20 Nisan 2026 6 dk okuma 8 0
Merhaba arkadaşlar, serinin ikinci bölümüne hoş geldiniz. Geçen yazıda iskeleti kurduk, bu yazıda backend'i inşa ediyoruz — EF Core ile veritabanı modelleri, migrations, CRUD endpoint'leri, DTO pattern ve validation. Bu bölümü bitirdiğinizde çalışan bir REST API'niz olacak, Swagger'dan test edebileceksiniz.

Domain Modelimiz: Blog Uygulaması
Yapacağımız uygulama basit bir blog. Üç ana tablo:

- Users — kullanıcılar (auth için, bir sonraki bölümde JWT'de kullanacağız)
- Posts — blog yazıları
- Comments — yorumlar

Entity Modellerini Yazalım
api/Models/ klasörü oluşturup entity'leri koyuyoruz:

// Models/User.cs
public class User
{
    public int Id { get; set; }
    public required string KullaniciAdi { get; set; }
    public required string Email { get; set; }
    public required string SifreHash { get; set; }
    public string Rol { get; set; } = "User";
    public DateTime OlusturmaTarihi { get; set; } = DateTime.UtcNow;

    public List<Post> Posts { get; set; } = new();
}

// Models/Post.cs
public class Post
{
    public int Id { get; set; }
    public required string Baslik { get; set; }
    public required string Icerik { get; set; }
    public string? KapakResmi { get; set; }
    public DateTime OlusturmaTarihi { get; set; } = DateTime.UtcNow;
    public DateTime? YayinlanmaTarihi { get; set; }
    public bool Yayinda { get; set; }

    public int YazarId { get; set; }
    public User? Yazar { get; set; }

    public List<Comment> Comments { get; set; } = new();
}

// Models/Comment.cs
public class Comment
{
    public int Id { get; set; }
    public required string Icerik { get; set; }
    public DateTime OlusturmaTarihi { get; set; } = DateTime.UtcNow;

    public int PostId { get; set; }
    public Post? Post { get; set; }

    public int YazarId { get; set; }
    public User? Yazar { get; set; }
}
DbContext'i Kuralım
EF Core paketini ekleyelim:

cd api
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
Package Manager Console - Add-Migration InitialCreate ve Update-Database calistirildi.
Package Manager Console - Add-Migration InitialCreate ve Update-Database calistirildi.

IDE tarafinda: Visual Studio tarafinda Tools > NuGet Package Manager > Package Manager Console'u acip EF Core migration'larini buradan yonetiyoruz. Default project secimini Data katmanina vermeyi unutmayin.

DbContext:

// Data/AppDb.cs
using Microsoft.EntityFrameworkCore;

public class AppDb(DbContextOptions<AppDb> options) : DbContext(options)
{
    public DbSet<User> Users => Set<User>();
    public DbSet<Post> Posts => Set<Post>();
    public DbSet<Comment> Comments => Set<Comment>();

    protected override void OnModelCreating(ModelBuilder b)
    {
        b.Entity<User>(e =>
        {
            e.HasIndex(x => x.KullaniciAdi).IsUnique();
            e.HasIndex(x => x.Email).IsUnique();
            e.Property(x => x.KullaniciAdi).HasMaxLength(50);
            e.Property(x => x.Email).HasMaxLength(256);
        });

        b.Entity<Post>(e =>
        {
            e.Property(x => x.Baslik).HasMaxLength(300);
            e.HasOne(x => x.Yazar).WithMany(x => x.Posts)
                .HasForeignKey(x => x.YazarId).OnDelete(DeleteBehavior.Restrict);
            e.HasIndex(x => x.Yayinda);
            e.HasIndex(x => x.YayinlanmaTarihi);
        });

        b.Entity<Comment>(e =>
        {
            e.HasOne(x => x.Post).WithMany(x => x.Comments)
                .HasForeignKey(x => x.PostId).OnDelete(DeleteBehavior.Cascade);
        });
    }
}
Program.cs Konfigürasyonu
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<AppDb>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// CORS — mobile cihazdan erişim için
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(p => p
        .AllowAnyOrigin()
        .AllowAnyMethod()
        .AllowAnyHeader());
});

var app = builder.Build();
app.UseCors();
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();
app.Run();
appsettings.json
{
  "ConnectionStrings": {
    "Default": "Server=(localdb)\\MSSQLLocalDB;Database=BlogAppDb;Trusted_Connection=True"
  },
  "Jwt": {
    "Key": "CokGizliBirSecretKey-En-Az-32-Karakter-Olmali",
    "Issuer": "BlogApp.Api",
    "Audience": "BlogApp.Mobile",
    "ExpireMinutes": 60
  }
}
İlk Migration'ı Çalıştıralım
dotnet ef migrations add Initial
dotnet ef database update
LocalDB'de BlogAppDb veritabanı oluşur. SQL Server Management Studio'dan veya VS'nin SQL Server Object Explorer'ından doğrulayabilirsiniz.

DTO Pattern — Neden ve Nasıl
Entity'leri direkt dışarı açmak yaygın bir hata:

- Kullanıcının SifreHash'ini API'de görmek istemezsiniz
- İstemcinin ihtiyacı olmayan alanlar (CreatedBy, UpdatedBy vs.) gereksiz payload
- Entity değişirse API de otomatik değişir — versiyonlama kabusu

Çözüm: DTO (Data Transfer Object). Her entity için girdi ve çıktı DTO'ları:

// DTOs/PostDtos.cs
public record PostListItemDto(int Id, string Baslik, string? KapakResmi, DateTime? YayinlanmaTarihi, string YazarAdi, int YorumSayisi);

public record PostDetailDto(int Id, string Baslik, string Icerik, string? KapakResmi, DateTime? YayinlanmaTarihi, string YazarAdi);

public record PostCreateDto(
    [property: Required, StringLength(300, MinimumLength = 3)] string Baslik,
    [property: Required, MinLength(10)] string Icerik,
    string? KapakResmi);

public record PostUpdateDto(
    [property: Required, StringLength(300, MinimumLength = 3)] string Baslik,
    [property: Required] string Icerik,
    bool Yayinda);
Controller — Tam CRUD
// Controllers/PostsController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

[ApiController]
[Route("api/[controller]")]
public class PostsController(AppDb db) : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> Tumu(int page = 1, int pageSize = 20)
    {
        var query = db.Posts
            .Where(p => p.Yayinda)
            .OrderByDescending(p => p.YayinlanmaTarihi)
            .Select(p => new PostListItemDto(
                p.Id, p.Baslik, p.KapakResmi, p.YayinlanmaTarihi,
                p.Yazar!.KullaniciAdi,
                p.Comments.Count));

        var total = await query.CountAsync();
        var items = await query.Skip((page - 1) * pageSize).Take(pageSize).ToListAsync();

        Response.Headers["X-Total-Count"] = total.ToString();
        return Ok(items);
    }

    [HttpGet("{id:int}")]
    public async Task<IActionResult> Getir(int id)
    {
        var post = await db.Posts
            .Include(p => p.Yazar)
            .Where(p => p.Id == id)
            .Select(p => new PostDetailDto(
                p.Id, p.Baslik, p.Icerik, p.KapakResmi,
                p.YayinlanmaTarihi, p.Yazar!.KullaniciAdi))
            .FirstOrDefaultAsync();

        return post is null ? NotFound() : Ok(post);
    }

    [HttpPost]
    public async Task<IActionResult> Olustur([FromBody] PostCreateDto dto)
    {
        var post = new Post
        {
            Baslik = dto.Baslik,
            Icerik = dto.Icerik,
            KapakResmi = dto.KapakResmi,
            YazarId = 1, // geçici, JWT bölümünde current user'dan alacağız
            Yayinda = false
        };
        db.Posts.Add(post);
        await db.SaveChangesAsync();

        return CreatedAtAction(nameof(Getir), new { id = post.Id }, new { id = post.Id });
    }

    [HttpPut("{id:int}")]
    public async Task<IActionResult> Guncelle(int id, [FromBody] PostUpdateDto dto)
    {
        var post = await db.Posts.FindAsync(id);
        if (post is null) return NotFound();

        post.Baslik = dto.Baslik;
        post.Icerik = dto.Icerik;
        if (dto.Yayinda && post.YayinlanmaTarihi is null)
            post.YayinlanmaTarihi = DateTime.UtcNow;
        post.Yayinda = dto.Yayinda;

        await db.SaveChangesAsync();
        return NoContent();
    }

    [HttpDelete("{id:int}")]
    public async Task<IActionResult> Sil(int id)
    {
        var post = await db.Posts.FindAsync(id);
        if (post is null) return NotFound();
        db.Posts.Remove(post);
        await db.SaveChangesAsync();
        return NoContent();
    }
}
Seed Data — İlk Testler İçin
Uygulama ayağa kalkınca boş bir DB yerine birkaç örnek veri olsun:

// Program.cs içinde app.Run()'dan ÖNCE
using (var scope = app.Services.CreateScope())
{
    var db = scope.ServiceProvider.GetRequiredService<AppDb>();
    db.Database.Migrate();

    if (!db.Users.Any())
    {
        var admin = new User
        {
            KullaniciAdi = "admin",
            Email = "admin@blogapp.com",
            SifreHash = "geciciBCryptHashBuradaOlacak",
            Rol = "Admin"
        };
        db.Users.Add(admin);
        db.SaveChanges();

        db.Posts.AddRange(
            new Post { Baslik = "İlk Yazım", Icerik = "Merhaba blog!", YazarId = admin.Id, Yayinda = true, YayinlanmaTarihi = DateTime.UtcNow },
            new Post { Baslik = "API'ye Giriş", Icerik = "Bugün API yazıyoruz", YazarId = admin.Id, Yayinda = true, YayinlanmaTarihi = DateTime.UtcNow });
        db.SaveChanges();
    }
}
Swagger'dan Test
dotnet run çalıştırıp https://localhost:5000/swagger adresine gidin:

- GET /api/posts — listeyi almak
- GET /api/posts/1 — tek yazıyı almak
- POST /api/posts — yeni oluşturmak (Try it out)

Hepsi çalışıyorsa backend'imiz hazır.

Dikkat Edilmesi Gereken Şeyler
- N+1 problemi: Include() veya .Select() projection ile önleyin
- Pagination: Büyük listeler için şart, hep Skip/Take + X-Total-Count
- Transaction: Birden fazla tablo değişiyorsa db.Database.BeginTransaction()
- Soft delete: Kalıcı silme yerine IsDeleted flag (kullanıcı verisinde özellikle)

Özet
Bu bölümde EF Core ile modelleri, migrations, DTO pattern ve tam CRUD API'yi yazdık. Elimizde artık production-hazır bir backend var.

Bir sonraki bölümde bu API'ye JWT authentication ekleyeceğiz — login, token üretimi, rol kontrolleri, şifre hash'leme. Kolay gelsin, yorumlarda gördüklerinizi bekliyorum...
Etiketler: #ASP.NET #C#
Paylaş:

Yorumlar (0)

Henüz yorum yok. İlk yorumu sen yap!

Yorum bırak

* Yorumlar moderasyon sonrası yayınlanır. E-posta gizli tutulur.