Levge.ConsistentResponse 2.3.0

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

Publish NuGet Package

Levge.ConsistentResponse

ASP.NET Core projelerinde API yanıtlarını standart bir yapıya kavuşturmak için tasarlanmış NuGet paketidir. Tüm endpoint'lerin aynı formatta yanıt dönmesini, FluentValidation hatalarının otomatik olarak alan bazlı (field-based) iletilmesini ve Levge.Domain Result pattern'iyle sorunsuz çalışmasını sağlar.

Kurulum

dotnet add package Levge.ConsistentResponse

Bağımlılıklar: Levge.Exceptions, Levge.Domain, FluentValidation


Program.cs Kurulumu

// Program.cs
builder.Services.AddLevgeConsistentResponse(
    lowerUrls: true,                        // URL'leri kebab-case'e çevirir (opsiyonel)
    typeof(CreateUserCommand).Assembly      // FluentValidation validator'larını tara (opsiyonel)
);

// ... 

app.UseLevgeConsistentResponse(); // GlobalExceptionMiddleware'i pipeline'a ekler
app.MapControllers();

Yanıt Yapısı

Tüm endpoint'ler aşağıdaki JSON yapısını döner:

{
  "isSuccess": true,
  "message": "Operation successful.",
  "data": { ... },
  "errors": null
}

Hata durumunda:

{
  "isSuccess": false,
  "message": "Validation failed.",
  "data": null,
  "errors": {
    "email": ["Bu e-posta adresi zaten kullanımda."],
    "password": ["En az 8 karakter olmalıdır.", "En az bir büyük harf içermelidir."]
  }
}
Alan Tip Açıklama
isSuccess bool İşlemin başarılı olup olmadığı
message string Başarı veya hata mesajı
data T? Başarı durumunda dönen veri
errors Dictionary<string, string[]>? Alan bazlı hata listesi (camelCase anahtarlar)

FluentValidation Entegrasyonu

Validator'lar DI'ya otomatik kaydedilir. AbstractValidator<T>'den türetmek yeterlidir.

// Application/Users/Validators/CreateUserCommandValidator.cs
public class CreateUserCommandValidator : AbstractValidator<CreateUserCommand>
{
    public CreateUserCommandValidator()
    {
        RuleFor(x => x.Email)
            .NotEmpty().WithMessage("E-posta zorunludur.")
            .EmailAddress().WithMessage("Geçerli bir e-posta adresi giriniz.");

        RuleFor(x => x.Password)
            .NotEmpty().WithMessage("Şifre zorunludur.")
            .MinimumLength(8).WithMessage("Şifre en az 8 karakter olmalıdır.")
            .Matches("[A-Z]").WithMessage("En az bir büyük harf içermelidir.");

        RuleFor(x => x.FullName)
            .NotEmpty().WithMessage("Ad Soyad zorunludur.")
            .MaximumLength(100).WithMessage("Ad Soyad 100 karakterden uzun olamaz.");
    }
}

Controller veya Application katmanında hata fırlatarak middleware'in yakalamasını sağlayın:

// Application katmanı (CQRS Handler / Service)
public class CreateUserCommandHandler
{
    private readonly IValidator<CreateUserCommand> _validator;

    public async Task<Result<long>> Handle(CreateUserCommand command, CancellationToken ct)
    {
        var validation = await _validator.ValidateAsync(command, ct);
        if (!validation.IsValid)
            throw new ValidationException(validation.Errors); // → 400 alan bazlı yanıt

        // ...
    }
}

Otomatik yanıt örneği:

{
  "isSuccess": false,
  "message": "Validation failed.",
  "data": null,
  "errors": {
    "email": ["Geçerli bir e-posta adresi giriniz."],
    "password": ["Şifre en az 8 karakter olmalıdır.", "En az bir büyük harf içermelidir."]
  }
}

Result Pattern ile Kullanım

Levge.Domain'deki Result<T> nesnelerini direkt IActionResult'a dönüştürmek için extension metotları mevcuttur.

ToOkResult — 200 OK

[HttpGet("{id}")]
public async Task<IActionResult> GetUser(long id)
{
    Result<UserDto> result = await _userService.GetByIdAsync(id);
    return result.ToOkResult(); // başarıysa 200, hata varsa 404 veya 400
}

ToCreatedResult — 201 Created

[HttpPost]
public async Task<IActionResult> CreateUser(CreateUserCommand command)
{
    Result<long> result = await _handler.Handle(command);
    return result.ToCreatedResult(nameof(GetUser), new { id = result.Value });
}

ToNoContentResult — 204 No Content

[HttpDelete("{id}")]
public async Task<IActionResult> DeleteUser(long id)
{
    Result result = await _userService.DeleteAsync(id);
    return result.ToNoContentResult(); // başarıysa 204, hata varsa 400
}

Özel Error → HTTP kodu eşlemesi

Error.Code değerine göre otomatik HTTP kodu atanır:

Error.Code HTTP Kodu
"Error.NotFound" 404 Not Found
"Error.Unauthorized" 401 Unauthorized
Diğer 400 Bad Request
// Domain/Errors/UserErrors.cs
public static class UserErrors
{
    public static readonly Error NotFound     = Error.Create("Error.NotFound", "Kullanıcı bulunamadı.");
    public static readonly Error EmailExists  = Error.Create("User.EmailExists", "Bu e-posta zaten kayıtlı.");
    public static readonly Error Unauthorized = Error.Create("Error.Unauthorized", "Bu işlem için yetkiniz yok.");
}
// Application katmanı
public async Task<Result<UserDto>> GetByIdAsync(long id)
{
    var user = await _repo.FindAsync(id);
    if (user is null)
        return Result.Failure<UserDto>(UserErrors.NotFound); // → controller'da 404 döner
    return Result.Success(user.ToDto());
}

Exception ile Hata Yönetimi

GlobalExceptionMiddleware aşağıdaki exception türlerini yakalar ve standart yanıta dönüştürür:

Exception HTTP Kodu Açıklama
FluentValidation.ValidationException 400 Alan bazlı validation hataları
LevgeValidationException 400 Manuel validation hataları
LevgeUnauthorizedException 401 Kimlik doğrulama hatası
LevgeForbiddenException 403 Yetki hatası
LevgeNotFoundException 404 Kayıt bulunamadı
LevgeConflictException 409 Çakışma hatası
LevgeBusinessRuleException 422 İş kuralı ihlali
LevgeLicenseException 403 Lisans hatası
LevgeExternalServiceException 502 Dış servis hatası
LevgeTenantException 400 / 403 Multi-tenant hatası
Exception 500 Beklenmedik hata (loglanır)

Kullanım örnekleri:

// 404 + alan bazlı hata
throw new LevgeNotFoundException("userId", "Kullanıcı bulunamadı.");

// 409 + alan bazlı hata
throw new LevgeConflictException("email", "Bu e-posta adresi zaten kayıtlı.");

// 422 iş kuralı
throw new LevgeBusinessRuleException("INSUFFICIENT_BALANCE", "Yetersiz bakiye.");

// 502 dış servis
throw new LevgeExternalServiceException("PaymentGateway", "Ödeme servisi yanıt vermedi.");

Sayfalama (Pagination)

PaginationRequest

Listeleme endpoint'lerinde istek gövdesi veya query string olarak kullanılır:

public class PaginationRequest
{
    public int Page { get; set; } = 1;
    public int PageSize { get; set; } = 10;
    public string? SearchText { get; set; }
    public List<SortOption> Sorts { get; set; } = [];
    public List<FilterOption> Filters { get; set; } = [];
}

PaginationData<T>

Application katmanından dönen sayfalı veri yapısı:

// Application/Users/Queries/GetUsersQueryHandler.cs
public async Task<Result<PaginationData<UserDto>>> Handle(GetUsersQuery query, CancellationToken ct)
{
    var users = await _repo.GetPagedAsync(query.Request, ct);

    return Result.Success(new PaginationData<UserDto>
    {
        Items = users.Items.Select(u => u.ToDto()).ToList(),
        Meta = new PaginationMeta
        {
            Page      = query.Request.Page,
            PageSize  = query.Request.PageSize,
            TotalCount = users.TotalCount
        }
    });
}

Controller

[HttpGet]
public async Task<IActionResult> GetUsers([FromQuery] PaginationRequest request)
{
    var result = await _handler.Handle(new GetUsersQuery(request));
    return result.ToOkResult();
}

Yanıt Örneği

{
  "isSuccess": true,
  "message": "Operation successful.",
  "data": {
    "items": [
      { "id": 1, "fullName": "Serdar ÖZKAN", "email": "serdar@levge.com" },
      { "id": 2, "fullName": "Ahmet Yılmaz", "email": "ahmet@levge.com" }
    ],
    "meta": {
      "page": 1,
      "pageSize": 10,
      "totalCount": 42,
      "totalPages": 5
    }
  },
  "errors": null
}

SortOption & FilterOption

// GET /api/users ile birlikte gönderilecek örnek body veya query
{
  "page": 1,
  "pageSize": 20,
  "searchText": "serdar",
  "sorts": [
    { "field": "createdAt", "direction": "desc" }
  ],
  "filters": [
    { "field": "isActive", "operator": "eq", "values": ["true"] }
  ]
}

Tam Clean Architecture Örneği

Katman Yapısı

MyProject.sln
├── MyProject.Domain          (Levge.Domain)
├── MyProject.Application     (FluentValidation validators, CQRS handlers)
├── MyProject.Infrastructure  (EF Core, dış servisler)
└── MyProject.API             (Levge.ConsistentResponse)

Program.cs

builder.Services.AddLevgeConsistentResponse(
    lowerUrls: true,
    typeof(CreateUserCommand).Assembly  // Application assembly'si
);

app.UseLevgeConsistentResponse();
app.MapControllers();

Controller

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly IMediator _mediator;
    public UsersController(IMediator mediator) => _mediator = mediator;

    [HttpGet]
    public async Task<IActionResult> GetAll([FromQuery] PaginationRequest request)
        => (await _mediator.Send(new GetUsersQuery(request))).ToOkResult();

    [HttpGet("{id:long}")]
    public async Task<IActionResult> GetById(long id)
        => (await _mediator.Send(new GetUserByIdQuery(id))).ToOkResult();

    [HttpPost]
    public async Task<IActionResult> Create(CreateUserCommand command)
        => (await _mediator.Send(command)).ToCreatedResult(nameof(GetById), new { id = /* result.Value */ 0 });

    [HttpDelete("{id:long}")]
    public async Task<IActionResult> Delete(long id)
        => (await _mediator.Send(new DeleteUserCommand(id))).ToNoContentResult();
}

Lisans

MIT © Serdar ÖZKAN

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.3.0 101 4/29/2026
2.1.0 100 4/27/2026
2.0.1 90 4/27/2026
2.0.0 92 4/26/2026
1.1.45 118 1/22/2026
1.1.44 421 6/25/2025
1.1.43 215 6/24/2025
1.1.42 219 6/24/2025
1.1.41 200 6/22/2025
1.1.35 172 6/22/2025
1.0.1 198 6/17/2025
1.0.0 219 6/17/2025