Ledbim.AspNetCore 1.2.0

dotnet add package Ledbim.AspNetCore --version 1.2.0
                    
NuGet\Install-Package Ledbim.AspNetCore -Version 1.2.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Ledbim.AspNetCore" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ledbim.AspNetCore" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Ledbim.AspNetCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Ledbim.AspNetCore --version 1.2.0
                    
#r "nuget: Ledbim.AspNetCore, 1.2.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Ledbim.AspNetCore@1.2.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Ledbim.AspNetCore&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Ledbim.AspNetCore&version=1.2.0
                    
Install as a Cake Tool

Ledbim.AspNetCore

Versiyon: 1.2.0  |  Target: .NET 10  |  Bağımlı: Ledbim.Core, Microsoft.AspNetCore.App, Serilog, Asp.Versioning, Scalar

ASP.NET Core projelerinin ihtiyaç duyduğu yatay kesim altyapısını tek pakette sunar: global exception handling, yapısal HTTP logging, MediatR pipeline behavior kayıtları, API versiyonlama, OpenAPI/Scalar entegrasyonu, localization ve kimlik doğrulama yardımcıları. Ledbim.Core'un framework-agnostic davranışlarını HTTP katmanına bağlar.


İçindekiler

  1. Paket Amacı
  2. Paket Kapsamı
  3. Bağımlılıklar
  4. Önemli Yapılar
  5. BaseController
  6. Middleware'ler
  7. Pipeline Behavior'ları
  8. API Versiyonlama
  9. OpenAPI ve Scalar
  10. Serilog Yapılandırması
  11. Background Job Logging
  12. Action Filter'lar
  13. Localization
  14. Güvenlik: ICurrentUserAccessor
  15. Entegrasyon ve DI Kaydı
  16. Tam Program.cs Şablonu
  17. Kullanım Örnekleri
  18. Mimari Notlar
  19. Dikkat Edilmesi Gerekenler

1. Paket Amacı

Bu paket ne işe yarar?

Ledbim.AspNetCore, Ledbim.Core'un sağladığı framework-agnostic kontratları ASP.NET Core'un HTTP pipeline'ına bağlar:

  • ValidationExceptionResult.Fail(BadRequest) dönüşümü (middleware)
  • Request/response body'lerini yapısal log'a yazan, hassas alanları maskeleyen HTTP logging (middleware)
  • MediatR behavior'larının kayıt sırası ve internal olan class'ların dışarıya açılması
  • API versiyonlama — URL segment, query string, header ve media type
  • OpenAPI dokümanları ve Scalar UI; convention-based endpoint dokümantasyonu
  • Kültüre göre enum mesaj çözümleme (localization catalog)
  • HTTP context'ten JWT claim'ini okuyan ICurrentUserAccessor implementasyonu
  • Background job'lar için HTTP logging ile tutarlı Serilog helper

Ne zaman kullanılmalıdır?

ASP.NET Core Web API projelerinde standart olarak eklenir. Ledbim.EntityFramework, Ledbim.Security gibi diğer Ledbim paketleri bu paketin sağladığı altyapıyı (örn. ICurrentUserAccessor) kullanır.


2. Paket Kapsamı

Bu pakette neler var?

Alan İçerik
Controller BaseController — route şablonu, CreateActionResult, CurrentUserId
Action Filter'lar GetCurrentUserIdActionFilter, LocalizationActionFilter
Middleware'ler ExceptionHandlingMiddleware, HttpLoggingMiddleware
Pipeline Behaviors LoggingBehaviour, PerformanceBehaviour, UnhandledExceptionBehaviour (hepsi internal)
DI Uzantıları AddPipelineBehaviors(), AddTransactionalBehavior(), AddApiVersion(), AddOpenApi(), UseMiddlewares(), AddLocalizations()
Logging ConfigureLoggingExtension, BackgroundJobLoggingHelper, UserIdExtensions, CorrelationIdExtensions
Localization ILocaleAccessor, ICultureCatalog, ILocalizedMessages, LocalizedMessages, HttpContextLocaleAccessor
Güvenlik HttpContextCurrentUserAccessor, CurrentUserExtensions
Request Modeli BaseRequestLocalizationCode taşıyıcı

Neler bu paket dışında tutulmuştur?

Alan Nerede bulunur?
JWT token üretimi, BasicAuth handler Ledbim.Security
EF Core, BaseContext Ledbim.EntityFramework
TransactionBehaviour, ValidationBehaviour class'ları Ledbim.Core (kayıt buradan)
StructuredLoggingOptions, LoggingOptions, LogFields Ledbim.Core

3. Bağımlılıklar

NuGet Bağımlılıkları

