Ledbim.Core 1.2.0

dotnet add package Ledbim.Core --version 1.2.0
                    
NuGet\Install-Package Ledbim.Core -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.Core" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ledbim.Core" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Ledbim.Core" />
                    
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.Core --version 1.2.0
                    
#r "nuget: Ledbim.Core, 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.Core@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.Core&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Ledbim.Core&version=1.2.0
                    
Install as a Cake Tool

Ledbim.Core

Versiyon: 1.2.0  |  Target: .NET 10  |  Bağımlılık: Yok (Ledbim ekosisteminin köküdür)

Ledbim ekosisteminin framework-agnostic temel altyapı kütüphanesi. ASP.NET Core, EF Core veya herhangi bir transport teknolojisine doğrudan bağımlı olmaksızın; Clean Architecture uygulamalarının ortak ihtiyaçlarını — CQRS, domain event'ler, result pattern, pipeline behavior'lar, pagination ve yardımcı araçlar — tek bir yerden standartlaştırır.


İçindekiler

  1. Paket Amacı
  2. Paket Kapsamı
  3. Bağımlılıklar
  4. Önemli Yapılar
  5. CQRS Marker Interface'leri
  6. Result Pattern
  7. Entity Modeli
  8. Domain Events
  9. Pipeline Behavior'ları
  10. Messaging (IIntegrationEventBus)
  11. Pagination, Filter ve Sort
  12. Yardımcı Araçlar (Helpers)
  13. Logging Seçenekleri
  14. Güvenlik: ICurrentUserAccessor
  15. Entegrasyon ve DI Kaydı
  16. Kullanım Örnekleri
  17. Mimari Notlar
  18. Dikkat Edilmesi Gerekenler
  19. Hızlı Başlangıç

1. Paket Amacı

Bu paket ne işe yarar?

Ledbim.Core, birden fazla .NET projesinde tekrar kullanılmak üzere tasarlanmış temel kontratları ve altyapıyı barındırır:

  • Tüm handler'ların dönüş tipi olarak kullandığı Result pattern
  • MediatR pipeline üzerinden transaction ve validation'ı otomatikleştiren CQRS marker interface'leri
  • Aggregate'lerin yayınladığı event'lerin commit sonrasında dispatch edilmesini sağlayan domain event altyapısı
  • Audit alanları ve soft delete davranışını tüm entity'lere kazandıran base entity hiyerarşisi
  • Gelişmiş filtreleme, sıralama ve sayfalama için pagination modelleri
  • JSON maskeleme, enum yardımcıları ve timezone dönüşümleri için helper araçlar

Hangi problemi çözer?

Her projenin sıfırdan yazması gereken standart altyapı kodunu ortadan kaldırır. Result<T> sözleşmesi, transaction yönetimi veya audit trail için projeye özgü çözümler üretmek yerine bu paketi eklemek yeterlidir.

Ne zaman kullanılmalıdır?

Clean Architecture yapısındaki her projede. Ledbim.Core, diğer tüm Ledbim paketlerinin dayandığı köktür; bu nedenle herhangi bir Ledbim paketini kullanıyorsanız bu paket zaten bağımlılık zincirinde yer alır.


2. Paket Kapsamı

Bu pakette neler var?

Alan İçerik
CQRS ICommand, IQuery, IQueryWithSideEffect marker interface'leri ve generic varyantları
Result Pattern Result, Result<T>, ResultStatus, ErrorResult, PagedResponse<T>
Entity Hiyerarşisi BaseEntity, BaseEntity<TKey>, AggregateRoot<TKey>, IHardDelete, IAggregateRoot
Domain Events IDomainEventCollector, DomainEventCollector, AggregateRoot.AddDomainEvent()
Transaction Kontratı ITransactionManager (pipeline-internal)
Messaging Kontratı IIntegrationEventBus (transport-agnostic)
Pipeline Behaviors TransactionBehaviour, ValidationBehaviour
Pagination PaginationFilterQuery, FilterColumnQuery, FilterGroupQuery, SortQuery, PagedModel<T>, PagedResponse<T>, PagedHelper
Expression Builder PredicateBuilder (LINQ AND/OR kombinasyonları)
Helpers EnumHelper, MaskHelper, TimeZoneHelper, Utils
Logging Modelleri LogFields, LoggingOptions, StructuredLoggingOptions, CorrelationIdExtensions
Güvenlik Kontratı ICurrentUserAccessor, CurrentUserConstants

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

Alan Nerede bulunur?
HTTP middleware, BaseController Ledbim.AspNetCore
EF Core BaseContext, Repository Ledbim.EntityFramework
JWT, BasicAuth implementasyonu Ledbim.Security
MassTransit/RabbitMQ entegrasyonu Ledbim.Messaging
MongoDB repository Ledbim.Mongo
FTP dosya servisi Ledbim.Storage
Typed HTTP client Ledbim.Http
Pipeline behavior DI kaydı Ledbim.AspNetCore

3. Bağımlılıklar

NuGet Bağımlılıkları

Paket Versiyon Kullanım Amacı
MediatR 14.1.0 IRequest, INotification, IPipelineBehavior kontratları
FluentValidation 12.1.1 ValidationBehaviour için validator desteği

