Levge.ConsistentResponse
2.3.0
dotnet add package Levge.ConsistentResponse --version 2.3.0
NuGet\Install-Package Levge.ConsistentResponse -Version 2.3.0
<PackageReference Include="Levge.ConsistentResponse" Version="2.3.0" />
<PackageVersion Include="Levge.ConsistentResponse" Version="2.3.0" />
<PackageReference Include="Levge.ConsistentResponse" />
paket add Levge.ConsistentResponse --version 2.3.0
#r "nuget: Levge.ConsistentResponse, 2.3.0"
#:package Levge.ConsistentResponse@2.3.0
#addin nuget:?package=Levge.ConsistentResponse&version=2.3.0
#tool nuget:?package=Levge.ConsistentResponse&version=2.3.0
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 | 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)
- FluentValidation.DependencyInjectionExtensions (>= 12.1.1)
- Levge.Domain (>= 2.3.0)
- Levge.Exceptions (>= 2.3.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.