Skip to main content

Zincir

Her Command/Query, Handler’a ulaşmadan önce bir IPipelineBehavior zincirinden geçer. Zincir, MediatR’da kayıt sırasıyla çalışır. DependencyInjection.cs:
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ExceptionPipelineBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(AuthorizationPipelineBehavior<,>));
//builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(LoggingPipelineBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(ValidationPipelineBehavior<,>));
builder.Services.AddTransient(typeof(IPipelineBehavior<,>), typeof(PermissionPipelineBehavior<,>));
Etkin sıra: Exception → Authorization → Validation → Permission → Handler
LoggingPipelineBehavior kayıtlı ama yorum satırına alınmıştır (disabled). Etkinleştirmek için ilgili satırın yorumunu kaldırmak yeterlidir; sıradaki konumu (Authorization’dan sonra) korunur.

1. ExceptionPipelineBehavior

En dış halka. Bilinen exception’ları olduğu gibi yukarı geçirir (throw;), bilinmeyenleri UndefinedApplicationException’a sarar. IBusMessage (Hangfire/bus mesajları) sarılmaz — DLQ/retry kararı altyapıya bırakılır.
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
{
    try
    {
        return await next();
    }
    catch (Exception ex) when (IsKnownException(ex))
    {
        _logger.LogWarning(ex, "Known exception in {RequestName}", typeof(TRequest).Name);
        throw;   // bilinen exception → olduğu gibi bubble-up
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Unhandled exception in {RequestName}. Request: {@Request}", typeof(TRequest).Name, request);

        if (request is IBusMessage)   // bus mesajlarında wrap yok
            throw;

        throw new UndefinedApplicationException("Beklenmeyen bir hata oluştu.", ex);
    }
}

private static bool IsKnownException(Exception ex) =>
    ex is FluentValidation.ValidationException
    || ex is DomainException
    || ex is ApplicationException
    || ex is SmsServiceException
    || ex is EmailServiceException;
Bilinen exception’lar API’deki ProblemDetails katmanına ulaşır ve doğru HTTP koduna çevrilir: DomainException → 400, ValidationException → 422, Sms/EmailServiceException → 503. Yani ForbiddenException (bir DomainException alt sınıfı) bilinen sayılır ve sarılmadan geçer.

2. AuthorizationPipelineBehavior

RBAC ve multi-tenant erişim kontrolü. Yalnızca IRequirePermissions veya IRequireTenantAccess uygulayan request’lerde devreye girer; aksi halde doğrudan next() çağrılır. Kullanıcı bağlamını ICurrentUserService (BuildingBlocks.Keycloak) üzerinden okur.
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
{
    bool requiresPermissions   = request is IRequirePermissions;
    bool requiresTenantAccess  = request is IRequireTenantAccess;

    if (!requiresPermissions && !requiresTenantAccess)
        return await next();

    if (!_currentUser.IsAuthenticated)
        throw new ForbiddenException();

    // Permission kontrolü — tüm izinler mevcut olmalı (AND)
    if (request is IRequirePermissions permRequest)
    {
        var missing = permRequest.RequiredPermissions
            .Where(p => !_currentUser.HasPermission(p))
            .ToList();

        if (missing.Count > 0)
            throw new ForbiddenException(missing);
    }

    // Tenant izolasyonu — token tenant'ı ile request tenant'ı örtüşmeli
    if (request is IRequireTenantAccess tenantRequest)
    {
        if (!_currentUser.IsInTenant(tenantRequest.TenantId))
            throw new ForbiddenException("Farklı bir organizasyona ait kaynağa erişim engellendi.");
    }

    return await next();
}
Başarısız her durumda ForbiddenException fırlatılır; bu da HTTP 403’e dönüşür.

3. ValidationPipelineBehavior

İlgili request için kayıtlı tüm IValidator<TRequest>’ları paralel çalıştırır. IBusMessage’ı atlar ve hiç validator yoksa devam eder. Hata varsa tek bir ValidationException (tüm failures ile) fırlatılır.
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
{
    if (request is IBusMessage)
        return await next();

    if (!_validators.Any())
        return await next();

    var context = new ValidationContext<TRequest>(request);

    var validationResults = await Task.WhenAll(
        _validators.Select(v => v.ValidateAsync(context, ct)));

    var failures = validationResults
        .SelectMany(r => r.Errors)
        .Where(f => f != null)
        .ToList();

    if (failures.Count != 0)
    {
        _logger.LogInformation("Validation failed for {RequestName}. Fields: {Fields}",
            typeof(TRequest).Name, failures.Select(f => f.PropertyName).Distinct());

        throw new ValidationException("Bir veya daha fazla doğrulama hatası oluştu.", failures);
    }

    return await next();
}
ValidationException API tarafında HTTP 422 + alan bazlı errors listesine çevrilir. Validator’ların nasıl yazıldığı için bkz. Validation & Mapping.

4. PermissionPipelineBehavior

IRequirePermission (tekil) uygulayan request’lerde JWT permissions claim’ini doğrudan IHttpContextAccessor üzerinden okur. Bu, API’deki [RequirePermission] attribute’una ek bir defense-in-depth katmanıdır — özellikle Hangfire job’larından veya event handler’lardan tetiklenen command’lerde değerlidir.
public async Task<TResponse> Handle(TRequest request, RequestHandlerDelegate<TResponse> next, CancellationToken ct)
{
    if (request is not IRequirePermission permissionRequest)
        return await next();

    var user = _httpContextAccessor.HttpContext?.User;

    if (user?.Identity?.IsAuthenticated != true)
        throw new DomainException("Bu işlem için kimlik doğrulaması gerekli.");

    var requiredPermission = permissionRequest.RequiredPermission;

    bool hasPermission = user.Claims
        .Where(c => c.Type == "permissions")
        .Any(c => c.Value == requiredPermission || c.Value == "*:*");   // "*:*" = wildcard süper yetki

    if (!hasPermission)
        throw new DomainException($"Bu işlem için '{requiredPermission}' yetkisi gereklidir.");

    return await next();
}
AuthorizationPipelineBehavior (IRequirePermissions, çoğul) ile PermissionPipelineBehavior (IRequirePermission, tekil) iki ayrı marker’dır. İlki ICurrentUserService soyutlamasını ve ForbiddenException (403) üretir; ikincisi ham HttpContext claim’lerini okur ve DomainException (400) fırlatır. Çoğu admin command’i IRequirePermissions’ı tercih eder.

Sonraki adımlar

Authorization marker'ları

IRequirePermissions, IRequireTenantAccess, IRequirePermission detayları.

Validation & Mapping

FluentValidation custom kuralları ve AutoMapper.

Command / Query deseni

Behavior’lardan geçen gerçek handler örnekleri.

Güvenlik

Çift JWT, RBAC ve token doğrulama mimarisi.