Ledbim Bağımlılıkları

Yok. Ledbim.Core, ekosistemde hiçbir başka Ledbim paketine bağımlı değildir.


4. Önemli Yapılar

Public Interface'ler

Interface Sorumluluk
ICommand / ICommand<TResponse> Write operasyonları; TransactionBehaviour tarafından otomatik transaction'a alınır
IQuery / IQuery<TResponse> Read-only operasyonlar; transaction bypass edilir
IQueryWithSideEffect / IQueryWithSideEffect<TResponse> Atomic okuma+yazma gerektiren nadir senaryolar; transaction'a alınır
IAggregateRoot Domain event koleksiyonu tutan aggregate'leri işaretler
IHardDelete Fiziksel silme yapılmasını isteyen entity'leri işaretler
ITransactionManager BeginTransactionAsync, CommitAsync, RollbackAsync — pipeline-internal, handler'lar inject etmez
IDomainEventCollector Request scope boyunca toplanan domain event'leri saklar ve temizler
IIntegrationEventBus Transport-agnostic cross-service mesaj yayınlama (PublishAsync, SendAsync)
ICurrentUserAccessor Authenticated kullanıcı kimliğini döner; HTTP context'ten bağımsız soyutlama

Public Base Class'lar

Sınıf Sorumluluk
BaseEntity 7 audit alanı + IsDeleted + Restore() metodu
BaseEntity<TKey> Strongly-typed Id alanı ekler; where TKey : IEquatable<TKey>
AggregateRoot<TKey> BaseEntity<TKey> + IAggregateRoot; AddDomainEvent(), ClearDomainEvents()
DomainEventCollector IDomainEventCollector implementasyonu; scoped lifetime

Pipeline Behavior'ları

Behavior Kayıt Sırası Tetikleyici Sorumluluk
ValidationBehaviour Innermost Tüm IRequest'ler FluentValidation'ı çalıştırır; hata varsa ValidationException
TransactionBehaviour Outermost ICommand | IQueryWithSideEffect Transaction aç/kapat; commit sonrası domain event dispatch et

Not: Bu behavior'ların DI kaydı (AddPipelineBehaviors(), AddTransactionalBehavior()) Ledbim.AspNetCore paketindedir.


5. CQRS Marker Interface'leri

Temel Ayrım

// Write — pipeline transaction açar
public record CreateOrderCommand(Guid CustomerId, decimal Amount)
    : IRequest<Result>, ICommand;

// Read-only — transaction bypass
public record GetOrderQuery(Guid OrderId)
    : IRequest<Result<OrderDto>>, IQuery;

// Atomic okuma + yazma (nadir kullanım)
public record MarkNotificationsReadQuery(Guid UserId)
    : IRequest<Result<List<NotificationDto>>>, IQueryWithSideEffect;

Generic Varyantlar

// ICommand<TResponse> = IRequest<TResponse> + ICommand
public record CreateOrderCommand(...) : ICommand<Result>;

// IQuery<TResponse> = IRequest<TResponse> + IQuery
public record GetOrderQuery(...) : IQuery<Result<OrderDto>>;

TransactionBehaviour Karar Matrisi

Request türü Transaction Davranış
ICommand Açılır Handler başarılıysa commit + domain event dispatch; exception'da rollback
IQueryWithSideEffect Açılır Aynı ICommand davranışı
IQuery Açılmaz Pass-through
Diğer IRequest'ler Açılmaz Pass-through

Nested Transaction Desteği

TransactionBehaviour, ITransactionManager.HasActiveTransaction kontrolü yaparak iç içe handler çağrılarında yalnızca en dıştaki transaction'ın commit/rollback yapmasını sağlar.

// Outer handler: transaction açar ve kapatır
// Inner handler (başka bir command): zaten aktif transaction bulur, kendi transaction açmaz
await mediator.Send(new OuterCommand());

6. Result Pattern

ResultStatus Enum

public enum ResultStatus
{
    Success              = 200,
    Created              = 201,
    NoContent            = 204,
    BadRequest           = 400,
    Unauthorized         = 401,
    Forbidden            = 403,
    NotFound             = 404,
    Conflict             = 409,
    UnprocessableEntity  = 422,
    TooManyRequests      = 429,
    InternalServerError  = 500
}

Result (veri taşımayan)

public class Result
{
    public bool IsSuccess { get; init; }
    public string? Message { get; init; }
    public List<ErrorResult>? Errors { get; init; }
    [JsonIgnore] public int StatusCode { get; init; }
    [JsonIgnore] public ResultStatus Status { get; init; }

    // Başarı factory'leri — yalnızca 200/201/204 ile çalışır
    public static Result Success(ResultStatus status, string? message = null);

    // Hata factory'leri — yalnızca 4xx/5xx ile çalışır
    public static Result Fail(ResultStatus status, string? message = null);
    public static Result Fail(ResultStatus status, string? message, List<ErrorResult> errors);
}

Kritik kural: Success() ile BadRequest gibi bir hata kodu verilirse ArgumentException fırlatılır. Aynı şekilde Fail() ile Success (200) verilirse de fırlatılır. Bu kontrat zorunludur.

Result<T> (veri taşıyan)

public class Result<T> : Result
{
    public T? Data { get; init; }