Paket Versiyon Kullanım Amacı
Asp.Versioning.Mvc 8.1.0 API versiyonlama
Asp.Versioning.Mvc.ApiExplorer 8.1.0 OpenAPI versiyonlama entegrasyonu
Microsoft.AspNetCore.OpenApi 10.0.1 OpenAPI doküman üretimi
Microsoft.OpenApi 2.3.0 OpenAPI model tipleri
Scalar.AspNetCore 2.11.9 Scalar UI
Serilog.AspNetCore 9.0.0 Serilog ASP.NET Core entegrasyonu
Serilog.Sinks.Elasticsearch 9.0.3 Elasticsearch sink (opsiyonel)
Serilog.Sinks.Seq 9.0.0 Seq sink

Ledbim Bağımlılıkları

Paket Kullanım Amacı
Ledbim.Core Result, ICurrentUserAccessor, StructuredLoggingOptions, CQRS marker'ları, pipeline behavior class'ları

4. Önemli Yapılar

Public Class'lar ve Interface'ler

Yapı Tür Sorumluluk
BaseController abstract class Tüm API controller'larının base'i
BaseRequest class LocalizationCode taşıyan request base'i
GetCurrentUserIdActionFilter IAsyncActionFilter Bearer token'dan UserId claim'ini okur, HttpContext.Items'a yazar
LocalizationActionFilter IAsyncActionFilter URL'den locale kodu alır, doğrular, controller ve request'e set eder
HttpContextCurrentUserAccessor ICurrentUserAccessor impl. HTTP context claim'lerinden kullanıcı kimliğini çözer
BackgroundJobLoggingHelper static class Background job'lar için structured Serilog log helper
ILocaleAccessor interface Aktif locale kodu soyutlaması
ICultureCatalog interface Kültür-enum mesaj haritası
ILocalizedMessages interface Enum anahtarından lokalize mesaj döner

Internal (Dolaylı Erişim) Yapılar

Aşağıdaki class'lar internal tanımlıdır; doğrudan referans alınamaz, yalnızca ilgili extension method'lar aracılığıyla DI'a kaydedilir:

Yapı Kayıt Yolu
LoggingBehaviour<,> AddPipelineBehaviors()
PerformanceBehaviour<,> AddPipelineBehaviors()
UnhandledExceptionBehaviour<,> AddPipelineBehaviors()
HttpLoggingMiddleware UseMiddlewares()

5. BaseController

Tüm API controller'larının extend etmesi gereken abstract base class.

[Route("api/v{version:apiVersion}/{localizationCode}/[controller]")]
[ApiController]
public abstract class BaseController : ControllerBase
{
    // HttpContext.Items["CurrentUserId"] üzerinden okunur
    // GetCurrentUserIdActionFilter tarafından set edilir
    protected Guid? CurrentUserId { get; }

    // LocalizationActionFilter tarafından URL segment'ten set edilir
    public string LocalizationCode { get; set; }

    protected IActionResult CreateActionResult(Result result);
    protected IActionResult CreateActionResult<T>(Result<T> result);
}

Route Şablonu

/api/v{version:apiVersion}/{localizationCode}/[controller]

Örnek URL'ler:

GET  /api/v1/tr-tr/orders
POST /api/v2/en-en/customers

CreateActionResult

Handler'dan dönen Result veya Result<T>'yi StatusCode(result.Status, result) olarak HTTP response'a çevirir. ResultStatus enum değeri HTTP status code'una doğrudan map'lenir.

[HttpPost]
public async Task<IActionResult> Create(CreateOrderRequest request, CancellationToken ct)
{
    var result = await _mediator.Send(new CreateOrderCommand(request), ct);
    return CreateActionResult(result);
}

[HttpGet("{id}")]
public async Task<IActionResult> GetById(Guid id, CancellationToken ct)
{
    var result = await _mediator.Send(new GetOrderQuery(id), ct);
    return CreateActionResult(result);
}

CurrentUserId

GetCurrentUserIdActionFilter action'dan önce çalışarak JWT token'daki "UserId" claim'ini parse eder ve HttpContext.Items["CurrentUserId"]'a Guid olarak yazar. BaseController.CurrentUserId bu değeri okur.

// Controller'da
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(Guid id, CancellationToken ct)
{
    var userId = CurrentUserId; // Guid? — filter çalıştıysa dolu
    var result = await _mediator.Send(new DeleteOrderCommand(id, userId), ct);
    return CreateActionResult(result);
}

6. Middleware'ler

Kayıt

app.UseMiddlewares();
// ExceptionHandlingMiddleware → HttpLoggingMiddleware sırası ile kaydedilir

Önerilen middleware sırası (Program.cs):

