İçeriğe geç
Educora
İleri25 dk16 / 16

ASP.NET Core ile Web API

ASP.NET Core ile EF Core üzerinde bir CRUD API kur: minimal API'ler ve denetleyiciler, bağımlılık enjeksiyonu, ara katman (middleware), yapılandırma, doğrulama ve OpenAPI.

Kendini test et
Bu derste öğreneceklerin
  • Bir Web API projesi oluşturmak ve Program.cs dosyasının yapısını (servisler, ara katman, uç noktalar) açıklamak
  • EF Core ile hem minimal API hem de denetleyici tarzında CRUD uç noktaları yazmak
  • DI yaşam sürelerini seçmek, yapılandırmayı okumak ve API'yi curl ve OpenAPI ile test etmek

Önceki derste verileri EF Core ile veritabanında saklamayı öğrendin. Şimdi onları dünyaya açalım: bir mobil uygulama, bir web sitesi ya da başka bir sunucu, HTTP üzerinden öğrenci listesini isteyebilmeli ve yeni öğrenci ekleyebilmelidir. ASP.NET Core, .NET'in hızlı, çok platformlu ve açık kaynaklı web çatısıdır. Bu derste onunla EF Core üzerinde tam bir CRUD API kuracak; bağımlılık enjeksiyonunu, ara katmanı (middleware), yapılandırmayı ve OpenAPI belgesini öğreneceksin.

  1. 1
    Projeyi oluştur

    dotnet new webapi -n SchoolApi varsayılan olarak bir minimal API oluşturur; denetleyiciler için --use-controllers ekle.

  2. 2
    EF Core'u ekle

    Microsoft.EntityFrameworkCore.Sqlite paketini ekle, model ve bağlam sınıflarını yaz.

  3. 3
    Yapılandırma ve veritabanı

    Bağlantı dizesini appsettings.json dosyasına yaz, ardından önceki dersteki gibi dotnet ef migrations add ve dotnet ef database update çalıştır.

  4. 4
    Çalıştır ve test et

    dotnet run --urls http://localhost:5080 sunucuyu başlatır; istekleri curl, bir .http dosyası ya da Swagger UI ile gönder.

Terminal
dotnet new webapi -n SchoolApi
cd SchoolApi
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet run --urls http://localhost:5080
Beklenen çıktı
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5080
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
Örnek çıktı (kısaltılmış): yerleşik Kestrel sunucusu 5080 portunu dinliyor

Program.cs: servisler ve ara katman

Bir minimal API'de tüm uygulama Program.cs içinde kurulur ve üç bölümden oluşur. Önce builder.Services içine servisler kaydedilir; bu, .NET'in yerleşik bağımlılık enjeksiyonu (DI) kapsayıcısıdır: AddDbContext, SchoolContext'i; AddOpenApi, belge oluşturucuyu; AddValidation ise özniteliklere dayalı doğrulamayı ekler. Ardından builder.Build() uygulamayı oluşturur ve ara katman (middleware) hattı kurulur. En sonda uç noktalar, yani URL'lere bağlanmış metotlar gelir.

C#
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;

var builder = WebApplication.CreateBuilder(args);

// 1. services for dependency injection
builder.Services.AddDbContext<SchoolContext>(options =>
    options.UseSqlite(builder.Configuration.GetConnectionString("School")));
builder.Services.AddOpenApi();
builder.Services.AddValidation();              // .NET 10: checks [Required], [Range]...

var app = builder.Build();

// 2. the middleware pipeline
if (app.Environment.IsDevelopment())
    app.MapOpenApi();                          // the document at /openapi/v1.json

app.Use(async (context, next) =>               // our own middleware: request timing
{
    var watch = System.Diagnostics.Stopwatch.StartNew();
    await next(context);
    app.Logger.LogInformation("{Method} {Path} -> {Status} in {Ms} ms", context.Request.Method,
        context.Request.Path, context.Response.StatusCode, watch.ElapsedMilliseconds);
});
Program.cs, 1. bölüm: servisler ve ara katman (EF Core ve OpenAPI paketlerini gerektirir)

Ara katman (middleware), her HTTP isteğinin geçtiği zincirin bir halkasıdır. Her halka isteğe bakabilir, onu değiştirebilir, yanıtı kendisi verebilir ya da next'i çağırıp isteği bir sonraki halkaya iletebilir. Yukarıdaki kendi ara katmanımız bir kronometre başlatır, await next(context) ile zincirin geri kalanını bekler, ardından durum kodunu ve süreyi günlüğe yazar. Halkaların sırası önemlidir ve genellikle şöyledir:

  1. UseExceptionHandler — altındaki tüm halkalardan gelen hataları yakalar
  2. UseHttpsRedirection — HTTP isteklerini HTTPS'e yönlendirir
  3. UseCors — hangi sitelerin API'yi çağırabileceğini belirler
  4. UseAuthentication — “sen kimsin?”
  5. UseAuthorization — “neye izin verildi?”
  6. Uç noktalar: MapGet, MapPost, MapControllers