    public static Result<T> Success(ResultStatus status, T data, string? message = null);
    public new static Result<T> Fail(ResultStatus status, string? message = null);
    public new static Result<T> Fail(ResultStatus status, string? message, List<ErrorResult> errors);

    // Null-safe veri erişimi
    public bool TryGetValue(out T? value);
}

ErrorResult

public class ErrorResult
{
    public string? PropertyName { get; init; }   // Hatalı alan adı
    public string? ErrorMessage { get; init; }   // Hata açıklaması
    public string? ErrorCode { get; init; }      // İsteğe bağlı kod
}

PagedResponse<T> (sayfalanmış sonuç)

Result<T>'den türer; sayfalama metadata'sını ekler.

public class PagedResponse<T> : Result<T>
{
    public int TotalPages { get; init; }
    public int TotalRecords { get; init; }
    public int PageNumber { get; init; }
    public int PageSize { get; init; }

    // Boş sayfalı başarısız sonuç
    public static PagedResponse<IEnumerable<T>> EmptyPagedResponse();
    public static PagedResponse<IEnumerable<T>> FailEmptyPagedResponse();
}

Handler'da Kullanım

// Başarı
return Result.Success(ResultStatus.Created, "Sipariş oluşturuldu.");
return Result<OrderDto>.Success(ResultStatus.Success, dto);

// Hata
return Result.Fail(ResultStatus.NotFound, "Sipariş bulunamadı.");
return Result.Fail(ResultStatus.Conflict, "Bu referans numarası zaten kullanılmaktadır.");

// Validasyon hataları ile
return Result.Fail(ResultStatus.BadRequest, "Girdi hatası.", errors);

// PagedResponse
return PagedHelper.CreatePagedResponse(items, totalCount, request.PageNumber, request.PageSize);

7. Entity Modeli

BaseEntity

public abstract class BaseEntity
{
    [Column(Order = 95)] public DateTime? CreatedDate { get; set; }
    [Column(Order = 96)] public string? CreatedBy { get; set; }
    [Column(Order = 97)] public DateTime? UpdatedDate { get; set; }
    [Column(Order = 98)] public string? UpdatedBy { get; set; }
    [Column(Order = 99)] public bool IsDeleted { get; set; }
    [Column(Order = 100)] public DateTime? DeletedDate { get; set; }
    [Column(Order = 101)] public string? DeletedBy { get; set; }

    // Soft delete'i geri al
    public void Restore();
}

BaseEntity<TKey>

public abstract class BaseEntity<TKey> : BaseEntity
    where TKey : IEquatable<TKey>
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Column(Order = 0)]
    public TKey Id { get; set; } = default!;
}

Tipik kullanım: public class Order : BaseEntity<Guid> veya BaseEntity<int>

AggregateRoot<TKey>

Domain event desteği gerektiren aggregate'ler BaseEntity<TKey> yerine bunu extend eder.

public abstract class AggregateRoot<TKey> : BaseEntity<TKey>, IAggregateRoot
    where TKey : IEquatable<TKey>
{
    private readonly List<INotification> _domainEvents = [];

    [NotMapped]
    public IReadOnlyList<INotification> DomainEvents => _domainEvents.AsReadOnly();

    protected void AddDomainEvent(INotification @event) => _domainEvents.Add(@event);
    public void ClearDomainEvents() => _domainEvents.Clear();
}

IHardDelete

Bu interface'i implement eden entity'ler soft delete yerine fiziksel olarak silinir.

public interface IHardDelete { }

// Kullanım
public class AuditLog : BaseEntity<Guid>, IHardDelete { ... }

Ne Zaman Hangi Base Class Kullanılır?

Senaryo Kullanılacak Base
Standart entity (Id + audit + soft delete) BaseEntity<TKey>
Domain event yayınlaması gereken aggregate AggregateRoot<TKey>
Fiziksel silinmesi gereken entity BaseEntity<TKey> + IHardDelete

8. Domain Events

Kavramsal Akış

1. Business method, entity üzerinde çağrılır:
       order.Confirm()
         → Status = Confirmed
         → AddDomainEvent(new OrderConfirmedEvent(Id, OccurredAt))

2. Handler, repository üzerinden sadece change tracker'ı günceller:
       await uow.OrderRepository.UpdateAsync(order);
       // SaveChanges YOKTUR — pipeline halleder

3. TransactionBehaviour commit aşamasında:
       context.SaveChangesAsync()
         → BaseContext.CollectDomainEvents()
             → Entity listesindeki event'ler IDomainEventCollector'a taşınır
             → Entity'nin event listesi temizlenir
       transaction.CommitAsync()

4. Commit sonrası TransactionBehaviour:
       foreach event in collector.GetNotifications()
           await publisher.Publish(event)
           // Hata → LogWarning (exception throw edilmez)
       collector.Clear()

5. Rollback veya exception durumunda:
       collector.Clear()  ← event'ler dispatch edilmez

IDomainEventCollector

public interface IDomainEventCollector
{
    void AddNotification(INotification notification);
    IReadOnlyList<INotification> GetNotifications();
    void Clear();
}

DI kaydı: Scoped — her HTTP request için ayrı instance.

services.AddScoped<IDomainEventCollector, DomainEventCollector>();

Event Tanımlama

// Domain katmanı — Aggregate klasörü altında Events/
public record OrderConfirmedEvent(Guid OrderId, DateTime OccurredAt) : INotification;

Event Handler

// Application katmanı
public class OrderConfirmedEventHandler(IEmailService emailService)
    : INotificationHandler<OrderConfirmedEvent>
{
    public async Task Handle(OrderConfirmedEvent @event, CancellationToken ct)
        => await emailService.SendConfirmationAsync(@event.OrderId, ct);
}

IAggregateRoot ile IDomainEventCollector Farkı

AggregateRoot<TKey> kullanan entity'lerde event'ler otomatik olarak IDomainEventCollector'a aktarılır; handler'ın buna dokunması gerekmez. AggregateRoot kullanılmıyorsa handler içinde doğrudan IDomainEventCollector.AddNotification() çağrılabilir.


9. Pipeline Behavior'ları

ValidationBehaviour

MediatR pipeline'ına FluentValidation entegre eder.

Akış:

  1. Request için kayıtlı tüm IValidator<TRequest> instance'ları toplanır
  2. Tüm validator'lar paralel execute edilir
  3. Herhangi bir hata varsa ValidationException fırlatılır (Ledbim.AspNetCore bunu yakalar → Result.Fail(BadRequest))
  4. Hata yoksa handler çağrılır
// Validator tanımı
public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
    public CreateOrderCommandValidator()
    {
        RuleFor(x => x.CustomerId).NotEmpty();
        RuleFor(x => x.Amount).GreaterThan(0);
    }
}

TransactionBehaviour

ICommand ve IQueryWithSideEffect request'lerini otomatik olarak transaction içine alır.

Akış:

Request → ICommand veya IQueryWithSideEffect mi?
    Evet → HasActiveTransaction?
        Hayır → BeginTransactionAsync()
    Handler çalıştır
    IsSuccess == true?
        Evet → CommitAsync()
              → Domain event'leri dispatch et
        Hayır → RollbackAsync()
              → Event'leri temizle
    Exception?
        → RollbackAsync()
        → Event'leri temizle
        → Rethrow

Kayıt (Ledbim.AspNetCore üzerinden):

// addTransactionalBehavior, ITransactionManager gerektirir
services.AddTransactionalBehavior();

// ITransactionManager, projeye özgü IUnitOfWork üzerinden sağlanır
services.AddScoped<ITransactionManager>(sp => sp.GetRequiredService<IUnitOfWork>());

ITransactionManager

public interface ITransactionManager
{
    bool HasActiveTransaction { get; }
    Task BeginTransactionAsync(CancellationToken ct = default);
    Task CommitAsync(CancellationToken ct = default);
    Task RollbackAsync(CancellationToken ct = default);
}

Handler'lar bu interface'i inject etmez. Yalnızca TransactionBehaviour kullanır. Projeye özgü IUnitOfWork bu interface'i extend etmeli ve repository'leri eklemeli:

// Application katmanı — projeye özgü
public interface IUnitOfWork : ITransactionManager
{
    IOrderRepository OrderRepository { get; }
    ICustomerRepository CustomerRepository { get; }
}

10. Messaging (IIntegrationEventBus)

Cross-service mesajlaşma için transport-agnostic kontrat.

public interface IIntegrationEventBus
{
    // Publish/fanout: tüm subscriber'lara iletilir (RabbitMQ fanout/topic exchange)
    Task PublishAsync<T>(T message, CancellationToken ct = default)
        where T : class;

    // Send/point-to-point: belirli bir kuyruğa gönderilir
    Task SendAsync<T>(T message, Uri queueAddress, CancellationToken ct = default)
        where T : class;
}

Implementasyon: Ledbim.Messaging paketindeki MassTransitIntegrationEventBus.

Domain event'ten farkı: Domain event'ler aynı process içinde (commit sonrası) dispatch edilir. Integration event'ler servisler arası iletişim için kullanılır ve bir mesaj broker'ı üzerinden iletilir.

// Handler'da integration event yayınlama
public class OrderShippedCommandHandler(IIntegrationEventBus bus) : IRequestHandler<...>
{
    public async Task<Result> Handle(OrderShippedCommand request, CancellationToken ct)
    {
        // ... iş mantığı ...
        await bus.PublishAsync(new OrderShippedIntegrationEvent(request.OrderId), ct);
        return Result.Success(ResultStatus.Success);
    }
}

11. Pagination, Filter ve Sort

PaginationFilterQuery

Standart sayfalama + filtreleme + sıralama isteği:

public class PaginationFilterQuery
{
    public int PageNumber { get; set; } = 1;
    public int PageSize { get; set; } = 10;
    public List<FilterColumnQuery>? FilterItems { get; set; }
    public FilterGroupQuery? FilterGroup { get; set; }
    public string FilterLogic { get; set; } = "and";  // "and" | "or"
    public List<SortQuery>? Sorts { get; set; }
}

FilterColumnQuery

public class FilterColumnQuery
{
    public string? ColumnField { get; set; }    // Alan adı: "name", "status"
    public string? OperatorValue { get; set; }  // "equals", "contains", "greaterThan", ...
    public string? Value { get; set; }          // Karşılaştırma değeri
}

