FluentValidation Global Validation Middleware ile Model Binding Hataları Yakalamak

Bir önceki yazımda asp.net core projesine basitçe fluentvalidation ekledim ve request’leri valide ettim. Burada bir ufak sorun var oda model binding hataları, bu şekilde manuel validasyon yapmadan önce DTO’ request body den değerler alır ve nesne oluşturur.

  • Tip uyumsuzluğu
  • Eksik property
  • Farklı veri

Sebepleri ile bu DTO nesnesi oluşamaz bile ve uygulamamız validasyondan önce crush olur.

Önceki yazı

Bunu çözmek için uygulamamızı önceden middleware ile valide etmemiz gerekir ki bunun için ek tek bir class yeterlidir.

Bu yazıda ek bir middleware ekleyerek global bir validasyon sağlayacağım ve sorunumuz ortadan kalkmış olacak

📄 Middlewares/ValidationMiddleware.cs

C#
using FluentValidation;
using FluentValidation.Results;
using System.Text.Json;

namespace FluentValidationDemo.Middlewares
{
    public class ValidationMiddleware
    {
        private readonly RequestDelegate _next;

        public ValidationMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task InvokeAsync(HttpContext context, IServiceProvider serviceProvider)
        {
            var method = context.Request.Method;

            // Sadece POST ve PUT isteklerde çalışsın
            if (method != HttpMethods.Post && method != HttpMethods.Put)
            {
                await _next(context);
                return;
            }

            var endpoint = context.GetEndpoint();
            if (endpoint is null)
            {
                await _next(context);
                return;
            }

            context.Request.EnableBuffering();
            using var reader = new StreamReader(context.Request.Body, leaveOpen: true);
            var body = await reader.ReadToEndAsync();
            context.Request.Body.Position = 0;

            if (string.IsNullOrWhiteSpace(body))
            {
                await _next(context);
                return;
            }

            try
            {
                var controllerActionDescriptor = endpoint.Metadata
                    .OfType<Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor>()
                    .FirstOrDefault();

                if (controllerActionDescriptor == null)
                {
                    await _next(context);
                    return;
                }

                foreach (var parameter in controllerActionDescriptor.Parameters)
                {
                    var validatorType = typeof(IValidator<>).MakeGenericType(parameter.ParameterType);
                    var validator = serviceProvider.GetService(validatorType) as IValidator;
                    if (validator == null) continue;

                    object? model = null;
                    try
                    {
                        var options = new JsonSerializerOptions
                        {
                            PropertyNameCaseInsensitive = true,
                            NumberHandling = System.Text.Json.Serialization.JsonNumberHandling.Strict
                        };

                        model = JsonSerializer.Deserialize(body, parameter.ParameterType, options);
                    }
                    catch (JsonException)
                    {
                        await WriteErrorResponse(context, StatusCodes.Status400BadRequest, new[]
                        {
                            new { field = "body", message = "Invalid JSON format or wrong data types." }
                        });
                        return;
                    }

                    if (model == null)
                    {
                        await WriteErrorResponse(context, StatusCodes.Status400BadRequest, new[]
                        {
                            new { field = "body", message = "Request body could not be deserialized." }
                        });
                        return;
                    }

                    ValidationResult result = await validator.ValidateAsync(new ValidationContext<object>(model));
                    if (!result.IsValid)
                    {
                        await WriteErrorResponse(context, StatusCodes.Status400BadRequest,
                            result.Errors.Select(e => new { field = e.PropertyName, message = e.ErrorMessage }));
                        return;
                    }
                }
            }
            catch (Exception ex)
            {
                await WriteErrorResponse(context, StatusCodes.Status500InternalServerError, new[]
                {
                    new { field = "internal", message = $"Validation middleware error: {ex.Message}" }
                });
                return;
            }

            context.Request.Body.Position = 0;
            await _next(context);
        }

        private static async Task WriteErrorResponse(HttpContext context, int statusCode, IEnumerable<object> errors)
        {
            context.Response.StatusCode = statusCode;
            context.Response.ContentType = "application/json";
            var response = new
            {
                success = false,
                errors
            };
            await context.Response.WriteAsync(JsonSerializer.Serialize(response));
        }
    }

    public static class ValidationMiddlewareExtensions
    {
        public static IApplicationBuilder UseValidationMiddleware(this IApplicationBuilder app)
        {
            return app.UseMiddleware<ValidationMiddleware>();
        }
    }
}

Bu middleware tanıladıktan sonra Program.cs dosyamıza da ekliyoruz.

Program.cs’ middleware tanımlamak için tek satır kod eklememiz yetiyor bildiğiniz gibi.

C#
app.UseValidationMiddleware();

Tam olarak bu kadar.

Ve tabi program.cs son hali

C#

using FluentValidation;
using FluentValidationDemo.Middlewares;
using FluentValidationDemo.Validators;


var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();

builder.Services.AddOpenApi();
// Swagger.NET 9’da manuel açılmalı:
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// ✅ FluentValidation ayarları 
builder.Services.AddValidatorsFromAssemblyContaining<RegisterValidator>();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
    app.UseSwagger();
    app.UseSwaggerUI(options =>
    {
        options.SwaggerEndpoint("/swagger/v1/swagger.json", "ECommerce API V1");
        options.RoutePrefix = string.Empty;
    });
}

app.UseHttpsRedirection();

app.UseValidationMiddleware();

app.UseAuthorization();

app.MapControllers();

app.Run();

Artık controller’da tekrar valide etmene gerek yok, controller’daki validasyon kodlarını silebilirsin.

Bu yazımızdan önce aşağıdaki yazıyı incelemeni tavsiye ederim.

Yorumlar

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir