ASP.NET Core ile RESTful API Geliştirme

15 dakika okuma9 Şubat 2026Güncellendi: 9 Mar 2026
ASP.NET Core APIREST API .NETWeb API tutorialASP.NET Core controllerAPI development C#.NET REST best practicesSwagger .NETAPI versioning

# ASP.NET Core ile RESTful API Gelistirme

ASP.NET Core, modern ve performansli API'ler olusturmak icin en guclu seceneklerden biri. Hizli HTTP pipeline'i, olgun middleware altyapisi ve surdurulebilir servisler icin saglam konvansiyonlari ile kurumsal projelerde kendini defalarca kanitlamis bir framework. Uretim ortaminda gelistirdigim API'lerde, framework'un yogun yuk altinda bile guvenilir sekilde calistigini defalarca gordum; ancak en iyi sonucu almak icin ilk gunden itibaren bilineli mimari kararlari almak sart.

API Temelleri

Proje Yapisi ve Controller Tasarimi

Iyi organize edilmis bir API, temiz bir controller katmaniyla baslar. Controller'lar ince birer orkestrator olmali ve is mantigi servis katmanina devredilmeli:

csharp
[ApiController]
[Route(class="code-string">"api/v{version:apiVersion}/[controller]")]
[Produces(class="code-string">"application/json")]
public class ProductsController : ControllerBase
{
    private readonly IProductService _productService;
    private readonly ILogger<ProductsController> _logger;

    public ProductsController(IProductService productService, ILogger<ProductsController> logger)
    {
        _productService = productService;
        _logger = logger;
    }

    [HttpGet]
    [ProducesResponseType(typeof(PagedResult<ProductDto>), StatusCodes.Status200OK)]
    public async Task<IActionResult> GetAll([FromQuery] ProductQueryParameters query)
    {
        var result = await _productService.GetAllAsync(query);
        return Ok(result);
    }

    [HttpGet(class="code-string">"{id:guid}")]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> GetById(Guid id)
    {
        var product = await _productService.GetByIdAsync(id);
        if (product is null)
            return NotFound();

        return Ok(product);
    }