Desteklenen operatörler: equals, is, not, contains, startsWith, endsWith, greaterThan, greaterThanOrEqual, lessThan, lessThanOrEqual, isEmpty, isNotEmpty

FilterGroupQuery

Parantezli ve nested filtre grupları için:

public class FilterGroupQuery
{
    public bool Negate { get; set; }                    // NOT operatörü
    public string Logic { get; set; } = "and";          // "and" | "or"
    public List<FilterColumnQuery>? Items { get; set; }
    public List<FilterGroupQuery>? Groups { get; set; } // Nested gruplar
}

SortQuery

public class SortQuery
{
    public string? Field { get; set; }  // "createdDate", "name"
    public string? Sort { get; set; }   // "asc" | "desc"
}

PagedModel<T>

Repository katmanından dönen ham sayfalanmış veri:

public class PagedModel<T>(IReadOnlyList<T> pagedData, int totalRecords)
{
    public int TotalRecords { get; init; }
    public IReadOnlyList<T> PagedData { get; init; }
}

PagedHelper

API response'u oluşturmak için:

// Handler içinde
var pagedResult = await uow.OrderRepository.GetAllPagedAsync(filterRequest);

return PagedHelper.CreatePagedResponse(
    data: pagedResult.PagedData,
    totalRecords: pagedResult.TotalRecords,
    pageNumber: request.FilterQuery.PageNumber,
    pageSize: request.FilterQuery.PageSize);

Güvenli Filter Kullanımı

Client'tan gelen alan adlarını doğrudan entity property'lerine bağlamayın. Bir allow-list ile sınırlayın:

private static readonly HashSet<string> _allowedFields = ["name", "createdDate", "status"];

// Handler başında kontrol et
var invalidFields = request.FilterQuery.FilterItems?
    .Where(f => !_allowedFields.Contains(f.ColumnField))
    .ToList();
if (invalidFields?.Count > 0)
    return Result.Fail(ResultStatus.BadRequest, "Geçersiz filtre alanı.");

12. Yardımcı Araçlar (Helpers)

EnumHelper

Enum'ları reflection ve DisplayAttribute ile çalıştıran, ConcurrentDictionary ile cache'leyen yardımcı.

// DisplayAttribute okuma
string name = EnumHelper.GetDisplayName(OrderStatus.Confirmed);       // "Onaylandı"
string shortName = EnumHelper.GetDisplayShortName(OrderStatus.Confirmed);
string description = EnumHelper.GetDisplayDescription(OrderStatus.Confirmed);

// Custom attribute okuma
var attr = EnumHelper.GetAttribute<OrderStatus, MyAttribute>(OrderStatus.Confirmed);

// UI/dropdown için liste
IEnumerable<(OrderStatus Value, string DisplayName)> items = EnumHelper.GetDisplayItems<OrderStatus>();

// Parse
bool parsed = EnumHelper.TryParse("confirmed", out OrderStatus status, ignoreCase: true);
bool isDefined = EnumHelper.IsDefined(OrderStatus.Confirmed);

// Sayısal dönüşüm
int intVal = EnumHelper.ToInt32(OrderStatus.Confirmed);
long longVal = EnumHelper.ToInt64(OrderStatus.Confirmed);

TimeZoneHelper

Türkiye timezone işlemleri; Windows ve Linux/Mac platform farklılıklarını otomatik çözer.

// DateTime → Türkiye saati
DateTime turkeyTime = DateTime.UtcNow.ToTurkeyTime();

// DateTime → DateTimeOffset (Türkiye offset'i ile)
DateTimeOffset offset = DateTime.UtcNow.ToTurkeyOffset();

// Hangfire gibi kütüphaneler için TimeZoneInfo
TimeZoneInfo tz = TimeZoneHelper.GetTurkeyTimeZoneInfo();

PredicateBuilder

Birden fazla Expression<Func<T, bool>> expression'ını AND veya OR ile birleştirmek için:

Expression<Func<Order, bool>> byCustomer = o => o.CustomerId == customerId;
Expression<Func<Order, bool>> byStatus   = o => o.Status == OrderStatus.Active;

// AND birleştirme
var combined = PredicateBuilder.CombineAnd(byCustomer, byStatus);

// OR birleştirme
var either = PredicateBuilder.CombineOr(byCustomer, byStatus);

// Çoklu AND
var multi = PredicateBuilder.CombineAnd(expr1, expr2, expr3);

MaskHelper

JSON veya plain text içindeki hassas alanları maskeler. HttpLoggingMiddleware tarafından kullanılır.

string maskedJson = MaskHelper.MaybeMask(jsonPayload, structuredLoggingOptions);
// {"username":"john","password":"****","token":"****"}

Maskeleme davranışı StructuredLoggingOptions.SensitiveFields ve SensitiveFieldMatchMode ile kontrol edilir.


13. Logging Seçenekleri

Bu modeller Ledbim.Core'da tanımlanır; gerçek logging implementasyonu Ledbim.AspNetCore'dadır.

LoggingOptions (temel)

public class LoggingOptions
{
    public bool EnableRequestLogging { get; set; } = true;
    public bool EnableResponseLogging { get; set; } = true;
    public bool MaskSensitiveData { get; set; } = true;
    public int MaxBodyLength { get; set; } = 65_536;  // 64 KB
}

