Ledbim.Core
1.2.0
dotnet add package Ledbim.Core --version 1.2.0
NuGet\Install-Package Ledbim.Core -Version 1.2.0
<PackageReference Include="Ledbim.Core" Version="1.2.0" />
<PackageVersion Include="Ledbim.Core" Version="1.2.0" />
<PackageReference Include="Ledbim.Core" />
paket add Ledbim.Core --version 1.2.0
#r "nuget: Ledbim.Core, 1.2.0"
#:package Ledbim.Core@1.2.0
#addin nuget:?package=Ledbim.Core&version=1.2.0
#tool nuget:?package=Ledbim.Core&version=1.2.0
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
- Paket Amacı
- Paket Kapsamı
- Bağımlılıklar
- Önemli Yapılar
- CQRS Marker Interface'leri
- Result Pattern
- Entity Modeli
- Domain Events
- Pipeline Behavior'ları
- Messaging (IIntegrationEventBus)
- Pagination, Filter ve Sort
- Yardımcı Araçlar (Helpers)
- Logging Seçenekleri
- Güvenlik: ICurrentUserAccessor
- Entegrasyon ve DI Kaydı
- Kullanım Örnekleri
- Mimari Notlar
- Dikkat Edilmesi Gerekenler
- 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.AspNetCorepaketindedir.
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()ileBadRequestgibi bir hata kodu verilirseArgumentExceptionfırlatılır. Aynı şekildeFail()ileSuccess(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ış:
- Request için kayıtlı tüm
IValidator<TRequest>instance'ları toplanır - Tüm validator'lar paralel execute edilir
- Herhangi bir hata varsa
ValidationExceptionfırlatılır (Ledbim.AspNetCorebunu yakalar →Result.Fail(BadRequest)) - 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:
ClaimTypes.Nameclaim'i"UserName"claim'iClaimTypes.NameIdentifierclaim'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ğilTransactionBehaviourITransactionManager'a bağımlıdır, doğrudanDbContext'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 | Versions 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. |
-
net10.0
- FluentValidation (>= 12.1.1)
- MediatR (>= 14.1.0)
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.