Ana içeriğe geç

Presentation Layer

Presentation katmanı, kullanıcı ile uygulama arasındaki iletişimi yönetir; yanlış kullanımlar controller’larda şişkinlik ve katman ihlallerine neden olur.


1. Controller İçinde İş Mantığı

Yanlış Kullanım: Controller’da veritabanı erişimi ve iş mantığı yazmak.

[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
    var customer = await _context.Customers.FindAsync(request.CustomerId);
    if (customer == null) return NotFound();

    var order = new Order { CustomerId = customer.Id };
    foreach (var item in request.Items)
    {
        var product = await _context.Products.FindAsync(item.ProductId);
        if (product.Stock < item.Quantity) return BadRequest("Stok yetersiz");
        product.Stock -= item.Quantity;
        order.Items.Add(new OrderItem { ProductId = product.Id, Quantity = item.Quantity });
    }
    _context.Orders.Add(order);
    await _context.SaveChangesAsync();
    return Ok(order);
}

İdeal Kullanım: Controller sadece HTTP concern’lerini yönetsin.

[HttpPost]
public async Task<IActionResult> CreateOrder(CreateOrderRequest request)
{
    var result = await _mediator.Send(new CreateOrderCommand(request.CustomerId, request.Items));
    return CreatedAtAction(nameof(GetOrder), new { id = result }, result);
}

2. Tutarsız API Response Formatı

Yanlış Kullanım: Her endpoint farklı response yapısı döndürmek.

[HttpGet("{id}")]
public async Task<IActionResult> GetOrder(int id)
{
    var order = await _service.GetAsync(id);
    if (order == null) return NotFound("Sipariş bulunamadı");
    return Ok(order);
}

[HttpGet]
public async Task<IActionResult> GetProducts()
{
    var products = await _service.GetAllAsync();
    return Ok(new { data = products, total = products.Count });
}

İdeal Kullanım: Standart API response wrapper kullanın.

public class ApiResponse<T>
{
    public bool Success { get; init; }
    public T Data { get; init; }
    public string Message { get; init; }
    public List<string> Errors { get; init; }
}

[HttpGet("{id}")]
public async Task<IActionResult> GetOrder(int id)
{
    var order = await _mediator.Send(new GetOrderQuery(id));
    return Ok(new ApiResponse<OrderDto> { Success = true, Data = order });
}

[HttpGet]
public async Task<IActionResult> GetProducts()
{
    var products = await _mediator.Send(new GetProductsQuery());
    return Ok(new ApiResponse<List<ProductDto>> { Success = true, Data = products });
}

3. Exception Handling’i Her Controller’da Tekrarlamak

Yanlış Kullanım: Her action’da try-catch yazmak.

[HttpPost]
public async Task<IActionResult> CreateProduct(CreateProductDto dto)
{
    try
    {
        var result = await _service.CreateAsync(dto);
        return Ok(result);
    }
    catch (ValidationException ex)
    {
        return BadRequest(ex.Errors);
    }
    catch (NotFoundException ex)
    {
        return NotFound(ex.Message);
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Hata oluştu");
        return StatusCode(500, "Bir hata oluştu");
    }
}

İdeal Kullanım: Global exception handler middleware kullanın.

public class GlobalExceptionMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<GlobalExceptionMiddleware> _logger;

    public GlobalExceptionMiddleware(RequestDelegate next, ILogger<GlobalExceptionMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (ValidationException ex)
        {
            context.Response.StatusCode = 400;
            await context.Response.WriteAsJsonAsync(new { errors = ex.Errors });
        }
        catch (NotFoundException ex)
        {
            context.Response.StatusCode = 404;
            await context.Response.WriteAsJsonAsync(new { message = ex.Message });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Beklenmeyen hata");
            context.Response.StatusCode = 500;
            await context.Response.WriteAsJsonAsync(new { message = "Bir hata oluştu" });
        }
    }
}

// Controller temiz kalır
[HttpPost]
public async Task<IActionResult> CreateProduct(CreateProductDto dto)
{
    var result = await _mediator.Send(new CreateProductCommand(dto));
    return Ok(result);
}

4. Model Binding ve Validation Karışımı

Yanlış Kullanım: Controller’da manuel validasyon yapmak.

[HttpPost]
public async Task<IActionResult> Register(RegisterRequest request)
{
    if (string.IsNullOrEmpty(request.Email)) return BadRequest("Email boş olamaz");
    if (request.Password.Length < 6) return BadRequest("Şifre en az 6 karakter olmalı");
    if (request.Password != request.ConfirmPassword) return BadRequest("Şifreler eşleşmiyor");

    var result = await _service.RegisterAsync(request);
    return Ok(result);
}

İdeal Kullanım: FluentValidation ile validasyonu otomatikleştirin.

public class RegisterRequestValidator : AbstractValidator<RegisterRequest>
{
    public RegisterRequestValidator()
    {
        RuleFor(x => x.Email).NotEmpty().EmailAddress();
        RuleFor(x => x.Password).MinimumLength(6);
        RuleFor(x => x.ConfirmPassword).Equal(x => x.Password).WithMessage("Şifreler eşleşmiyor");
    }
}

// Controller
[HttpPost]
public async Task<IActionResult> Register(RegisterRequest request)
{
    var result = await _mediator.Send(new RegisterCommand(request));
    return Ok(result);
}

5. Versiyonlama Yapmamak

Yanlış Kullanım: API’yi versiyonsuz sunmak.

[Route("api/products")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetAll() { /* ... */ }
    // Breaking change yapıldığında mevcut istemciler bozulur
}

İdeal Kullanım: API versiyonlama stratejisi uygulayın.

builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
});

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/products")]
public class ProductsV1Controller : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetAll() { /* v1 response */ }
}

[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/products")]
public class ProductsV2Controller : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetAll() { /* v2 response - yeni format */ }
}