StructuredLoggingOptions (gelişmiş)

LoggingOptions'tan türer:

public class StructuredLoggingOptions : LoggingOptions
{
    // Correlation header adı (default: "X-Correlation-Id")
    public string CorrelationHeaderName { get; set; }

    // Bu path'ler loglanmaz (health, metrics, swagger, static, vb.)
    public string[] ExcludePaths { get; set; }

    // Body sınırlama
    public bool TruncateRequestBody { get; set; } = true;
    public int MaxRequestBodyBytes { get; set; } = 65_536;
    public int MaxResponseBodyBytes { get; set; } = 65_536;

    // Maskeleme
    public string MaskWith { get; set; } = "****";
    public string[] SensitiveFields { get; set; }
    // Varsayılan: password, token, accessToken, authorization, ssn,
    //             iban, creditCard, cardNumber, tcKimlik, email, phone, recoveryCode

    public SensitiveFieldMatchMode SensitiveFieldMatchMode { get; set; } // Contains | Equals | StartsWith

    // Log seviyeleri
    public string ResponseLogLevel { get; set; } = "Information";
    public string ErrorLogLevel { get; set; } = "Error";
}

appsettings.json örneği:

{
  "StructuredLogging": {
    "enableRequestLogging": true,
    "enableResponseLogging": true,
    "maskSensitiveData": true,
    "correlationHeaderName": "X-Correlation-Id",
    "excludePaths": ["/health", "/metrics", "/openapi"],
    "sensitiveFields": ["password", "token", "iban", "tcKimlik"],
    "sensitiveFieldMatchMode": "Contains",
    "maskWith": "****",
    "maxRequestBodyBytes": 65536,
    "maxResponseBodyBytes": 65536
  }
}

LogFields

Structured log alanlarının standart isimleri:

LogFields.CorrelationId  // "correlation.id"
LogFields.UserId         // "user.id"
LogFields.RequestName    // "request.name"
LogFields.RequestBody    // "request.body"
LogFields.ResponseBody   // "response.body"
LogFields.ElapsedMs      // "elapsed.ms"
LogFields.MessageSource  // "message.source"
LogFields.HttpMethod     // "http.method"
LogFields.HttpPath       // "http.path"
LogFields.HttpStatusCode // "http.status.code"

14. Güvenlik: ICurrentUserAccessor

HTTP context'ten bağımsız, authenticated kullanıcı kimliğine erişim soyutlaması.

public interface ICurrentUserAccessor
{
    string? GetCurrentUser();
}

Implementasyon: Ledbim.AspNetCore paketi HttpContextCurrentUserAccessor sağlar ve şu sırayla okur:

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

Background job'larda: ICurrentUserAccessor'ın custom bir implementasyonu register edilerek job context'inden kullanıcı bilgisi okunabilir.

// Handler'da
public class UpdateOrderHandler(ICurrentUserAccessor userAccessor) : IRequestHandler<...>
{
    public async Task<Result> Handle(...)
    {
        var userId = userAccessor.GetCurrentUser();
        // ...
    }
}

15. Entegrasyon ve DI Kaydı

Ledbim.Core içindeki bileşenlerin büyük kısmı direkt instantiate edilmez; DI container üzerinden çalışır.

Zorunlu Kayıtlar

// MediatR (projeye özgü)
services.AddMediatR(cfg =>
    cfg.RegisterServicesFromAssembly(typeof(SomeHandler).Assembly));

// FluentValidation (projeye özgü)
services.AddValidatorsFromAssembly(typeof(SomeValidator).Assembly);

// Domain event collector
services.AddScoped<IDomainEventCollector, DomainEventCollector>();

// ITransactionManager — UnitOfWork üzerinden resolve edilmeli
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<ITransactionManager>(sp => sp.GetRequiredService<IUnitOfWork>());

Pipeline Behavior Kayıtları (Ledbim.AspNetCore üzerinden)

// ValidationBehaviour + LoggingBehaviour + UnhandledExceptionBehaviour + PerformanceBehaviour
services.AddPipelineBehaviors();

// TransactionBehaviour (opt-in — ITransactionManager gerektirir)
services.AddTransactionalBehavior();

Logging Options (Ledbim.AspNetCore üzerinden)

services.Configure<StructuredLoggingOptions>(
    configuration.GetSection("StructuredLogging"));

16. Kullanım Örnekleri

Tam Command Handler

// Command tanımı
public record CreateOrderCommand(Guid CustomerId, decimal Amount, List<OrderLineDto> Lines)
    : IRequest<Result<Guid>>, ICommand;

// Validator
public class CreateOrderCommandValidator : AbstractValidator<CreateOrderCommand>
{
    public CreateOrderCommandValidator()
    {
        RuleFor(x => x.CustomerId).NotEmpty();
        RuleFor(x => x.Amount).GreaterThan(0);
        RuleFor(x => x.Lines).NotEmpty();
    }
}

