Skip to main content
Controller’lar incedir (thin): iş mantığı taşımazlar, yalnızca isteği bir MediatR komut/query’sine çevirip _mediator.Send(...) çağırırlar. Tüm validasyon, yetkilendirme ve iş kuralları Application katmanındaki pipeline behavior’lar ve handler’lar tarafından işlenir.

Base controller’lar

İki ortak taban sınıf vardır (src/DiyanetCleanArchitecture.API/SeedWork/Controllers/):
Base classKullanımCORS policy
AdminApiControllerBaseYönetim paneli (Backoffice)AllowedBackofficePolicy (sadece backoffice origin)
WebsiteApiControllerBaseVatandaş portali (kimlik doğrulamalı)AllowedWebsitePolicy (sadece website origin)
Her ikisi de [ApiController] + [EnableCors(...)] taşır ama auth attribute’u kasten içermez.
Auth attribute’u yalnızca derived controller’da, tek satırda yazılır. Base’e [Authorize(Schemes=Personel)] + derived’a [Authorize(Schemes=Personel, Roles=...)] koymak, ASP.NET’in policy merge davranışı yüzünden 403’e yol açar (combined policy beklenen claim’leri göremez). Doğru kullanım:
[Authorize(AuthenticationSchemes = KeycloakSchemeNames.Personel, Roles = "SuperAdmin,Admin")]

Üç rota grubu

Path prefixAuthSchemeCORS
api/admin/*PersonelPersonelScheme (KeycloakSchemeNames.Personel)Backoffice
api/website/*VatandaşVatandasScheme (KeycloakSchemeNames.Vatandas)Website
api/website/public/*AnonimPublic (website + backoffice, sadece GET)
api/website/public/* hem website hem backoffice origin’ine açıktır — admin panelindeki form dropdown’ları (il/ilçe lookup gibi) public veriye erişebilsin diye. Named policy’ler Program.cs’te tanımlıdır:
  • AuthPolicies.PersonelAuthenticatedPersonelScheme + RequireAuthenticatedUser()
  • AuthPolicies.VatandasAuthenticatedVatandasScheme + RequireAuthenticatedUser()
  • AuthPolicies.AnyKeycloakAuthenticated → her iki scheme + RequireAuthenticatedUser()
Bare [Authorize(Schemes=X)] boş-requirement’lı bir policy üretir ve AuthorizationMiddleware’de 403’e düşebilir. Bu yüzden “sadece authenticated olsun yeter” senaryolarında named policy (RequireAuthenticatedUser()) kullanılır. DynamicPermissionPolicyProvider bu policy’leri ActiveAccountRequirement ile augment eder — yani Pending/Banned/Suspended kullanıcılar geçemez (gerekirse action’a [AllowPendingAccount] eklenir).

Gerçek örnek: MeController (Admin)

src/DiyanetCleanArchitecture.API/Controllers/Admin/MeController.cs:
[Route("api/admin/me")]
[Authorize(Policy = AuthPolicies.PersonelAuthenticated)]
public class MeController : AdminApiControllerBase
{
    private readonly IMediator _mediator;
    public MeController(IMediator mediator) => _mediator = mediator;

    [HttpGet]
    public async Task<IActionResult> Me(CancellationToken cancellationToken)
    {
        var result = await _mediator.Send(new GetAdminMeQuery(), cancellationToken);
        return result.IsSuccess ? Ok(result) : BadRequest(result);
    }

    // Pending/Banned kullanıcılar da erişebilir — [AllowPendingAccount] taşır.
    [HttpGet("status")]
    [AllowPendingAccount]
    public IActionResult GetStatus() { /* claim'lerden hesap durumu */ }
}
GET /api/admin/me → tam profil (frontend login sonrası bir kez çağırır). GET /api/admin/me/status → sadece hesap durumu; full /me 403 alırsa frontend “Onay bekliyor” ekranı için bunu okur.

Controller listesi

Admin (api/admin/*)Users, Citizens, Roles, Announcements, Centers, Faqs, Locations, BagisBasvurular (+ BagisBasvuruPlanlari), EtkinlikBasvurular (+ EtkinlikBasvuruPlanlari), AdminNotifications, AdminSupportTickets, LegalDocuments, Me. Website auth (api/website/*)Me, Users, BagisBasvurular, EtkinlikBasvurular, LegalDocuments. Website public (api/website/public/*)Announcements, Centers, Faqs, Locations, Enums, BagisBasvuruPlanlari, EtkinlikBasvuruPlanlari. GenelAuthController (api/auth — login/refresh/logout/OTP/TOTP/OAuth), SiteSettingsController.

ResponseWrapper zarfı

Başarılı yanıtlar ResponseWrapper<TResponse> ile sarılır:
public class ResponseWrapper<TResponse> : IResponseWrapper<TResponse>
{
    public TResponse Response  { get; }
    public string    RequestId { get; }
    public bool      IsSuccess { get; }
    public Exception Exception { get; }
    // static Success(...) / Error(...)
}
Controller, result.IsSuccess üzerinden Ok(result) veya BadRequest(result) döner.

Hata yanıtları — RFC 7807 ProblemDetails

Exception’lar Hellang.Middleware.ProblemDetails ile yakalanıp application/problem+json olarak döndürülür (SeedWork/ProblemDetails/ProblemDetailsOptionsConfiguration.cs):
ExceptionHTTP statusGövde
DomainException400title = exception mesajı
ApplicationException400title = exception mesajı
FluentValidation.ValidationException422ValidationProblemDetails + errors (alan → mesaj[])
SmsServiceException503”SMS servisi şu anda kullanılamıyor.”
EmailServiceException503”E-posta servisi şu anda kullanılamıyor.”
UndefinedApplicationException500”Beklenmeyen bir hata oluştu.”
diğer Exception500fallback
Her yanıta traceId extension’ı eklenir (Activity.Current?.Id ?? ctx.TraceIdentifier) — destek/log korelasyonu için tek bağlantı noktasıdır.
IncludeExceptionDetails her ortamda false olarak ayarlıdır (dev dahil). Stack trace, dosya yolları, iç class isimleri yanıta eklenmez — bilgi sızıntısını önler. Tam exception zaten Serilog’a yazılır; geliştirici client’tan gelen traceId ile log’da arar (“client’a minimal, log’a tam”).
Örnek 422 yanıtı:
{
  "title": "Bir veya daha fazla doğrulama hatası oluştu.",
  "status": 422,
  "instance": "/api/admin/users",
  "errors": {
    "Email": [ "Geçerli bir e-posta adresi giriniz." ]
  },
  "traceId": "00-a1b2c3...-01"
}

Sonraki adımlar

API genel bakış

Pipeline sırası ve servis kayıtları.

OpenAPI / Swagger

NSwag, security scheme’ler, client üretimi.

Frontend SPA'lar

axios interceptor RFC 7807 hatalarını nasıl işler.

Keycloak

Çift realm, scheme’ler, claim’ler.