    [HttpPost]
    [ProducesResponseType(typeof(ProductDto), StatusCodes.Status201Created)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> Create([FromBody] CreateProductRequest request)
    {
        var product = await _productService.CreateAsync(request);
        return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
    }

    [HttpPut(class="code-string">"{id:guid}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
    public async Task<IActionResult> Update(Guid id, [FromBody] UpdateProductRequest request)
    {
        await _productService.UpdateAsync(id, request);
        return NoContent();
    }

    [HttpDelete(class="code-string">"{id:guid}")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    public async Task<IActionResult> Delete(Guid id)
    {
        await _productService.DeleteAsync(id);
        return NoContent();
    }
}

Model Binding ve DTO'lar

Domain entity'lerinizi asla dogrudan disariya acmayin. API kontratinizi kararli tutmak icin ozel request ve response modelleri kullanin:

csharp
public record CreateProductRequest(
    string Name,
    string Description,
    decimal Price,
    string Category
);

public record ProductDto(
    Guid Id,
    string Name,
    string Description,
    decimal Price,
    string Category,
    DateTime CreatedAt
);

public record ProductQueryParameters
{
    public int Page { get; init; } = class="code-number">1;
    public int PageSize { get; init; } = class="code-number">20;
    public string? SortBy { get; init; }
    public string? Search { get; init; }
}

FluentValidation ile Dogrulama

Basit durumlar icin Data Annotations yeterli olabilir, ancak kurallar karmasiklastiginda FluentValidation gercek gucunu gosterir. Uretim ortaminda gelistirdigim API'lerde FluentValidation, hatali isteklerin debuglanmasinda sayisiz saat kazandirdi:

csharp
public class CreateProductRequestValidator : AbstractValidator<CreateProductRequest>
{
    public CreateProductRequestValidator()
    {
        RuleFor(x => x.Name)
            .NotEmpty().WithMessage(class="code-string">"Urun adi zorunludur.")
            .MaximumLength(class="code-number">200).WithMessage(class="code-string">"Urun adi class="code-number">200 karakteri asamaz.");

        RuleFor(x => x.Description)
            .MaximumLength(class="code-number">2000);

        RuleFor(x => x.Price)
            .GreaterThan(class="code-number">0).WithMessage(class="code-string">"Fiyat pozitif bir deger olmalidir.")
            .PrecisionScale(class="code-number">10, class="code-number">2, ignoreTrailingZeros: true)
            .WithMessage(class="code-string">"Fiyat en fazla class="code-number">2 ondalik basamak icermelidir.");

        RuleFor(x => x.Category)
            .NotEmpty()
            .Must(BeAValidCategory).WithMessage(class="code-string">"Gecersiz kategori.");
    }

    private bool BeAValidCategory(string category)
    {
        var validCategories = new[] { class="code-string">"Electronics", class="code-string">"Clothing", class="code-string">"Books", class="code-string">"Food" };
        return validCategories.Contains(category);
    }
}

FluentValidation'i `Program.cs` dosyanizda kaydedin:

csharp
builder.Services.AddValidatorsFromAssemblyContaining<CreateProductRequestValidator>();
builder.Services.AddFluentValidationAutoValidation();

ProblemDetails ile Global Hata Yonetimi

Tutarli hata yanitlari tartismaya acik degildir. Yerlesik `ProblemDetails` altyapisini global bir exception handler ile birlikte kullanin:

csharp
builder.Services.AddProblemDetails(options =>
{
    options.CustomizeProblemDetails = context =>
    {
        context.ProblemDetails.Extensions[class="code-string">"traceId"] = context.HttpContext.TraceIdentifier;
    };
});

app.UseExceptionHandler(appError =>
{
    appError.Run(async context =>
    {
        context.Response.ContentType = class="code-string">"application/problem+json";
        var exceptionFeature = context.Features.Get<IExceptionHandlerFeature>();
        var exception = exceptionFeature?.Error;

        var problemDetails = exception switch
        {
            NotFoundException ex => new ProblemDetails
            {
                Status = StatusCodes.Status404NotFound,
                Title = class="code-string">"Kaynak Bulunamadi",
                Detail = ex.Message,
                Type = class="code-string">"https:class="code-commentclass="code-string">">//tools.ietf.org/html/rfc9110#section-class="code-number">15.5.class="code-number">5"
            },
            ConflictException ex => new ProblemDetails
            {
                Status = StatusCodes.Status409Conflict,
                Title = class="code-string">"Catisma",
                Detail = ex.Message,
                Type = class="code-string">"https:class="code-commentclass="code-string">">//tools.ietf.org/html/rfc9110#section-class="code-number">15.5.class="code-number">10"
            },
            _ => new ProblemDetails
            {
                Status = StatusCodes.Status500InternalServerError,
                Title = class="code-string">"Sunucu Hatasi",
                Detail = class="code-string">"Beklenmeyen bir hata olustu.",
                Type = class="code-string">"https:class="code-commentclass="code-string">">//tools.ietf.org/html/rfc9110#section-class="code-number">15.6.class="code-number">1"
            }
        };

        context.Response.StatusCode = problemDetails.Status ?? class="code-number">500;
        await context.Response.WriteAsJsonAsync(problemDetails);
    });
});

API Tasarim En Iyi Pratikleri

Kaynak Adlandirma Kurallari

Kaynaklar icin cogul isimler kullanin ve URL'leri tahmin edilebilir tutin:

  • `GET /api/v1/products` — tum urunleri listele
  • `GET /api/v1/products/{id}` — tek bir urunu getir
  • `POST /api/v1/products` — yeni urun olustur
  • `PUT /api/v1/products/{id}` — urunu tamamen guncelle
  • `PATCH /api/v1/products/{id}` — urunu kismen guncelle
  • `DELETE /api/v1/products/{id}` — urunu sil
  • `GET /api/v1/products/{id}/reviews` — ic ice alt kaynaklar
  • URL'lerde fiil kullanmaktan kacinin. `/api/products/getAll` yerine sadece `GET /api/products` kullanin. HTTP metodu zaten islemi ifade eder.

    HTTP Metodlari ve Durum Kodlari

    Durum kodlarini bilineli kullanin. Her sey icin 200 dondurmek, iletisim firsatini kacirmak demektir:

    | Islem | Basari Kodu | Anlami |

    |-------|-------------|--------|

    | GET | 200 OK | Kaynak bulundu ve donduruldu |

    | POST | 201 Created | Kaynak olusturuldu, Location header ekle |

    | PUT | 204 No Content | Kaynak basariyla guncellendi |

    | PATCH | 200 OK | Kismi guncelleme yapildi, guncel hali don |

    | DELETE | 204 No Content | Kaynak silindi |

    Hatalar icin bu kodlara bagli kalin:

  • **400** — Hatali istek, dogrulama hatalari
  • **401** — Kimlik dogrulanmamis
  • **403** — Kimlik dogrulanmis ama yetkisiz
  • **404** — Kaynak bulunamadi
  • **409** — Catisma (tekrar, versiyon uyusmazligi)
  • **422** — Anlami gecersiz istek
  • **429** — Hiz siniri asildi
  • **500** — Sunucu hatasi (asla kasitli olmamali)
  • Sayfalama, Filtreleme ve Siralama

    Her liste endpoint'i basindan itibaren sayfalama desteklemeli. Sonradan eklemek her zaman daha zahmetli olur:

    csharp
    public class PagedResult<T>
    {
        public IReadOnlyList<T> Items { get; init; } = [];
        public int TotalCount { get; init; }
        public int Page { get; init; }
        public int PageSize { get; init; }
        public bool HasNextPage => Page * PageSize < TotalCount;
        public bool HasPreviousPage => Page > class="code-number">1;
    }

    Versiyonlama Stratejisi

    Ilk surumden itibaren versiyonlama planlayin. Uretim ortaminda gelistirdigim API'lerde versiyonlamayi atlam, her zaman agri dolu breaking-change goculeriyle sonuclandi. URL tabanli versiyonlama en acik ve istemci dostu yaklasimdir:

    csharp
    builder.Services.AddApiVersioning(options =>
    {
        options.DefaultApiVersion = new ApiVersion(class="code-number">1, class="code-number">0);
        options.AssumeDefaultVersionWhenUnspecified = true;
        options.ReportApiVersions = true;
        options.ApiVersionReader = ApiVersionReader.Combine(
            new UrlSegmentApiVersionReader(),
            new HeaderApiVersionReader(class="code-string">"X-API-Version")
        );
    })
    .AddApiExplorer(options =>
    {
        options.GroupNameFormat = class="code-string">"class="code-string">'v'VVV";
        options.SubstituteApiVersionInUrl = true;
    });

    Breaking change gerektiginde, eski controller'i yasatirken yeni bir versiyon olusturun:

    csharp
    [ApiController]
    [Route(class="code-string">"api/v{version:apiVersion}/products")]
    [ApiVersion(class="code-string">"class="code-number">2.0")]
    public class ProductsV2Controller : ControllerBase
    {
        [HttpGet(class="code-string">"{id:guid}")]
        public async Task<IActionResult> GetById(Guid id)
        {
            class=class="code-string">"code-comment">// V2 daha zengin bir yanit yapisi doner
            var product = await _productService.GetByIdDetailedAsync(id);
            return Ok(product);
        }
    }

    Kullanicilar goc edebilsin diye eski versiyonlari acikca deprecated olarak isaretleyin:

    csharp
    [ApiVersion(class="code-string">"class="code-number">1.0", Deprecated = true)]

    OpenAPI/Swagger Dokumantasyonu

    Iyi API dokumantasyonu, destek taleplerinin cogunlugunu ortadan kaldirir. Swagger'i zengin metadata ile yapilandirin:

    csharp
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen(options =>
    {
        options.SwaggerDoc(class="code-string">"v1", new OpenApiInfo
        {
            Title = class="code-string">"Urun Katalogu API",
            Version = class="code-string">"v1",
            Description = class="code-string">"Urun katalogu yonetimi icin API.",
            Contact = new OpenApiContact
            {
                Name = class="code-string">"API Destek",
                Email = class="code-string">"api-destek@ornek.com"
            }
        });
    
        options.SwaggerDoc(class="code-string">"v2", new OpenApiInfo
        {
            Title = class="code-string">"Urun Katalogu API",
            Version = class="code-string">"v2"
        });
    
        class=class="code-string">"code-comment">// Projenizdeki XML yorumlarini dahil edin
        var xmlFile = $class="code-string">"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
        var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
        options.IncludeXmlComments(xmlPath);
    
        class=class="code-string">"code-comment">// Swagger UIclass="code-string">'a JWT kimlik dogrulamasi ekleyin
        options.AddSecurityDefinition(class="code-string">"Bearer", new OpenApiSecurityScheme
        {
            Description = class="code-string">"Bearer semasi ile JWT yetkilendirme header'i.",
            Name = class="code-string">"Authorization",
            In = ParameterLocation.Header,
            Type = SecuritySchemeType.Http,
            Scheme = class="code-string">"bearer"
        });
    
        options.AddSecurityRequirement(new OpenApiSecurityRequirement
        {
            {
                new OpenApiSecurityScheme
                {
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = class="code-string">"Bearer"
                    }
                },
                Array.Empty<string>()
            }
        });
    });

    Swagger middleware'ini etkinlestirin:

    csharp
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI(options =>
        {
            options.SwaggerEndpoint(class="code-string">"/swagger/v1/swagger.json", class="code-string">"Urun API V1");
            options.SwaggerEndpoint(class="code-string">"/swagger/v2/swagger.json", class="code-string">"Urun API V2");
        });
    }

    Uretim Ortami En Iyi Pratikleri

  • **Idempotency key'ler** kritik yazma endpoint'leri icin — basarisiz POST isteklerini guvenle tekrar denemek icin `Idempotency-Key` header'i kullanin
  • **Korelasyon ID'leri** dagitik izleme icin — servis sinirlari arasinda `X-Correlation-Id` header'ini tasitim
  • **Saglik kontrolleri** orkestrator'ler icin — `/health/live` ve `/health/ready` endpoint'lerini sunun
  • **Hiz sinirlandirma** servisinizi korumak icin — .NET 7+'deki yerlesik `RateLimiter` middleware'ini kullanin
  • **Cikti onbellekleme** yogun okuma yapilan pahali endpoint'ler icin
  • Performans Ipuclari

  • Bastan sona `async`/`await` kullanin — zincirdeki tek bir senkron cagri, thread pool thread'ini bloke eder
  • Buyuk veri setlerini akis halinde gondermek icin her seyi bellekte tamponlamak yerine `IAsyncEnumerable<T>` kullanin
  • JSON agirlikli yanit govdeleri icin `UseResponseCompression()` ile yanit sikistirma uygulayin
  • Mikro-optimizasyon yapmadan once `dotnet-trace` ve `dotnet-counters` ile profil cikartin
  • AOT uyumlu serializasyon icin `System.Text.Json` source generator'larini kullanin
  • Guvenlik Temelleri

  • `UseHttpsRedirection()` ve HSTS header'lari ile her yerde HTTPS zorunlu kilin
  • Durumsuz kimlik dogrulamasi icin kisa sureli erisim token'lariyla JWT/OAuth2 kullanin
  • Hassas erisim kontrolu icin policy-based authorization uygulayin
  • Hassas verileri asla loglamayin — yapilandirilmis loglarda PII ve gizli bilgileri maskeleyin
  • Kimlik dogrulanmis endpoint'lerde bile tum kullanici girdilerini dogrulayin ve sanitize edin
  • Yaygin API Tasarim Hatalari

    Her sey icin 200 dondurmek. API'niz `200 OK` ile `{ "success": false, "error": "Bulunamadi" }` donduruyorsa, HTTP'yi kullanmak yerine onunla savasiyorsunuz demektir. Istemciler durum kodlarina guvenemez, cache ve load balancer gibi middleware'ler karisir, tekrar deneme mantigi bozulur.

    Domain entity'lerini yanit olarak acmak. Veritabani semaniz API kontratiniza sizdiginda, her sema degisikligi bir breaking change haline gelir. Her zaman ozel DTO'lar kullanin.

    Liste endpoint'lerinde sayfalama ihmal etmek. Gelistirme ortaminda 50 kayitla sorunsuz calisir. Sonra uretim ortaminda 500.000 kayit olur ve endpoint zaman asimina ugramaya baslar. Ilk gunden itibaren her zaman sayfalama yapin.

    Basindan itibaren versiyonlama yapmamak. "Ihtiyac duyunca versiyonlama ekleriz" demek, kacinilmaz bir breaking change geldiginde acil goc yapacaksiniz anlamina gelir. URL tabanli versiyonlama kurulumu hicbir sey maliyeti yoktur.

    Tutarsiz hata formatlari. Bazi endpoint'ler `{ "error": "mesaj" }` dondururken digerleri `ProblemDetails` donduruyorsa, her istemcinin birden fazla formati ele almasi gerekir. Tek bir standart secin ve global olarak uygulayin.

    Istek dogrulamasini atlamak. Istemcilerin dogru formatta veri gonderecegine guvenmenk, bozuk durum ve anlasilmaz 500 hatalarina davetiye cikarmaktir. Her zaman sinirlarda dogrulayin.

    Sonuc

    ASP.NET Core, guvenilir kurumsal API'ler icin gereken yapi ve performansi saglar; ancak kalite, net kontratlardan, guclu gozlemlenebilirlikten ve disiplinli surum pratiklerinden gelir. Uretim ortaminda gelistirdigim API'lerde, iyi bir API ile harika bir API arasindaki fark her zaman detaylarda gizlidir: tutarli hata yonetimi, dusunulmus versiyonlama ve gelistiricilerin gercekten okumak istedigi dokumantasyon.

    API mimarinizi birlikte tasarlayip uretim ortamina hazir hale getirebiliriz.

    İlgili Makaleler

    Flutter Projeniz mi Var?

    iOS, Android ve web için yüksek performanslı Flutter uygulamaları geliştiriyorum.

    İletişime Geç