EF Core ile CRUD uç noktaları

C#
public class Student
{
    public int Id { get; set; }
    public required string Name { get; set; }
    public int Score { get; set; }
}

public class SchoolContext(DbContextOptions<SchoolContext> options) : DbContext(options)
{
    public DbSet<Student> Students => Set<Student>();
}

public record StudentDto([Required] string Name, [Range(0, 100)] int Score);
Sadeleştirilmiş model, ayarlarını DI'dan alan bağlam ve girdi için bir DTO
C#
// 3. endpoints
var students = app.MapGroup("/api/students");

students.MapGet("/", async (SchoolContext db) =>
    await db.Students.AsNoTracking().OrderBy(s => s.Id).ToListAsync());

students.MapGet("/{id:int}", async Task<Results<Ok<Student>, NotFound>> (int id, SchoolContext db) =>
    await db.Students.FindAsync(id) is Student s ? TypedResults.Ok(s) : TypedResults.NotFound());

students.MapPost("/", async (StudentDto dto, SchoolContext db) =>
{
    var student = new Student { Name = dto.Name, Score = dto.Score };
    db.Students.Add(student);
    await db.SaveChangesAsync();
    return TypedResults.Created($"/api/students/{student.Id}", student);
});

students.MapPut("/{id:int}", async Task<Results<NoContent, NotFound>> (int id, StudentDto dto, SchoolContext db) =>
    await db.Students.Where(s => s.Id == id)
        .ExecuteUpdateAsync(set => set.SetProperty(s => s.Name, dto.Name).SetProperty(s => s.Score, dto.Score)) == 0
        ? TypedResults.NotFound() : TypedResults.NoContent());

students.MapDelete("/{id:int}", async Task<Results<NoContent, NotFound>> (int id, SchoolContext db) =>
    await db.Students.Where(s => s.Id == id).ExecuteDeleteAsync() == 0
        ? TypedResults.NotFound() : TypedResults.NoContent());

app.Run();
Program.cs, 2. bölüm: beş uç nokta — okuma, oluşturma, güncelleme, silme

MapGroup, ortak /api/students önekini bir kez yazmanı sağlar. Bir uç noktanın parametreleri otomatik olarak doldurulur: id URL'den ({id:int} yalnızca tam sayı kabul eder), StudentDto JSON gövdesinden, SchoolContext ise DI kapsayıcısından. TypedResults doğru durum kodlarını döndürür: Created 201 ve bir Location başlığı, NoContent 204, NotFound 404 verir; Results<...> türü ise tüm olası yanıtları belgeler, OpenAPI da onları görür. JSON'daki adlar otomatik olarak camelCase olur: Name → name.