// Handler
internal sealed class CreateOrderCommandHandler(IUnitOfWork uow)
    : IRequestHandler<CreateOrderCommand, Result<Guid>>
{
    public async Task<Result<Guid>> Handle(CreateOrderCommand request, CancellationToken ct)
    {
        // 1. Benzersizlik / iş kuralı kontrolü
        var customer = await uow.CustomerRepository.GetAsync(
            new GenericExpression<Customer>(x => x.Id == request.CustomerId));

        if (customer is null)
            return Result<Guid>.Fail(ResultStatus.NotFound, "Müşteri bulunamadı.");

        // 2. Factory method ile entity oluştur (mapper.Map yok)
        var order = Order.Create(request.CustomerId, request.Amount, request.Lines);

        // 3. Change tracker'a ekle — pipeline CommitAsync çağırır
        await uow.OrderRepository.AddAsync(order);

        return Result<Guid>.Success(ResultStatus.Created, order.Id, "Sipariş oluşturuldu.");
    }
}

Domain Event Kullanan Aggregate

public class Order : AggregateRoot<Guid>
{
    public Guid CustomerId { get; private set; }
    public OrderStatus Status { get; private set; }

    private Order() { }

    public static Order Create(Guid customerId, decimal amount, ...)
    {
        var order = new Order { CustomerId = customerId, ... };
        order.AddDomainEvent(new OrderCreatedEvent(order.Id, customerId, DateTime.UtcNow));
        return order;
    }

    public Result Confirm()
    {
        if (Status != OrderStatus.Pending)
            return Result.Fail(ResultStatus.Conflict, "Yalnızca bekleyen siparişler onaylanabilir.");

        Status = OrderStatus.Confirmed;
        AddDomainEvent(new OrderConfirmedEvent(Id, DateTime.UtcNow));
        return Result.Success(ResultStatus.Success);
    }
}

Query Handler

public record GetOrderQuery(Guid OrderId) : IRequest<Result<OrderDto>>, IQuery;

