Ledbim.AspNetCore
1.2.0
dotnet add package Ledbim.AspNetCore --version 1.2.0
NuGet\Install-Package Ledbim.AspNetCore -Version 1.2.0
<PackageReference Include="Ledbim.AspNetCore" Version="1.2.0" />
<PackageVersion Include="Ledbim.AspNetCore" Version="1.2.0" />
<PackageReference Include="Ledbim.AspNetCore" />
paket add Ledbim.AspNetCore --version 1.2.0
#r "nuget: Ledbim.AspNetCore, 1.2.0"
#:package Ledbim.AspNetCore@1.2.0
#addin nuget:?package=Ledbim.AspNetCore&version=1.2.0
#tool nuget:?package=Ledbim.AspNetCore&version=1.2.0
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
- Paket Amacı
- Paket Kapsamı
- Bağımlılıklar
- Önemli Yapılar
- BaseController
- Middleware'ler
- Pipeline Behavior'ları
- API Versiyonlama
- OpenAPI ve Scalar
- Serilog Yapılandırması
- Background Job Logging
- Action Filter'lar
- Localization
- Güvenlik: ICurrentUserAccessor
- Entegrasyon ve DI Kaydı
- Tam Program.cs Şablonu
- Kullanım Örnekleri
- Mimari Notlar
- 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:
ValidationException→Result.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
internalolan 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
ICurrentUserAccessorimplementasyonu - 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 | BaseRequest — LocalizationCode 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;Warninglog 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;TransactionBehaviouroutermost 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
SummaryhemDescriptionaction ü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'larInformationseviyesine kısıtlanırMicrosoft.*veSystem.*log'ları filtreyle atlanır- Console sink:
SystemConsoleTheme.Literate
Configuration overload'unda ek olarak:
Seq:ServerUrlconfig key'inden okunur; yoksahttp://localhost:5341kullanı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"]= kodBaseController.LocalizationCode= kodBaseRequesttürevi tüm action argümanlarınınLocalizationCodeproperty'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 | 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
- Asp.Versioning.Mvc (>= 8.1.0)
- Asp.Versioning.Mvc.ApiExplorer (>= 8.1.0)
- Ledbim.Core (>= 1.2.0)
- Microsoft.AspNetCore.OpenApi (>= 10.0.1)
- Microsoft.OpenApi (>= 2.3.0)
- Scalar.AspNetCore (>= 2.11.9)
- Serilog.AspNetCore (>= 9.0.0)
- Serilog.Sinks.Elasticsearch (>= 9.0.3)
- Serilog.Sinks.Seq (>= 9.0.0)
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 |