JSON
{
  "ConnectionStrings": {
    "School": "Data Source=school.db"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}
appsettings.json: GetConnectionString("School") bu değeri okur; appsettings.Development.json ve ortam değişkenleri onu geçersiz kılabilir

curl ile test

Terminal
curl -X POST localhost:5080/api/students -H 'Content-Type: application/json' -d '{"name":"Aysel","score":95}'
curl -X POST localhost:5080/api/students -H 'Content-Type: application/json' -d '{"name":"Murad","score":78}'
curl localhost:5080/api/students
curl -s -o /dev/null -w '%{http_code}\n' -X PUT localhost:5080/api/students/2 \
     -H 'Content-Type: application/json' -d '{"name":"Murad","score":84}'
curl -s -o /dev/null -w '%{http_code}\n' -X DELETE localhost:5080/api/students/2
curl -s -o /dev/null -w '%{http_code}\n' localhost:5080/api/students/2
curl -X POST localhost:5080/api/students -H 'Content-Type: application/json' -d '{"name":"Elvin","score":120}'
Beklenen çıktı
{"id":1,"name":"Aysel","score":95}
{"id":2,"name":"Murad","score":78}
[{"id":1,"name":"Aysel","score":95},{"id":2,"name":"Murad","score":78}]
204
204
404
{"title":"One or more validation errors occurred.","errors":{"Score":["The field Score must be between 0 and 100."]}}
Çıktı, aynı API'nin veriyi bellekte tutan bir sürümüyle .NET 10'da denendi; boş bir SQLite veritabanıyla yanıtlar aynıdır. Son istek doğrulamadan geçemedi ve 400 aldı

Denetleyiciler, DI yaşam süreleri ve OpenAPI

Minimal API'ler küçük ve orta ölçekli servisler için kullanışlıdır. Büyük projelerde ise çoğu zaman denetleyiciler (controllers) kullanılır: uç noktalar ControllerBase'ten türeyen sınıflarda gruplanır, yollar özniteliklerle yazılır. dotnet new webapi --use-controllers böyle bir proje oluşturur. İki tarz da aynı DI'ı, ara katmanı ve EF Core'u kullanır; hatta tek bir projede birleştirilebilirler.

C#
[ApiController]
[Route("api/[controller]")]                      // -> /api/students
public class StudentsController(SchoolContext db) : ControllerBase
{
    [HttpGet]
    public async Task<List<Student>> GetAll() =>
        await db.Students.AsNoTracking().OrderBy(s => s.Id).ToListAsync();

    [HttpGet("{id:int}")]
    public async Task<ActionResult<Student>> Get(int id) =>
        await db.Students.FindAsync(id) is Student s ? s : NotFound();

    [HttpPost]
    public async Task<ActionResult<Student>> Create(StudentDto dto)
    {
        var student = new Student { Name = dto.Name, Score = dto.Score };
        db.Students.Add(student);
        await db.SaveChangesAsync();
        return CreatedAtAction(nameof(Get), new { id = student.Id }, student);
    }
}
// Program.cs: builder.Services.AddControllers(); ... app.MapControllers();
Aynı API bir denetleyiciyle: SchoolContext birincil yapıcıdan gelir, [ApiController] ise doğrulama hatalarında otomatik olarak 400 döndürür
MetotYaşam süresiNe için
AddSingletontüm uygulama için tek örnekyapılandırma, bellek içi önbellek
AddScopedher HTTP isteği için bir örnekDbContext ve onu kullanan servisler
AddTransienther istendiğinde yeni bir örnekhafif, durumsuz yardımcılar
Yanlış: bir singleton, scoped bir servisi yakalar
builder.Services.AddDbContext<SchoolContext>(...);   // scoped: one per request
builder.Services.AddSingleton<ReportService>();      // ReportService needs SchoolContext
// error at startup (Development):
// Cannot consume scoped service '...SchoolContext' from singleton '...ReportService'.
Doğru: uyumlu yaşam süreleri
builder.Services.AddDbContext<SchoolContext>(...);   // scoped: one per request
builder.Services.AddScoped<ReportService>();         // the same lifetime as its dependency
Development kipinde ASP.NET Core bu hatayı daha başlangıçta yakalar; aksi hâlde tek bir DbContext tüm istekler arasında paylaşılırdı

OpenAPI (eski adıyla Swagger), bir API'nin makinece okunabilir tanımıdır: hangi URL'ler var, hangi JSON'u bekliyorlar ve hangi durum kodlarını döndürüyorlar. .NET 9'dan beri şablon AddOpenApi() ve MapOpenApi() kullanır; belge /openapi/v1.json adresinde sunulur. Tarayıcıda etkileşimli bir sayfa için Swashbuckle.AspNetCore.SwaggerUI paketini ekle ve app.UseSwaggerUI(o => o.SwaggerEndpoint("/openapi/v1.json", "School API")) yaz: /swagger sayfasında her uç noktayı bir düğmeyle deneyebilirsin. Mobil uygulamalar için istemci kodu da bu belgeden otomatik olarak üretilebilir.

Önemli noktalar

  • Program.cs: servisler (DI) → Build() → ara katman → uç noktalar → Run().
  • Minimal API: MapGroup, MapGet/MapPost/MapPut/MapDelete; parametreler URL'den, gövdeden ve DI'dan gelir, TypedResults 200/201/204/404 döndürür.
  • DI yaşam süreleri: Singleton, Scoped (DbContext), Transient; scoped bir servisi asla bir singleton'a verme.
  • Yapılandırma appsettings.json, ortama özel dosyalar ve ortam değişkenlerinden gelir; bağlantı dizesini GetConnectionString okur.
  • Girdide DTO ve AddValidation; OpenAPI belgesi /openapi/v1.json adresindedir, Swagger UI ise bir paketle eklenir.

Kendini test et

10 soru. Her doğru cevap XP kazandırır.

1 / 10
AddDbContext, DbContext'i hangi yaşam süresiyle kaydeder?