internal sealed class GetOrderQueryHandler(IUnitOfWork uow)
    : IRequestHandler<GetOrderQuery, Result<OrderDto>>
{
    public async Task<Result<OrderDto>> Handle(GetOrderQuery request, CancellationToken ct)
    {
        var order = await uow.OrderRepository.GetAsync(
            new GenericExpression<Order>(
                predicate: x => x.Id == request.OrderId,
                includePaths: q => q.Include(o => o.Lines)));

        if (order is null)
            return Result<OrderDto>.Fail(ResultStatus.NotFound, "Sipariş bulunamadı.");

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

Sayfalı Listeleme

public record ListOrdersQuery(PaginationFilterQuery FilterQuery, Guid? CustomerId)
    : IRequest<PagedResponse<IEnumerable<OrderSummaryDto>>>, IQuery;

internal sealed class ListOrdersQueryHandler(IUnitOfWork uow)
    : IRequestHandler<ListOrdersQuery, PagedResponse<IEnumerable<OrderSummaryDto>>>
{
    private static readonly HashSet<string> _allowedFields = ["createdDate", "status", "amount"];

    public async Task<PagedResponse<IEnumerable<OrderSummaryDto>>> Handle(
        ListOrdersQuery request, CancellationToken ct)
    {
        var filterRequest = new PagedFilterRequest<Order>(
            filterQuery: request.FilterQuery,
            predicate: request.CustomerId.HasValue
                ? x => x.CustomerId == request.CustomerId.Value
                : null);

        var pagedResult = await uow.OrderRepository.GetAllPagedAsync(filterRequest);

        return PagedHelper.CreatePagedResponse(
            data: pagedResult.PagedData.Select(o => o.ToSummaryDto()),
            totalRecords: pagedResult.TotalRecords,
            pageNumber: request.FilterQuery.PageNumber,
            pageSize: request.FilterQuery.PageSize);
    }
}

PredicateBuilder ile Dinamik Sorgu

Expression<Func<Order, bool>> predicate = o => true; // başlangıç

if (customerId.HasValue)
    predicate = PredicateBuilder.CombineAnd(predicate, o => o.CustomerId == customerId.Value);

if (status.HasValue)
    predicate = PredicateBuilder.CombineAnd(predicate, o => o.Status == status.Value);

var orders = await uow.OrderRepository.GetAllAsync(new GenericExpression<Order>(predicate));

17. Mimari Notlar

Bağımlılık Hiyerarşisi

Ledbim.Core (bağımsız)
    ↑
    ├── Ledbim.AspNetCore   (HTTP pipeline, middleware, DI kayıtları)
    ├── Ledbim.EntityFramework (EF Core implementasyonları)
    ├── Ledbim.Security     (JWT, BasicAuth implementasyonları)
    ├── Ledbim.Messaging    (MassTransit implementasyonu)
    ├── Ledbim.Mongo        (MongoDB implementasyonları)
    ├── Ledbim.Http         (HTTP client wrapper)
    └── Ledbim.Storage      (FTP depolama)

Hangi Katmanda Kullanılır?

Ledbim.Core tüm katmanlarda kullanılır:

Proje Katmanı Kullandığı Core Yapılar
Domain BaseEntity<TKey>, AggregateRoot<TKey>, domain event record'ları
Application ICommand, IQuery, Result<T>, IDomainEventCollector, IUnitOfWork (extend eder)
Infrastructure ITransactionManager (implement eder), IDomainEventCollector
API ResultStatus, PagedResponse<T>

Framework-Agnostic Tasarım Kararları

  • BaseContext (EF) ICurrentUserAccessor'a bağımlıdır, IHttpContextAccessor'a değil
  • TransactionBehaviour ITransactionManager'a bağımlıdır, doğrudan DbContext'e değil
  • Localization interface'leri (ICultureCatalog, ILocalizedMessages) Ledbim.AspNetCore'da — Core saf kalır
  • Pipeline behavior'larının DI kaydı Ledbim.AspNetCore'da — bağımlılık yönü korunur

18. Dikkat Edilmesi Gerekenler

Durum Açıklama
Handler'da SaveChanges / Commit çağırma Kesinlikle yapılmaz. TransactionBehaviour pipeline'da halleder.
Result.Success() ile hata kodu ArgumentException fırlatılır. Success, Created, NoContent dışındaki değerler kullanılamaz.
Result.Fail() ile başarı kodu ArgumentException fırlatılır. 4xx/5xx dışındaki değerler kullanılamaz.
ICommand olmayan write request Pipeline transaction açmaz. İstemeden veri bütünlüğü kaybı yaşanabilir.
AggregateRoot yerine BaseEntity kullanımı BaseContext.CollectDomainEvents() yalnızca IAggregateRoot implement edenleri tarar; event'ler dispatch edilmez.
IDomainEventCollector singleton Scoped olmalıdır. Singleton kayıt event sızıntısına yol açar.
ITransactionManager doğrudan inject Handler'lar inject etmez — yalnızca TransactionBehaviour kullanır.
Event dispatch hatası LogWarning yazılır, exception fırlatılmaz; transaction zaten commit edilmiştir.
IHardDelete olmadan fiziksel silme BaseContext tüm silme işlemlerini soft delete'e çevirir.
Filter alan adları güvenliği Client'tan gelen ColumnField allow-list ile kontrol edilmezse injection riski oluşabilir.
InternalsVisibleTo Core'un bazı internal yapıları Ledbim.EntityFramework, Ledbim.Mongo, Ledbim.AspNetCore, Ledbim.Security tarafından erişilebilir.

19. Hızlı Başlangıç

// Program.cs — minimum kurulum

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

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

// 3. Domain event collector
builder.Services.AddScoped<IDomainEventCollector, DomainEventCollector>();

// 4. IUnitOfWork (projeye özgü — ITransactionManager'ı extend eder)
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<ITransactionManager>(
    sp => sp.GetRequiredService<IUnitOfWork>());

// 5. Pipeline behaviors (Ledbim.AspNetCore gerektirir)
builder.Services.AddPipelineBehaviors();
builder.Services.AddTransactionalBehavior();

// --- Handler şablonu ---

// Command
public record CreateProductCommand(string Name, decimal Price)
    : IRequest<Result<Guid>>, ICommand;

// Handler
internal sealed class CreateProductCommandHandler(IUnitOfWork uow)
    : IRequestHandler<CreateProductCommand, Result<Guid>>
{
    public async Task<Result<Guid>> Handle(CreateProductCommand request, CancellationToken ct)
    {
        var product = Product.Create(request.Name, request.Price);
        await uow.ProductRepository.AddAsync(product);
        return Result<Guid>.Success(ResultStatus.Created, product.Id);
    }
}

// Validator
public class CreateProductCommandValidator : AbstractValidator<CreateProductCommand>
{
    public CreateProductCommandValidator()
    {
        RuleFor(x => x.Name).NotEmpty().MaximumLength(128);
        RuleFor(x => x.Price).GreaterThan(0);
    }
}
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 (7)

Showing the top 5 NuGet packages that depend on Ledbim.Core:

Package Downloads
Ledbim.Security

JWT ve BasicAuth hybrid kimlik doğrulama, refresh token rotation ve hırsızlık tespiti, AES-256-GCM şifreleme ve PBKDF2-SHA512 parola hashing altyapısı. Ledbim.Core üzerine inşa edilmiştir.

Ledbim.AspNetCore

ASP.NET Core entegrasyonu: middleware, Serilog yapılandırması, API versioning, OpenAPI/Scalar, localization ve MediatR pipeline behavior kayıtları içerir. Ledbim.Core üzerine inşa edilmiştir.

Ledbim.Messaging

MassTransit/RabbitMQ entegrasyonu. IIntegrationEventBus arayüzünün transport-agnostic implementasyonu, consumer logging filtresi ve correlation ID altyapısı içerir. Ledbim.Core üzerine inşa edilmiştir.

Ledbim.EntityFramework

Entity Framework Core ve Dapper tabanlı veri erişim altyapısı. BaseContext (audit, soft delete, domain event toplama), generic Repository, ITransactionManager, Dapper executor ve dinamik LINQ sorgu desteği içerir. Ledbim.Core üzerine inşa edilmiştir.

Ledbim.Storage

FTP tabanlı dosya depolama altyapısı. Stream ve Base64 formatında yükleme, otomatik klasör oluşturma ve silme işlemleri içerir. Ledbim.Core üzerine inşa edilmiştir.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.2.0 226 3/28/2026
1.1.3 109 3/26/2026
1.1.2 132 3/2/2026
1.1.1 113 2/23/2026