app.UseRouting();
app.UseMiddlewares();        // Exception + Logging — UseRouting'den sonra
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

ExceptionHandlingMiddleware

Pipeline boyunca fırlatılan tüm Exception'ları yakalar.

Exception Türü HTTP Kodu Response
ValidationException (FluentValidation) 400 Result.Fail(BadRequest, "Validation failed", errors) — hata listesiyle
Diğer tüm exception'lar 500 Result.Fail(InternalServerError, ...) — exception type adı errorCode olarak eklenir

Response her zaman application/json, camelCase, Result formatında döner.

Not: Response zaten başladıysa (context.Response.HasStarted) middleware müdahale etmez; Warning log yazar.

HttpLoggingMiddleware (internal sealed)

Her HTTP request/response için yapısal Serilog log yazar. StructuredLoggingOptions ile davranışı yapılandırılır.

Loglanan alanlar:

Alan Serilog Property Açıklama
Correlation ID correlation.id Activity trace ID veya yeni Guid
User ID user.id JWT claim'den veya "anonymous"
HTTP Method http.method GET, POST, vb.
Path http.path Request path
Query String http.query Key-value nesne olarak
Status Code http.status.code Response HTTP kodu
Elapsed elapsed.ms Millisecond cinsinden süre
Request Body request.body Maskelenmiş, truncate edilmiş
Response Body response.body Maskelenmiş, truncate edilmiş
Message Source message.source Sabit: "Http"

Body okuma kriterleri:

Body yalnızca şu content type'lar için okunur:

  • application/json, application/xml, text/*, *form*
  • multipart/*atlanır

Correlation ID yönetimi:

Request header'da CorrelationHeaderName (default: X-Correlation-Id) varsa kullanılır; yoksa Activity.Current trace ID'sinden alınır veya yeni Guid oluşturulur. Hem request hem response header'ına eklenir.

Path exclusion:

{
  "StructuredLogging": {
    "excludePaths": ["/health", "/metrics", "/openapi", "/scalar"]
  }
}

Bu path'lere gelen istekler hiç loglanmaz.

Body boyutu yönetimi:

Content-Length değeri MaxRequestBodyBytes'ı aşıyorsa body okunmaz; "(content-length X > max Y bytes - skipped)" yazılır. Body okunursa ArrayPool<byte> ile bellek verimli şekilde stream okunur ve "... (truncated)" eklenir.


7. Pipeline Behavior'ları

Kayıt Sırası

services.AddPipelineBehaviors();      // 1-4 aşağıda
services.AddTransactionalBehavior();  // 0 — en dışta (outermost)

Tam pipeline sırası (outermost → innermost):

0. TransactionBehaviour          ← AddTransactionalBehavior() — ICommand/IQueryWithSideEffect için
1. UnhandledExceptionBehaviour   ← exception yakalar, loglar, rethrow eder
2. LoggingBehaviour              ← request/response JSON, elapsed, masked log
3. PerformanceBehaviour          ← 500 ms eşiği aşılırsa Warning
4. ValidationBehaviour           ← FluentValidation; hata → ValidationException
     Handler

Önemli: AddTransactionalBehavior(), AddPipelineBehaviors()'dan sonra çağrılmalıdır. MediatR'da ilk kaydedilen behavior en içte değil en dışta çalışır; TransactionBehaviour outermost olmalıdır.

UnhandledExceptionBehaviour (internal)

Tüm beklenmedik exception'ları yakalar, Serilog Error olarak loglar ve rethrow eder. ExceptionHandlingMiddleware bu exception'ı sonrasında yakalar.

Exception fırlatıldı
  → UnhandledExceptionBehaviour: Log.Error(...) + rethrow
    → LoggingBehaviour: Error log + rethrow
      → ExceptionHandlingMiddleware: HTTP 500 response

LoggingBehaviour (internal)

Her MediatR handler'ı için structured log yazar.

Log alanları:

Alan Değer
correlation.id Activity.Current trace ID'sinden
user.id JWT claim'den veya "anonymous"
request.name typeof(TRequest).Name
elapsed.ms Handler süresi
request.body JSON serialize edilmiş request (maskelenmiş)
response.body JSON serialize edilmiş response (maskelenmiş)
http.status.code HttpContext.Response.StatusCode
message.source Sabit: "MediatR"

Request/response JSON'u MaskHelper ile maskelenir. MaxBodyLength aşılırsa "...(truncated)" eklenir.

PerformanceBehaviour (internal)

Handler süresi 500 ms'yi aşarsa Warning log yazar.

Completed CreateOrderCommand in 734 ms
→ Warning: Long Running Request: CreateOrderCommand took 734 ms. Threshold: 500 ms.

ValidationBehaviour (Ledbim.Core, buradan kayıtlı)

FluentValidation validator'larını çalıştırır. Hata varsa ValidationException fırlatır. ExceptionHandlingMiddleware bunu yakalar ve Result.Fail(BadRequest, errors) olarak döner.


8. API Versiyonlama

AddApiVersion()

services.AddApiVersion();

Yapılandırma:

Ayar Değer
Default version 1.0
Belirtilmezse varsayılan Evet (AssumeDefaultVersionWhenUnspecified = true)
Mevcut versiyonları raporla Evet (ReportApiVersions = true)
GroupNameFormat 'v'VVV (örn. v1, v2)
URL'de versiyon değiştirme Evet (SubstituteApiVersionInUrl = true)

Versiyon okuma yöntemleri (hepsi birlikte aktif):

Yöntem Örnek
URL segment /api/v1/tr-tr/orders
Query string /api/orders?version=1
Header X-Version: 1
Media type Accept: application/json;ver=1

Controller'da Versiyon Tanımlama

[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class OrdersController : BaseController
{
    [HttpGet]
    [MapToApiVersion("1.0")]
    public async Task<IActionResult> GetV1(...) { ... }

    [HttpGet]
    [MapToApiVersion("2.0")]
    public async Task<IActionResult> GetV2(...) { ... }
}

9. OpenAPI ve Scalar

AddOpenApi()

services.AddOpenApi(configuration, openApiDocuments);
// Program.cs
var documents = configuration.GetSection("OpenApi:Documents").Get<string[]>() ?? ["v1"];
builder.Services.AddOpenApi(builder.Configuration, documents);

// appsettings.json
{
  "OpenApi": {
    "Documents": ["v1", "v2"]
  }
}

Endpoint Kayıtları

app.MapOpenApi("/openapi/{documentName}.json");
app.MapScalarApiReference(o => o.AddDocuments(openApiDocuments));

Güvenlik Scheme'leri

AddOpenApi() tüm belgelere otomatik olarak Bearer ve Basic security scheme'lerini ekler ve global security requirement olarak ayarlar. Scalar UI bu sayede "Auth" alanını otomatik sunar.

Bearer: HTTP Bearer scheme, JWT format
Basic:  HTTP Basic scheme

Anonymous endpoint'ler için [AllowAnonymous] attribute'u yeterlidir — global requirement bu endpointlerde UI'da işaretlenmemiş görünür.

Convention-Based Endpoint Dokümantasyonu

Action'da Summary veya Description verilmemişse, OpenApiExtension bunları bir docs class'ından otomatik okumaya çalışır.

Namespace dönüşümü:

Controller namespace:  YourApp.Api.Controllers.v1
Docs namespace:        YourApp.Api.OpenApiDocs.v1

Aday type isimleri (sırayla denenir):

1. {DocsNamespace}.{ControllerName}s.{ControllerName}OpenApiDocs   → plural folder
2. {DocsNamespace}.{ControllerName}.{ControllerName}OpenApiDocs    → singular folder
3. {DocsNamespace}.{ControllerName}OpenApiDocs                     → no folder

Nested type ve field'lar:

// YourApp.Api/OpenApiDocs/v1/Orders/OrderOpenApiDocs.cs
namespace YourApp.Api.OpenApiDocs.v1.Orders;

public static class OrderOpenApiDocs
{
    public static class Create
    {
        public const string Summary = "Sipariş oluşturur";
        public const string Description = """
            JWT Bearer token gerektirir.
            - **CustomerId** (`Guid`): zorunlu
            - **Lines** (`List<OrderLineDto>`): en az 1 satır zorunlu
            """;
    }

    public static class GetById
    {
        public const string Summary = "Sipariş detayını getirir";
    }
}

Hem Summary hem Description action üzerinde zaten doluysa transformer çalışmaz. Yalnızca eksik olanlar doldurulur.


10. Serilog Yapılandırması

ConfigureLoggingExtension

İki overload sunar:

// 1. Basit — yalnızca Console
new LoggerConfiguration()
    .ConfigureLogging()
    .CreateLogger();

// 2. Configuration ile — Console + Seq
new LoggerConfiguration()
    .ConfigureLogging(configuration)
    .CreateLogger();

Her iki overload'da aktif olan ayarlar:

  • Microsoft.* kaynaklarından gelen log'lar Information seviyesine kısıtlanır
  • Microsoft.* ve System.* log'ları filtreyle atlanır
  • Console sink: SystemConsoleTheme.Literate

Configuration overload'unda ek olarak:

  • Seq:ServerUrl config key'inden okunur; yoksa http://localhost:5341 kullanılır
  • Seq sink aktif

Elasticsearch: Kodda mevcuttur ancak varsayılan olarak // yorum satırı — aktif değil.

Program.cs'te Kurulum

using Serilog;

Log.Logger = new LoggerConfiguration()
    .ConfigureLogging(builder.Configuration)
    .CreateLogger();

builder.Host.UseSerilog(Log.Logger);

appsettings.json'da Seq URL

{
  "Seq": {
    "ServerUrl": "http://your-seq-server:5341"
  }
}

11. Background Job Logging

BackgroundJobLoggingHelper, Hangfire, Quartz veya Worker Service gibi background job framework'lerinde HTTP log'larıyla tutarlı yapısal Serilog log üretir.

public static class BackgroundJobLoggingHelper
{
    // Job başlar: correlation ID oluşturur/saklar ve log yazar
    public static string LogJobStart(
        string jobName,
        string? correlationId = null,
        string messageSource = "BackgroundJob");

    // Job tamamlandı: aynı correlation ID ile log yazar
    public static void LogJobCompleted(
        string jobName,
        long elapsedMs,
        string? correlationId = null,
        string messageSource = "BackgroundJob");

    // Job hata aldı: exception ile log yazar
    public static void LogJobFailed(
        string jobName,
        Exception exception,
        long elapsedMs,
        string? correlationId = null,
        string messageSource = "BackgroundJob");
}

Kullanım

public class SendDailyReportJob
{
    public async Task ExecuteAsync(CancellationToken ct)
    {
        var stopwatch = Stopwatch.StartNew();
        var correlationId = BackgroundJobLoggingHelper.LogJobStart("SendDailyReport");

        try
        {
            await _reportService.SendAsync(ct);
            stopwatch.Stop();

            BackgroundJobLoggingHelper.LogJobCompleted(
                "SendDailyReport",
                stopwatch.ElapsedMilliseconds,
                correlationId);
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            BackgroundJobLoggingHelper.LogJobFailed(
                "SendDailyReport",
                ex,
                stopwatch.ElapsedMilliseconds,
                correlationId);
            throw;
        }
    }
}

messageSource özelleştirme:

// Hangfire kullanıyorsanız kaynak etiketini özelleştirin
BackgroundJobLoggingHelper.LogJobStart("CleanupJob", messageSource: "Hangfire");

Correlation ID AsyncLocal<string?> ile saklanır; aynı job içindeki tüm log'lar aynı ID'yi taşır.


12. Action Filter'lar

GetCurrentUserIdActionFilter

Yalnızca Bearer token içeren isteklerde çalışır.

Authorization: Bearer <token>
  ↓
JWT'deki "UserId" claim'i Guid olarak parse et
  ↓ başarılı
HttpContext.Items["CurrentUserId"] = userId (Guid)
  ↓ başarısız veya token yok/geçersiz
HTTP 401 — Result.Fail(Unauthorized, ...)

Basic Auth veya anonymous endpoint'ler bu filter'dan geçse bile kontrol yapılmaz.

Global veya controller bazlı kayıt:

// Global — tüm endpoint'lere uygulanır
builder.Services.AddControllers(options =>
    options.Filters.Add<GetCurrentUserIdActionFilter>());

// Controller bazlı
[ServiceFilter(typeof(GetCurrentUserIdActionFilter))]
public class OrdersController : BaseController { ... }

LocalizationActionFilter

URL'deki {localizationCode} segment'ini doğrular ve dağıtır.

Doğrulama kuralları:

  • Format: ^[a-z]{2}-[a-z]{2}$ (örn. tr-tr, en-en)
  • Desteklenen kodlar: tr-tr, en-en
  • Geçersiz format veya desteklenmeyen kod → HTTP 400

Başarılı durumda:

  • HttpContext.Items["localizationCode"] = kod
  • BaseController.LocalizationCode = kod
  • BaseRequest türevi tüm action argümanlarının LocalizationCode property'si = kod
// Global kayıt
builder.Services.AddControllers(options =>
    options.Filters.Add<LocalizationActionFilter>());

BaseRequest

LocalizationCode gerektiren request DTO'ları BaseRequest'ten türetilir:

public class CreateOrderRequest : BaseRequest
{
    public Guid CustomerId { get; set; }
    public List<OrderLineDto> Lines { get; set; } = [];
}

// Controller
[HttpPost]
public async Task<IActionResult> Create(CreateOrderRequest request, CancellationToken ct)
{
    // request.LocalizationCode → "tr-tr" (filter tarafından set edildi)
    var result = await _mediator.Send(new CreateOrderCommand(request), ct);
    return CreateActionResult(result);
}

13. Localization

Enum anahtarlarından kültüre özgü metin çözümleyen katalog tabanlı localization sistemi.

Interface'ler

ILocalizedMessages

public interface ILocalizedMessages
{
    string Get<TEnum>(TEnum key, params object[] args) where TEnum : Enum;
}

ICultureCatalog

public interface ICultureCatalog
{
    string Culture { get; }
    IReadOnlyDictionary<Type, IReadOnlyDictionary<Enum, string>> Map { get; }
}

ILocaleAccessor

public interface ILocaleAccessor
{
    string Code { get; }
}

Mesaj Çözümleme Mantığı

Get<OrderMessages>(OrderMessages.NotFound)
  ↓
Aktif kültürde ara (ILocaleAccessor.Code)
  ↓ bulunamadı
Default kültürde ara (LocalizationOptions.DefaultCulture = "tr-tr")
  ↓ bulunamadı
key.ToString() — enum adını döner

string.Format() ile parametreler desteklenir; FormatException güvenli şekilde handle edilir.

DI Kaydı

services.AddLocalizations();

// veya default culture yapılandırarak
services.AddLocalizations(options => options.DefaultCulture = "tr-tr");

Kültür Kataloğu Oluşturma

// Domain/Application katmanında
public enum OrderMessages
{
    NotFound,
    AlreadyExists,
    StatusChanged
}

// Infrastructure/Application — TrCatalog
public class TrOrderCatalog : ICultureCatalog
{
    public string Culture => "tr-tr";

    public IReadOnlyDictionary<Type, IReadOnlyDictionary<Enum, string>> Map =>
        new Dictionary<Type, IReadOnlyDictionary<Enum, string>>
        {
            [typeof(OrderMessages)] = new Dictionary<Enum, string>
            {
                [OrderMessages.NotFound]      = "Sipariş bulunamadı.",
                [OrderMessages.AlreadyExists] = "Bu sipariş numarası zaten mevcut.",
                [OrderMessages.StatusChanged] = "Sipariş durumu {0} olarak güncellendi."
            }
        };
}

// EnCatalog
public class EnOrderCatalog : ICultureCatalog
{
    public string Culture => "en-en";

    public IReadOnlyDictionary<Type, IReadOnlyDictionary<Enum, string>> Map =>
        new Dictionary<Type, IReadOnlyDictionary<Enum, string>>
        {
            [typeof(OrderMessages)] = new Dictionary<Enum, string>
            {
                [OrderMessages.NotFound]      = "Order not found.",
                [OrderMessages.AlreadyExists] = "This order number already exists.",
                [OrderMessages.StatusChanged] = "Order status updated to {0}."
            }
        };
}

// DI kaydı
services.AddSingleton<ICultureCatalog, TrOrderCatalog>();
services.AddSingleton<ICultureCatalog, EnOrderCatalog>();
services.AddLocalizations();

Handler'da Kullanım

internal sealed class GetOrderQueryHandler(
    IUnitOfWork uow,
    ILocalizedMessages messages) : IRequestHandler<GetOrderQuery, Result<OrderDto>>
{
    public async Task<Result<OrderDto>> Handle(GetOrderQuery request, CancellationToken ct)
    {
        var order = await uow.OrderRepository.FirstOrDefaultAsync(
            new GenericExpression<Order>(x => x.Id == request.OrderId), ct);

        if (order is null)
            return Result<OrderDto>.Fail(
                ResultStatus.NotFound,
                messages.Get(OrderMessages.NotFound));

        return Result<OrderDto>.Success(ResultStatus.Success, order.ToDto());
    }
}

14. Güvenlik: ICurrentUserAccessor

HttpContextCurrentUserAccessor

ICurrentUserAccessor'ın HTTP context implementasyonu. Claim okuma sırası:

1. "UserName" claim
2. ClaimTypes.Name
3. ClaimTypes.NameIdentifier

İlk bulunan değeri döner; hiçbiri yoksa null.

DI kaydı:

services.AddScoped<ICurrentUserAccessor, HttpContextCurrentUserAccessor>();

Bu kayıt, Ledbim.EntityFramework'ün BaseContext'inin audit trail'de kullanıcı bilgisini doldurabilmesi için gereklidir.


15. Entegrasyon ve DI Kaydı

Extension Method Özeti

Method Konum Açıklama
services.AddPipelineBehaviors() PipelineBehaviorExtension 4 behavior kaydeder (UnhandledException, Logging, Performance, Validation)
services.AddTransactionalBehavior() PipelineBehaviorExtension TransactionBehaviour outermost olarak ekler; ITransactionManager gerektirir
services.AddApiVersion() ApiVersionExtension URL/query/header/mediatype versiyonlama
services.AddOpenApi(config, docs) OpenApiExtension Çoklu doküman, Bearer+Basic scheme, convention-based docs
services.AddLocalizations() Localization.DependencyInjection Locale + mesaj çözümleme
services.AddScoped<ICurrentUserAccessor, HttpContextCurrentUserAccessor>() HTTP context'ten kullanıcı çözümleme
app.UseMiddlewares() MiddlewareExtension ExceptionHandling + HttpLogging middleware

16. Tam Program.cs Şablonu

using Ledbim.AspNetCore.Extensions;
using Ledbim.AspNetCore.Logging;
using Ledbim.AspNetCore.Security.CurrentUser;
using Ledbim.Core.DomainEvents;
using Ledbim.Core.Security.CurrentUser;
using Scalar.AspNetCore;
using Serilog;

// Serilog erken başlatma
Log.Logger = new LoggerConfiguration()
    .ConfigureLogging(builder.Configuration)
    .CreateLogger();

var builder = WebApplication.CreateBuilder(args);
builder.Host.UseSerilog(Log.Logger);

// ── Controllers ──────────────────────────────────────────────────
builder.Services.AddControllers(options =>
{
    options.Filters.Add<LocalizationActionFilter>();
    options.Filters.Add<GetCurrentUserIdActionFilter>();
});

// ── API Versioning ────────────────────────────────────────────────
builder.Services.AddApiVersion();

// ── OpenAPI ───────────────────────────────────────────────────────
var openApiDocuments = builder.Configuration
    .GetSection("OpenApi:Documents")
    .Get<string[]>() ?? ["v1"];
builder.Services.AddOpenApi(builder.Configuration, openApiDocuments);

// ── MediatR ──────────────────────────────────────────────────────
builder.Services.AddMediatR(cfg =>
    cfg.RegisterServicesFromAssembly(typeof(SomeHandler).Assembly));

// ── FluentValidation ─────────────────────────────────────────────
builder.Services.AddValidatorsFromAssembly(typeof(SomeValidator).Assembly);

// ── Pipeline Behaviors ───────────────────────────────────────────
builder.Services.AddPipelineBehaviors();        // UnhandledException + Logging + Performance + Validation
builder.Services.AddTransactionalBehavior();    // TransactionBehaviour (outermost) — ITransactionManager gerektirir

// ── Logging Options ──────────────────────────────────────────────
builder.Services.Configure<StructuredLoggingOptions>(
    builder.Configuration.GetSection("StructuredLogging"));

// ── Current User ─────────────────────────────────────────────────
builder.Services.AddScoped<ICurrentUserAccessor, HttpContextCurrentUserAccessor>();

// ── Domain Events ────────────────────────────────────────────────
builder.Services.AddScoped<IDomainEventCollector, DomainEventCollector>();

// ── Localization (isteğe bağlı) ──────────────────────────────────
builder.Services.AddLocalizations();
builder.Services.AddSingleton<ICultureCatalog, TrOrderCatalog>();
builder.Services.AddSingleton<ICultureCatalog, EnOrderCatalog>();

// ── UnitOfWork (proje-özgü) ───────────────────────────────────────
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<ITransactionManager>(
    sp => sp.GetRequiredService<IUnitOfWork>());

// ── JWT (Ledbim.Security) ─────────────────────────────────────────
builder.Services.AddJwtAuthentication(builder.Configuration);

var app = builder.Build();

// ── Middleware Pipeline ───────────────────────────────────────────
app.MapOpenApi("/openapi/{documentName}.json");
app.MapScalarApiReference(o => o.AddDocuments(openApiDocuments));

app.UseRouting();
app.UseMiddlewares();           // Exception + HTTP Logging
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();

17. Kullanım Örnekleri

Minimal Controller

[ApiVersion("1.0")]
public class OrdersController(IMediator mediator) : BaseController
{
    [HttpPost]
    public async Task<IActionResult> Create(
        CreateOrderRequest request, CancellationToken ct)
        => CreateActionResult(await mediator.Send(new CreateOrderCommand(request), ct));

    [HttpGet("{id:guid}")]
    public async Task<IActionResult> GetById(Guid id, CancellationToken ct)
        => CreateActionResult(await mediator.Send(new GetOrderQuery(id), ct));

    [HttpGet]
    public async Task<IActionResult> List(
        [FromQuery] PaginationFilterQuery filterQuery, CancellationToken ct)
        => CreateActionResult(await mediator.Send(new ListOrdersQuery(filterQuery), ct));
}

Convention-Based OpenAPI Docs

// YourApp.Api/OpenApiDocs/v1/Orders/OrderOpenApiDocs.cs
namespace YourApp.Api.OpenApiDocs.v1.Orders;

public static class OrderOpenApiDocs
{
    public static class Create
    {
        public const string Summary = "Yeni sipariş oluşturur";
        public const string Description = """
            JWT Bearer token zorunludur.

            **Validasyon kuralları:**
            - `customerId`: zorunlu, geçerli müşteri ID'si
            - `lines`: en az 1 satır içermeli
            """;
    }

    public static class GetById
    {
        public const string Summary = "Sipariş detayını getirir";
        public const string Description = "Belirtilen ID'ye sahip siparişin tüm bilgilerini döner.";
    }
}

Background Job Logging (Hangfire ile)

[AutomaticRetry(Attempts = 3)]
public async Task ProcessDailyReports(CancellationToken ct)
{
    var sw = Stopwatch.StartNew();
    var correlationId = BackgroundJobLoggingHelper.LogJobStart(
        "ProcessDailyReports",
        messageSource: "Hangfire");
    try
    {
        await _reportService.ProcessAsync(ct);
        BackgroundJobLoggingHelper.LogJobCompleted(
            "ProcessDailyReports", sw.ElapsedMilliseconds, correlationId,
            messageSource: "Hangfire");
    }
    catch (Exception ex)
    {
        BackgroundJobLoggingHelper.LogJobFailed(
            "ProcessDailyReports", ex, sw.ElapsedMilliseconds, correlationId,
            messageSource: "Hangfire");
        throw;
    }
}

18. Mimari Notlar

Bu Paket Hangi Katmana Aittir?

Ledbim.AspNetCore, katmanlı mimaride yalnızca API katmanına doğrudan bağımlı olmalıdır:

Domain           → Ledbim.Core
Application      → Ledbim.Core
Infrastructure   → Ledbim.EntityFramework, Ledbim.Core
API              → Ledbim.AspNetCore, Ledbim.Security, Ledbim.Core

Pipeline Behavior Bölünme Kriteri

Behavior Paket Neden
ValidationBehaviour Ledbim.Core IValidator<T> bağımlılığı; HTTP context gerektirmez
TransactionBehaviour Ledbim.Core ITransactionManager bağımlılığı; HTTP context gerektirmez
LoggingBehaviour Ledbim.AspNetCore IHttpContextAccessor, Serilog, StructuredLoggingOptions bağımlılığı
PerformanceBehaviour Ledbim.AspNetCore IHttpContextAccessor, Serilog bağımlılığı
UnhandledExceptionBehaviour Ledbim.AspNetCore Serilog bağımlılığı

19. Dikkat Edilmesi Gerekenler

Durum Açıklama
AddTransactionalBehavior() sırası AddPipelineBehaviors()'dan sonra çağrılmalıdır. MediatR'da sıra önemlidir — ilk kayıtlı = outermost.
UseMiddlewares() konumu UseRouting()'den sonra, UseAuthentication()'dan önce olmalıdır. Yanlış sıra correlation ID eksikliğine ve auth hatalarının yanlış loglanmasına yol açar.
HTTP-aware behavior'lar internal typeof(LoggingBehaviour<,>) gibi doğrudan referans alınamaz. Yalnızca AddPipelineBehaviors() ile kayıt yapılır.
HttpLoggingMiddleware internal sealed UseMiddlewares() dışında app.UseMiddleware<HttpLoggingMiddleware>() çağrılamaz.
Desteklenen locale kodları LocalizationActionFilter'da "tr-tr" ve "en-en" hardcoded. Yeni dil eklenmesi için filter kaynağında Supported kümesi güncellenmeli.
GetCurrentUserIdActionFilter yalnızca Bearer Authorization: Bearer header'ı yoksa filter geçişine izin verir. Basic auth veya anonim endpoint'lerde CurrentUserId null olabilir.
Seq varsayılan URL ConfigureLogging(config) overload'unda Seq:ServerUrl config key yoksa http://localhost:5341 kullanılır. Prod ortamında config key tanımlanmalıdır.
Elasticsearch sink pasif Kodda mevcut ama // yorum satırı — aktif değil. Aktifleştirmek için ConfigureLoggingExtension.cs'deki yorum kaldırılmalı ve bağlantı bilgisi yapılandırılmalıdır.
StructuredLoggingOptions kaydı Configure<StructuredLoggingOptions>(...) çağrılmazsa HttpLoggingMiddleware varsayılan seçeneklerle çalışır (sensitive data maskeleme aktif, excludePaths boş).
Convention-based docs namespace Controller namespace'inde .Controllers. bulunmazsa OperationTransformer çalışmaz. Namespace şeması X.Api.Controllers.v1 formatında olmalıdır.
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 174 3/28/2026