Kategori: .NET Core

  • FluentValidation Global Validation Middleware ile Model Binding Hataları Yakalamak

    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.

  • ASP.NET CORE + FluentValidation ile Form Verisi Doğrulama

    ASP.NET CORE + FluentValidation ile Form Verisi Doğrulama

    Bu yazıda asp.net core web api projemizde fluentvalidation ile veri doğrulama işlemini gerçekleştireceğim, adım adım en basit haliyle anlatacağım ki siz de projenize entegre ederken sorun yaşamayın. Sadece validasyon kısmına odaklanacağım.

    Annotation tabanlı validasyona göre çok daha esnek olan FluentValidation paketi .net dünyasında en çok kullanılan validasyon yöntemi.

    En önemli faydalarından biri validasyon mantığını kurallar ile ayrı bir dosyada tanımlayabiliyorsunuz ve böylelikle kodunuz daha temiz ve yönetilebilir hale geliyor. İleride validasyon kuralı eklemek isterseniz ilgili dosyayı açarak ekleyebilirsiniz.

    Hali hazırda bir .net 9 asp.net web api projesi başlattığınızı varsayacağım ve hemen paketlerimi ekleyerek başlayacağım.

    FluentValidation paketini test etmek için ek olarak swagger’de kuracağım. Bildiğiniz gibi .net 9 ile birlikte default olarak entegre gelmiyor ve basit bir kaç adımla projemize dahil edebiliyoruz.

    Öncelikle paketlerimizi yükleyerek başlayalım.

    C#
    dotnet add package FluentValidation.DependencyInjectionExtensions
    dotnet add package Swashbuckle.AspNetCore

    Ben CLI kullanacağım fakat siz isterseniz nuget paket yöneticisi ile de dahil edebilirsiniz aynı kapıya çıkar.

    Paketlerimzi eklediğimize göre hemen DTO’ları tanımlayarak başlayalım.

    Projemizde tek endpoint olacak kullanıcı kayıt endpointi ve nested(iç içe) DTO objelerini valide etmemiz gerekecek, RegisterDTO içerisinde AddressInfoDTO olacak.

    📄 DTOs/RegisterDTO.cs

    C#
    namespace FluentValidationDemo.DTOs
    {
        public class RegisterDTO
        {
            public string? FirstName { get; set; } = string.Empty;
            public string? LastName { get; set; } = string.Empty;
            public string? Email { get; set; } = string.Empty;
            public AddressInfoDTO? AddressInfo { get; set; }
        }
    }

    📄 DTOs/AddressInfoDTO.cs

    C#
    namespace FluentValidationDemo.DTOs
    {
        public class AddressInfoDTO
        {
            public string? Phone { get; set; } = string.Empty;
            public string? Zip { get; set; } = string.Empty;
        }
    }

    Sırada validasyon sınıfımız var, projemiz içinde istediğmiiz yerde kullanabilmek için validasyonumuzu ayrı bir sınıf olarak kodluyoruz.

    Öncelikle alt nesne olan AddressInfoValidator sınıfını kodluyoruz.

    📄 Validators/AddressInfoValidator.cs

    C#
    using FluentValidation;
    using FluentValidationDemo.DTOs;
    
    namespace FluentValidationDemo.Validators
    {
        public class AddressInfoValidator : AbstractValidator<AddressInfoDTO>
        {
            public AddressInfoValidator()
            {
                RuleFor(x => x.Phone)
                    .NotEmpty().WithMessage("Telefon numarası zorunludur.")
                    .Matches(@"^\+?\d{10,15}$").WithMessage("Telefon numarası formatı geçersiz.");
    
                RuleFor(x => x.Zip)
                    .NotEmpty().WithMessage("Posta kodu zorunludur.")
                    .Length(4, 10).WithMessage("Posta kodu 4 ile 10 karakter arasında olmalıdır.");
            }
        }
    }
    

    Sınıfımız AbstractValidator sınıfından türetilmiş olacak ve <AddressInfoDTO> valide ettiğimiz sınıfı belirtmiş olacağız.

    Sırada ana validasyon sınıfımız var, aynı şekilde AbstractValidator<RegisterDTO> üzerinden türeterek kurallarımızı tanımlıyoruz.

    📄 Validators/RegisterValidator.cs

    C#
    using FluentValidation;
    using FluentValidationDemo.DTOs;
    
    namespace FluentValidationDemo.Validators
    {
        public class RegisterValidator : AbstractValidator<RegisterDTO>
        {
            public RegisterValidator()
            {
                RuleFor(x => x.FirstName)
                    .NotEmpty().WithMessage("Ad alanı boş olamaz.")
                    .MinimumLength(2).WithMessage("Ad en az 2 karakter olmalıdır.");
    
                RuleFor(x => x.LastName)
                    .NotEmpty().WithMessage("Soyad alanı boş olamaz.");
    
                RuleFor(x => x.Email)
                    .NotEmpty().WithMessage("Email zorunludur.")
                    .EmailAddress().WithMessage("Email formatı geçersiz.");
    
                // Nested validator kullanımı:
                RuleFor(x => x.AddressInfo)
                    .SetValidator(new AddressInfoValidator()!)
                    .When(x => x.AddressInfo != null);
            }
        }
    }

    Controller’da kullanımı

    controller’da kullanırken önce DI ile enjecte ettiğimiz sınıfı property olarak tanımlıyoruz.

    C#
      private readonly IValidator<RegisterDTO> _validator;
    
      public RegisterController(IValidator<RegisterDTO> validator)
      {
          _validator = validator;
      }

    Sonrasında bu sunufı controller içinde ilgili fonksiyonda manuel olarak kullanıyoruz.

    C#
    [HttpPost("signup")]
            public IActionResult Register([FromBody] RegisterDTO dto)
            {
                ValidationResult result = _validator.Validate(dto);
    
                if (!result.IsValid)
                {
                    var errors = result.Errors
                        .Select(e => new { Field = e.PropertyName, Message = e.ErrorMessage })
                        .ToList();
    
                    return BadRequest(new
                    {
                        Success = false,
                        Errors = errors
                    });
                }
    
                return Ok(new
                {
                    Success = true,
                    Message = "Kayıt başarılı.",
                    Data = dto
                });
            }
        }

    Controller’in son hali:

    📄 Controllers/RegisterController.cs

    C#
    using FluentValidation;
    using FluentValidation.Results;
    using FluentValidationDemo.DTOs;
    using Microsoft.AspNetCore.Mvc;
    
    namespace FluentValidationDemo.Controllers
    {
        [ApiController]
        [Route("api/[controller]")]
        public class RegisterController : ControllerBase
        {
            private readonly IValidator<RegisterDTO> _validator;
    
            public RegisterController(IValidator<RegisterDTO> validator)
            {
                _validator = validator;
            }
    
            [HttpPost("signup")]
            public IActionResult Register([FromBody] RegisterDTO dto)
            {
                ValidationResult result = _validator.Validate(dto);
    
                if (!result.IsValid)
                {
                    var errors = result.Errors
                        .Select(e => new { Field = e.PropertyName, Message = e.ErrorMessage })
                        .ToList();
    
                    return BadRequest(new
                    {
                        Success = false,
                        Errors = errors
                    });
                }
    
                return Ok(new
                {
                    Success = true,
                    Message = "Kayıt başarılı.",
                    Data = dto
                });
            }
        }
    }
    

    Program.cs dosyamızda aşağıdaki satır ile tanımladığımız validasyon sınıfını DI ine enjecte ediyoruz ve tabi opsiyonel swagger ayarları var ben test etmek için swagger’da kurdum siz kurmak zorunda değilsiniz.

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

    Program.cs son hali bu şekilde:

    📄 Program.cs

    C#
    
    using FluentValidation;  
    using ValidationProjecr.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.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    

    Bu videoda DI ile FluentValidation injecte ettik ve ilgili endpoint içeri,sinde çağıarark hataları kullanıcıya gösterdik.

    Bir sonraki yazıda görüşmek üzere.

    Örnek test sonucu ve api response

    Form verisini doğruladık herşey çok güzel ama bitmedi, dto oluşmadan önce tip uyumsuzlukları varsa eğer DTO hiç oluşmaz ve biz validasyonu hiç gerçekleştiremeyiz. Bu nedenle DTO oluşmadan önce bir global validasyon middleware’e ihtiyacımız var, bize ulaşmdan önce valide edecek nesnelerimizi ve biz her adımda tek tek uğraşmayacağız.

  • ASP.NET CORE SWAGGER AUTHORIZE BUTONU İLE JWT TEST ETMEK

    ASP.NET CORE SWAGGER AUTHORIZE BUTONU İLE JWT TEST ETMEK

    ASP.NET Core projelerinde swagger kurulumu’nu bir önceki yazıda anlatmıştım. Bu yazıda sıra geldi JWT (Json Web Token) ile istek göndermeye.

    Swagger’da yaptığımız varsayılan konfigürasyon ile sadece swagger dökümanları oluşuyor JWT desteği için küçük bir ayar gerekli.

    Öncelikle Program.cs dosyasının bir önceli versiyonunu baz alarak güncellenmiş halini paylaşıyorum.

    C#
    using Microsoft.OpenApi.Models;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    
    builder.Services.AddControllers();
    // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
    builder.Services.AddOpenApi();
    
    
    // Swagger servisini ekle
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen(options =>
    {
        // 🔒 Swagger'da Bearer token giriş alanı tanımla
        options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
        {
            Name = "Authorization",
            Type = SecuritySchemeType.Http,
            Scheme = "Bearer",
            BearerFormat = "JWT",
            In = ParameterLocation.Header,
            Description = "Lütfen 'Bearer {token}' formatında giriniz. (örnek: Bearer eyJhbGciOiJIUzI1...)"
        });
    
        // Her endpoint'te Bearer zorunlu olsun
        options.AddSecurityRequirement(new OpenApiSecurityRequirement
        {
            {
                new OpenApiSecurityScheme
                {
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer"
                    }
                },
                Array.Empty<string>()
            }
        });
    });
    
    
    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;
        }); // set options to root directory of api
    }
    
    app.UseHttpsRedirection();
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    

    Burada yaptığım tek değişiklik DI ile AddSwaggerGen metoduna bazı ayarlar eklemek oldu.

    Bu satırı:

    C#
    builder.Services.AddSwaggerGen();

    Bunun ile değiştirdim:

    C#
    
    builder.Services.AddSwaggerGen(options =>
    {
        // 🔒 Swagger'da Bearer token giriş alanı tanımla
        options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
        {
            Name = "Authorization",
            Type = SecuritySchemeType.Http,
            Scheme = "Bearer",
            BearerFormat = "JWT",
            In = ParameterLocation.Header,
            Description = "Lütfen 'Bearer {token}' formatında giriniz. (örnek: Bearer eyJhbGciOiJIUzI1...)"
        });
    
        // Her endpoint'te Bearer zorunlu olsun
        options.AddSecurityRequirement(new OpenApiSecurityRequirement
        {
            {
                new OpenApiSecurityScheme
                {
                    Reference = new OpenApiReference
                    {
                        Type = ReferenceType.SecurityScheme,
                        Id = "Bearer"
                    }
                },
                Array.Empty<string>()
            }
        });
    });

    Ek olarak sayfanın başına :

    C#
    using Microsoft.OpenApi.Models;

    Paketimi dahil ettim ekledim (eğer yoksa)

    Sonuç:

    Peki biz burada ne yaptık.

    Kısaca AddSecurityRequirement ve AddSecurityDefinition kullanarak endpointlerimize header üzerinden JWT gönderimini mümkün kıldık.

    ASP.NET Core projenize JWT Entegrasyonu için bu yazıyı takip edebilirsiniz :

  • AutoMapper Nedir, Nasıl Kullanılır ASP.NET CORE + EF CORE Örnek Anlatım

    AutoMapper Nedir, Nasıl Kullanılır ASP.NET CORE + EF CORE Örnek Anlatım

    Automapper, adından da anlayabileceğiniz gibi bir objeyi başka bir objeye dönüştüren bir yapıdır. Hemen basit kullanım örneği ile sizlere bunu anlatalım.

    Katmanlı bir mimari oluştururken benzer verileri tutan bir çok nesne ile uğraşırız, request gelir UserRequestDTO ile request içeriğini alırız, sonrasında ise User Enttiy oluşturur bunu veritabanına eklemeye çalışırız.

    Eğer manuel bir eşleme yapacak olursak her property’i teker teker birbirine eşitleriz ve yeni nesnemizi oluştururuz.

    Automapper’den önce şu şekilde ilerliyordu:

    C#
    public class User
    {
        public int Id { get; set; }
        public string FullName { get; set; }
        public string Email { get; set; }
    }
    
    public class UserDto
    {
        public string FullName { get; set; }
        public string Email { get; set; }
    }

    User ve UserDto entity’leri arasında dönüşüm yapmak için şu şekilde manuel bir kodlama yapmamız gerekiyordu.

    C#
    var user = new User { Id = 1, FullName = "Gökhan Çelik", Email = "[email protected]" };
    
    var dto = new UserDto
    {
        FullName = user.FullName,
        Email = user.Email
    };

    Böylelikle User modelinden yeni bir UserDto oluşturmuış oluyorduk.

    AutoMapper ile birlikte her kullanımda bu şekilde manuel uğraşmak ve kod tekrarına sebep olmak yerine bir ara metod oluşturuyoruz ve bu metod bize dönüştürüp yeni nesneyi veriyor.

    Öncelikle automapper için paketimizin kurulumunu yapalım.

    C#
    dotnet add package AutoMapper.Extensions.Microsoft.DependencyInjection

    Şimdi ise User ve UserDto sınıfları arası nesneleri otomatik çevirecek mapper sınıfımızı yazalım.

    📁Mappings/UserProfile.cs

    C#
    using AutoMapper;
    using BookStore.Api.Models; // örnek namespace
    using BookStore.Api.DTOs;
    
    namespace BookStore.Api.Mappings
    {
        public class UserProfile : Profile
        {
            public UserProfile()
            {
                CreateMap<User, UserDto>();
                CreateMap<UserDto, User>(); // iki yönlü istersen
            }
        }
    }

    Burada CreateMap ile hangi tür sınıfları birbirine dönüştürmek istediğimizi belirtiyoruz ve gerisi ile mapper ilgileniyor.

    Peki diyelim ki sınıflarımızın property isimleri birbiri ile uymuyor. Bu durumda ne yapacağız?

    Bu durumda da aşağıdaki gibi özelleştirmeler yapabiliriz.

    C#
    // Bu tamamen opsiyonal bir özelleştirme
    CreateMap<User, UserDto>()
        .ForMember(dest => dest.Name, opt => opt.MapFrom(src => src.FullName));
    

    Automapper sınıfımızı kullanmadan önce DI ile projemize tanıtmamız gerekiyor.

    📁Program.cs

    C#
    builder.Services.AddAutoMapper(typeof(Program));

    Peki şimdi sırada gerçek kullanım örneği var. Mapper sınıfımızı oluşturduk. Nesneleri birbirine nasıl dönüştüreceğimizi tanımladık, şimdi bunu contoller içinde kullanalım.

    C#
    using AutoMapper;
    using Microsoft.AspNetCore.Mvc;
    
    [ApiController]
    [Route("api/[controller]")]
    public class UsersController : ControllerBase
    {
        private readonly IMapper _mapper;
    
        public UsersController(IMapper mapper)
        {
            _mapper = mapper;
        }
    
        [HttpGet]
        public IActionResult GetUser()
        {
            var user = new User { Id = 1, FullName = "Gökhan Çelik", Email = "[email protected]" };
            var dto = _mapper.Map<UserDto>(user);
            return Ok(dto);
        }
    }
    

    Bu şekilde basitçe User sınıfımızın nesnesini USerDto olarak dönüştürmüş olduk…

    Buraya kadar yazımı okuduysanız düşüncelerinizi yorum olarak bırakmayı unutmayın.

    Özet

    Automapper basitçe sınıfların nesnelerini birbirine dönüştüren bir ara sınıf diyebiliriz.

  • ASP.NET CORE SWAGGER KURULUMU VE  NEDİR?

    ASP.NET CORE SWAGGER KURULUMU VE NEDİR?

    Swagger’ın değerini sadece kullananlar bilir, developer dostudur swagger, yarı yolda bırakmaz. Müşteriye & kullanıcıya ulaşmadan 1 tıkla test ettirir endpointleri, kabul edilen json yapısını hazır bekletir. Sen sadece TRY’a tıklarsın sonucu görürsün. Bazen crash olur hatanı görürsün bazen success olur mutlu olursun. Dürüst adamdır neyse sonuç onunla yüzleştirir seni…

    Bu yazımızın konusu ASP.NET CORE projesi için swagger entegrasyonu. Bildiğin gibi swagger artık direk kurulu gelmiyor ama hala iki tık uzağımızda.

    Nedir bu swagger?

    Swagger bir api dökümantasyon ve endpoint test aracıdır. Bir web api geliştirdiğinde onu otomatik olarak dökümante eder, ve sana tüm endpointlerin bir listesini çıkartır. Swagger bir OpenAPI Specification (OAS) standartıdır. Test arayüzü sunar, annotation ve metod isimlerinden ve parametlererinden otomatik girdileri field olarak karşına çıkartır.

    • Otomatik kodları tarar ve endpointleri oluşturur.
    • Test arayüzü sunar.
    • Authorize butonuna tıklayarak JWT girebilirsin ve bu sayede korumalı endpoinleri de test edebilirsin.

    ASP.NET Core ‘da Swagger kuralım (.net 9 ve üzeri)

    Öncelikle paketlerimizi ekleyelim projemize, arayüz kullananlar direk nuget üzerinden de yapabilir.

    PowerShell
    dotnet add package Swashbuckle.AspNetCore
    

    Bu kodu proje dizininde çalıştırırsan swagger projene konuk oyuncu olarak katılır. Öyle misafir gibi de davranmaz hemen birkaç komutla işe koyulur…

    Paketimizi kurduktan sonra tek bir dosya üzerinden development ortamımıza dahil edebiliriz swagger’ı adım adım anlatacağım ve en sonda Program.cs’ın son halini vereceğim.

    Ekleyeceğimiz kod satırları sırayla şu şekilde.

    Program.cs Son hali

    C#
     
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    
    builder.Services.AddControllers();
    // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
    builder.Services.AddOpenApi();
    
    
    // Swagger servisini ekle
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    
    
    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;
        }); // set options to root directory of api
    }
    
    app.UseHttpsRedirection();
    
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    

    Şimdi adım adım nasıl entegre ettiğimizi anlatalım, entegrasyonumuz iki adımdan oluşuyor öncelikle dependency injection kısmı sonrasında ise option ayarları.

    “builder.Services.AddOpenApi();” satırından sonra ve “var app = builder.Build();” satırından önce swagger servislerini eklemiş olduk.

    C#
    builder.Services.AddOpenApi(); // bu satırdan sonra
    
    // Swagger servisini ekle
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    
    var app = builder.Build(); // bu satırdan önce

    sonrasında sadece development mode da çalışması için:

    C#
    // Configure the HTTP request pipeline.
    if (app.Environment.IsDevelopment())
    {
        app.MapOpenApi();
    }

    bu if blogu içeriğini şu şekilde düzenledik:

    C#
    // 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;
        }); // set options to root directory of api
    }

    Burada swagger’ın gerekli option’larını ve ana dizinde açılması için gerekli ayarlarını yapmış olduk.

    sonrasında projemizi çalıştırdığımızda ana dizine gittiğimizde direk swagger gökümantasyonu bizi karşılıyor:

    Bir sonraki yazımızda swagger ile JWT authorized api testi sizlerle olacak.

  • Connection String Nasıl Oluşturulur?  MSSQL – EF Core Veritabanı Bağlantısı

    Connection String Nasıl Oluşturulur?  MSSQL – EF Core Veritabanı Bağlantısı

    Her yeni SQL Server + EF Core projesi oluştururken tekrar – tekarar connection string oluşturmam gerekiyor ve bu gerçekten sıkıcı bir hal aldı, bu yazıca Connection String nasıl oluşturulur ve proje içinde appsettings.json içindeki bu connection string ve diğer değerlere nasıl erişilir bundan kendi dilimce bahsedeceğim ve ihtiyaç oldukça ben de sen de bu kaynağı takip ederek kullanabiliriz.

    Connection String Nedir?

    Connection String uygulamaların veritabanına nasıl bağlanacağı bilgisini saklayan bir metin değeridir aslında. İçerisinde Server, veritabanı adı, şifre, gibi bilgiler barındırır.

    İçeriği şu bileşenlerden oluşabilir:

    • Server(Sunucu adresimiz)
    • Database(Veritabanı adımız)
    • Trusted_Connection(Windows oturumu ile bağlan:Şifre gerektirmez)
    • TrustServerCertificate(Sertifika hatalarını yoksay)
    • Password(Şifre)
    • User Id(Kullanıcı adı)

    Connection String(Veritabanı bilgileri) Nereden Alınır?

    Connection string oluşturuken tüm durumlarda Server ve Database yani sunucu adrsi ve veritabanı adı gereklidir.

    Eğer SQL Server Management Studio Kullanıyorsanız aşağıdaki şekilde bu bilgilere erişebilirsiniz.

    Örnek Windows Authentication Database Connection String

    Connection String:

    JSON
    "Server=SERVERNAMEVALUE;Database=DATABASENAMEVALUE;Trusted_Connection=True;TrustServerCertificate=True"

    appsettings.DEVELOPMENT.json son hali:

    JSON
    { 
      "ConnectionStrings": {
        "DefaultConnection": "Server=GOKHAN\\SQLEXPRESS;Database=MyECommerceDB;Trusted_Connection=True;TrustServerCertificate=True"
      }, 
    }
    
    1. Yöntem Açılış Ekranı:

    Burada “Server Name” yazan yerde hali hazırda conneciton string için “Server” Kısmı bulunmaktadır ve gördüğünüz gibi Authenticationkısmı da “Windows Authentication” yani şifre gerektirmeyen bir yöntemdir.

    2.Veritabanı Adı

    Veritabanı adına da listelenen veritabanları üzerinden ulaşabilirsiniz. Aynı şekilde veritabanına sağ tıklayarak Properties > View Connection Properties bağlantısı üzerinden de veritabanı adına ve server adresine ulaşabilirsiniz.

    Ekranda Server Name ve Database bilgileri yer alıyor. bunları kullanarak connection string deki istediğiniz alanları düzenleyebilirsiniz.

    Örnek Kullanıcı Adı ve Şifre Girişi için Connection String

    Eğer veritabanınızı özel kullanıcı adı ve şifre ile oluşturduysanız örnek connection string şu şekilde olacak

    JSON
    "Server=localhost;Database=BookStoreDb;User Id=username1;Password=YourPassword123;TrustServerCertificate=True;"

    appsettings.DEVELOPMENT.json son hali

    JSON
    {
      "ConnectionStrings": {
        "DefaultConnection": "Server=localhost;Database=BookStoreDb;User Id=username1;Password=YourPassword123;TrustServerCertificate=True;"
      } 
    }
    

    İhtiyaç duydukça buradan girip kendi connection string’ini oluşturabilirsin.

  • ASP.NET CORE MSSQL VERİTABANI BAĞLANTISI VE EF KURULUMU

    ASP.NET CORE MSSQL VERİTABANI BAĞLANTISI VE EF KURULUMU

    Yeni bir asp.net core projesi oluşturduğunuzda büyük ihtimalle ilk yapmanız gerekenlerden biri “EF configuration” ile veritabanı bağlantısı kurmak ve modellerinizi EF’e tanıtmak olacaktır.

    Bu yazıda sıfırdan bir projede basitçe model oluşturup bir api endpoint üzerinden EF ile MSSQL veritabanına bağlanarak veri çekip kullanıcının tarayıcısına ileteceğiz. Sadece EF kısmına odaklanacağız ve diğer best practice’lere çok takılmayacağım.

    Bu yazıda EF : Microsoft Entity Framework kısaltmasıdır.

    Projemiz basit bir blog sistemi. Post ve Category modellerimiz bulunuyor.

    Yazımız başlangıç düzeyi bir yazıdır ve temellere odaklanmakta best practice örneği değil. Bilginize.. Sadece veritabanı konfigüraasyonuna odaklanıyoruz.

    Öncelikle gerekli paketlerimizi kuralım

    PowerShell
    dotnet add package Microsoft.EntityFrameworkCore
    dotnet add package Microsoft.EntityFrameworkCore.SqlServer
    dotnet add package Microsoft.EntityFrameworkCore.Tools

    Projemizin dosya yapısı şu şekilde

    PowerShell
    MyBlogApi/
    
    ├── Controllers/
    ├── Data/
    │   └── AppDbContext.cs
    ├── Models/
    │   ├── Post.cs
    │   └── Category.cs
    ├── Migrations/
    └── Program.cs

    Projemiz MyBlogApi namespace’i kullanıyor. Yazı boyunca “MyBlogApi” yerine kendi namespace’nizi yapıştırabilirsiniz. Eğer örneğe bağlı kalmak isterseniz projenizi “MyBlogApi” ile oluşturabilirsiniz.

    Veritabanı Bilgileri

    Öncelikle appsettings.json dosyasında ConnectionScrings->DefaultConnection değerini ekliyoruz.

    Eğer projemiz development mode da çalışıyorsa appsettings.Development.json dosyasını düzenlemelisiniz.

    Bu değer uygulamamız için veritabanı bilgilerini saklıyor.

    JSON
    {
      "ConnectionStrings": {
        "DefaultConnection": "Server=localhost;Database=MyBlogDb;Trusted_Connection=True;TrustServerCertificate=True"
      }
    }
    

    MSSQL – EF Core için Connection String Nasıl Oluşturulur? yazımı da inceleyerek bu string’i nasıl oluşturduğumu öğrenebilirsiniz. Örnekde herhangi bir kullanıcı adı ve şifre doğrulaması olmadan veritabanına bağlanmaktadır. Sizin durumunuzda farklı bir connection string gerekebilir.

    Model Tanımlama

    Modellerimizi tanımlayalım:

    Models/Post.cs

    C#
    namespace MyBlogApi.Models
    {
        public class Post
        {
            public int Id { get; set; }
            public string Title { get; set; } = null!;
            public string Content { get; set; } = null!;
            public int CategoryId { get; set; }
    
            public Category Category { get; set; } = null!;
        }
    }
    

    Models/Category.cs

    C#
    namespace MyBlogApi.Models
    {
        public class Category
        {
            public int Id { get; set; }
            public string Name { get; set; } = null!;
            public ICollection<Post> Posts { get; set; } = new List<Post>();
        }
    }

    Veritabanı Sınıfı ve Dependency Injection

    Veritabanı yapımızı ve modellerimizi EF’e tanıtmak için AppDbContext sınıfımızı oluşturalım.

    Data/AppDbContext.cs

    C#
    using Microsoft.EntityFrameworkCore;
    using MyBlogApi.Models;
    
    namespace MyBlogApi.Data
    {
        public class AppDbContext : DbContext
        {
            public AppDbContext(DbContextOptions<AppDbContext> options)
                : base(options) { }
    
            public DbSet<Post> Posts { get; set; }
            public DbSet<Category> Categories { get; set; }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                // İlişki tanımlama
                modelBuilder.Entity<Category>()
                    .HasMany(c => c.Posts)
                    .WithOne(p => p.Category)
                    .HasForeignKey(p => p.CategoryId);
    
                base.OnModelCreating(modelBuilder);
            }
        }
    }
    

    Sıradaki adımımız Program.cs dosyasına veritabanı giriş bilgilerimizi ve bağlantı sınıfımızı “Dependency Injection” ile tanıtmak.

    Bunun için “connection string” değerimizi okuyacağız ve AddDbContect extension method ile sınıfımızı DI’a ekleyeceğiz.

    Ekleyeceğimiz satırlar

    C#
    // Connection string
    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
    
    // EF Core servisini ekle
    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(connectionString));

    Program.cs son hali:

    C#
    using Microsoft.EntityFrameworkCore;
    using MyBlogApi.Data;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // Connection string
    var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
    
    // EF Core servisini ekle
    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(connectionString));
    
    builder.Services.AddControllers();
    builder.Services.AddEndpointsApiExplorer();
    builder.Services.AddSwaggerGen();
    
    var app = builder.Build();
    
    // Swagger
    if (app.Environment.IsDevelopment())
    {
        app.UseSwagger();
        app.UseSwaggerUI();
    }
    
    app.UseHttpsRedirection();
    app.MapControllers();
    app.Run();
    

    Son hali paylaşma sebebim, nereye ekleyeceğinizi tam anlamanız için bunu yapıyorum.

    Tabloları Oluşturalım

    EF sistemi “Code First” bir yaklaşım içerisindedir ve tanımladığımız modellerden otomatik olarak veritabanı tablolarımızı oluşturur. Bunun için aşağıdaki iki komut ile migration oluşturup veritabanımızı güncellememiz gerekir.

    C#
    dotnet ef migrations add InitialCreate
    dotnet ef database update

    Bu yazıda yer alan bilgiler .net 9 için test edilmiştir. Farklı sürümlerde farklı sonuçlar oluşyabilir. Dersimizin amacı başlangıç düzey bir belge oluşturmak.

  • ASP.NET Core JWT 1. Bölüm: Kurulum

    ASP.NET Core JWT 1. Bölüm: Kurulum

    Bu yazı dizimizde asp.net core web api projemizde jwt kullanarak aşağıdaki işlemleri gerçekleştireceğiz.

    1. Kurulum(Bu yazı): Bu kısımda temel EF Core configurasyonunu ve JWT configurasyonunu yapacağız ve amacımız ilk olarak JWT kısmını çalışır hale getirmek.
    2. Refresh Token sistemi: bu bölümde uygulamamıza refresh token sistemini ekleyeceğiz
    3. Policy & Role bazlı yetkilendirme: Bu bölümde yetkilendirmeye ve rollere odaklanacağız, bu sistemi nasıl daha performanslı hale getirebileceğiz buna kafa yoracağız.

    Konumuza JWT Nedir? Sorusu ile başlayalım bununile ilgili daha ayrıntılı bir yazı da yazacağız ama şimdilik kısaca değinelim

    Her yazının kodlarının son haline github üzerinden ulaşabilirsiniz : https://github.com/gokhancelebi/jwt-tutorial

    JWT Nedir?

    JWT’nin açılımı Json Web Token, aslında bildiğimiz bir string’den ibaret ama amacı kullanıcı hakkınca server’a bilgi vermek.

    JWT kimliği doğrulanmış kullanıcılar için server tarafından oluşturulan bir string’dir bu string içinde server’in eklediği bilgileri içerir ve kullanııcya iletilir.

    Kullanıcı her istek attığında kimliğini doğrulamak için bu tokeni de istekle birlikte sunucuya göndermelidir.

    Sunucu tarafında şifrelenmiş olan bu tokenin anahtarı sadece sunucuda vardır ve sunucu açar içini bakar bu token içerisinde kullanıcı ile ilgili hangi bilgiler var.

    Ardından eğer valid yani geçerli bir token ise kullanıcının kimliği ve yetlikeri doğrulanır ve bu çerçevede istekler atmasına izin verilir.

    Geçersiz ise oturumun geçersiz olduğu kullanıcıya bildirilir.

    JWT’nin Faydaları

    JWT’nin faydaları saymakla bitmez ama en önemli faydası stateless bir yapıya sahip olmasıdır, ölçeklediğiniz bir sistemde hangi sunucuya istek attığınızın bir önemi kalmaz çünkü jwt her node tarafından doğrulanabilir. Bu JWT nin en önemli faydasıdır, bunun dışında performans konusunda da bizlere yardımcı olur çünkü token valid ise ve cachelenmiş şekilde geçerli olduğunu sorgulayabiliyorsam tekrar veritabanına gitmeme kullanıcının yetkilerini ve kimliğini sorgulamama gerek kalmaz.

    Yani hem stateless hem de performanslı bir yapı kurmamızı sağlar.

    JWT’nin ne olduğunu ve faydalarını bilmemiz bizim açımızdan önemliydi, bilmediğimiz bir şeyi sistemimize dahil edersek karmaşa dışında bir işe hizmet etmeyecektir.

    JWT sistemini anladığımıza göre şimdi yavaştan ASP.NET Core projemizde JWT kurulumuna başlayalım!

    JWT KURULUMU

    JWT kurulumu için öncelikle bir webapi projesi oluşturuyoruz. Ben konsol üzerinden anlatacağım işlemleri siz isteseniz Visual Studio ile de aynı işlemleri gerçekleştirebilirsiniz.

    Proje oluşturma komutu

    PowerShell
    dotnet new webapi -n JwtAuthDemo

    Öncelikle EF Core paketlerini projemize dahil edelim

    PowerShell
    dotnet add package Microsoft.EntityFrameworkCore
    dotnet add package Microsoft.EntityFrameworkCore.SqlServer
    dotnet add package Microsoft.EntityFrameworkCore.Tools

    Şimdi sırada JWT için gerekli paket var

    PowerShell
    dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

    Bununla birlikte gerekli paketleri kurmuş olduk

    Şimdi sırada proje yapımız var, hedeflediğimiz proje yapısı şu şekilde olacak

    PowerShell
    JwtAuthDemo/
     ┣ Controllers/
    Data/
     ┣ Models/
     ┣ DTOs/
     ┣ Services/
     ┗ Program.cs

    Öncelikle kullanıcı modeli ile başlayalım

    Dosya: Models/User.cs

    C#
    namespace JwtAuthDemo.Models
    {
        public class User
        {
            public int Id { get; set; }
    
            public string Username { get; set; } = string.Empty;
    
            public string PasswordHash { get; set; } = string.Empty;
    
            public string Role { get; set; } = "User"; // Admin / User gibi roller
        }
    }

    Sırada DBContext var EF Core ile kullanmak için DBContext sınıfımızı oluşturalım

    Dosya: Data/AppDbContext.cs

    C#
    using Microsoft.EntityFrameworkCore;
    using JwtAuthDemo.Models;
    
    namespace JwtAuthDemo.Data
    {
        public class AppDbContext : DbContext
        {
            public AppDbContext(DbContextOptions<AppDbContext> options)
                : base(options)
            {
            }
    
            public DbSet<User> Users => Set<User>();
        }
    }
    

    JWT sistemimizin çalışabilir olması için bazı gerekli configurasyonlar var bunları appsettings.json’a ekleyelim.

    appsettings.json

    JSON
    {
      "ConnectionStrings": {
        "DefaultConnection": "Server=localhost;Database=JwtDemoDb;Trusted_Connection=True;TrustServerCertificate=True"
      },
      "Jwt": {
        "Key": "supersecretkey!123",
        "Issuer": "JwtAuthDemo",
        "Audience": "JwtAuthDemoUsers",
        "ExpireMinutes": 60
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
    

    EF Core DB Context ve JWT yapılandırmamızı Program.cs dosyamıza ekleyelim

    Dosya: Program.cs

    C#
    using JwtAuthDemo.Data;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.AspNetCore.Authentication.JwtBearer;
    using Microsoft.IdentityModel.Tokens;
    using System.Text;
    
    var builder = WebApplication.CreateBuilder(args);
    
    // EF Core
    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
    
    // Controller
    builder.Services.AddControllers();
    
    // JWT Authentication
    var jwtSettings = builder.Configuration.GetSection("Jwt");
    var key = Encoding.UTF8.GetBytes(jwtSettings["Key"]);
    
    builder.Services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = jwtSettings["Issuer"],
            ValidAudience = jwtSettings["Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(key)
        };
    });
    
    builder.Services.AddAuthorization();
    
    var app = builder.Build();
    
    app.UseHttpsRedirection();
    app.UseAuthentication();
    app.UseAuthorization();
    
    app.MapControllers();
    
    app.Run();
    

    Buraya kadar her şey tamam şimdi sırada token üretimi ve kontrolü için AuthController oluşturmak var

    Token oluşturmak için gerekli paketimizi projemize yükleyelim.

    PowerShell
    dotnet add package BCrypt.Net-Next

    Dosya: Controllers/AuthController.cs

    C#
    using JwtAuthDemo.Data;
    using JwtAuthDemo.Models;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.IdentityModel.Tokens;
    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using System.Text;
    
    namespace JwtAuthDemo.Controllers
    {
        [ApiController]
        [Route("api/[controller]")]
        public class AuthController : ControllerBase
        {
            private readonly AppDbContext _context;
            private readonly IConfiguration _config;
    
            public AuthController(AppDbContext context, IConfiguration config)
            {
                _context = context;
                _config = config;
            }
    
            [HttpPost("register")]
            public async Task<IActionResult> Register(User user)
            {
                user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(user.PasswordHash); // hashle
                _context.Users.Add(user);
                await _context.SaveChangesAsync();
                return Ok(new { message = "User registered" });
            }
    
            [HttpPost("login")]
            public async Task<IActionResult> Login(User login)
            {
                var user = await _context.Users.FirstOrDefaultAsync(u => u.Username == login.Username);
                if (user == null || !BCrypt.Net.BCrypt.Verify(login.PasswordHash, user.PasswordHash))
                    return Unauthorized("Invalid credentials");
    
                var token = GenerateJwtToken(user);
                return Ok(new { token });
            }
    
            private string GenerateJwtToken(User user)
            {
                var jwtSettings = _config.GetSection("Jwt");
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSettings["Key"]));
    
                var claims = new[]
                {
                    new Claim(JwtRegisteredClaimNames.Sub, user.Username),
                    new Claim("role", user.Role),
                    new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
                };
    
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
    
                var token = new JwtSecurityToken(
                    issuer: jwtSettings["Issuer"],
                    audience: jwtSettings["Audience"],
                    claims: claims,
                    expires: DateTime.Now.AddMinutes(double.Parse(jwtSettings["ExpireMinutes"])),
                    signingCredentials: creds
                );
    
                return new JwtSecurityTokenHandler().WriteToken(token);
            }
        }
    }
    

    AuthController sayesinde token oluşturma login/register işlemleri için gerekli endpointlerimizi oluşturmuş olduk.

    JWT mizi test etmek için bir test controller oluşturalım

    Dosya: Controllers/TestController.cs

    C#
    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    
    namespace JwtAuthDemo.Controllers
    {
        [ApiController]
        [Route("api/[controller]")]
        public class TestController : ControllerBase
        {
            [Authorize]
            [HttpGet("secure")]
            public IActionResult SecureEndpoint()
            {
                return Ok(new { message = "You are authorized!" });
            }
    
            [AllowAnonymous]
            [HttpGet("public")]
            public IActionResult PublicEndpoint()
            {
                return Ok(new { message = "Anyone can access this" });
            }
        }
    }
    

    Deploy etmeye hazır olsun diye migration larımızı oluşturalımı

    PowerShell
    dotnet ef migrations add InitialCreate
    dotnet ef database update

    Buraya kadar geldiyseniz artık projemiz hazır ve test edilebilir durumda Postman veya direk tarayıcı üzerinden sistemimizi test edebilirsiniz.

    Özet

    Bu yazıda sıfırdan bir projeye JWT ile kullanıcı kimliğini doğrulamayı entegre ettik. Herkese açık ve token korumalı endpointler oluşturarak bu özelliğimizi test ettik.

    Bir sonraki yazımızda Refresh Token Sistemini bu projemize ekleyeceğiz.

    Eğer swagger kullanarak JWT testi yapmak isterseniz:

  • ASP.NET Core: Middleware Nedir ve Kullanım Örneği

    ASP.NET Core projelerimizde middleware tanımlamak önemli bir özellik bu özellik sayesinde http isteklerini filtreleyebilir, tüm isteklerden önce veya sonra kod çalıştırabiliriz.

    Middleware Nedir?

    Middleware, bir web uygulamasında isteklerin uygulamaya ulaşmadan önce veya sonra ele alınmasını ve gerekli durumlarda gerekli işlemlerin yapılmasını sağlayan sınıflardır. ASP.NET Core Web API veya MVC Rozor Pages projelerinde bir pipeline( işleme hattı ) ile işlenir ve middleware kullanımı çok kolaydır.

    Hangi Durumlarda Kullanılır?

    Middleware kullanım senaryolarından bazıları şu şekilde:

    Loglama: isteklerden önce veya sonra istek ve isteği oluşturan kullanıcı hakkında log kayıtları oluşturabilirsiniz.

    Exception Handling: Hataları yakalamak için yapılar kurabilirsiniz. İsteğe bağlı olarak loglama yada belirli hata mesajları gösterme veya yönlendirmeler yapabilirsiniz.

    CORS Ayarları: Tarayıcıdan gelen cross orgin yani farklı kaynaklardan gelen istekleri kısıtlayabilirsiniz.

    Yetki / Yetkilendirme Kontrolü: Authorizon veya Authentication yapıları kurarak kullanıcıyı ve yetkilerini kontrol edebilirsiniz.

    Performans Ölçümü: İsteğin öncesinde ve sonrasında kod çalıştırabildiğimizi söylemiştik, bu sayede uygulamamızın bir isteği ne kadar sürede işlediğini ve sonucu hazırladığını ölçebiliriz.

    Rate Limiting, IP veya Lokasyon Kısıtlama: Bir kullanıcının hangi sıklıkla istek atabileceğini limitleyebilir veya uygulamaya erişimini tamamen kısıtlayabilirsiniz.

    Liste daha uzar ama genel olarak ne yapılabileceğine değinmiş olduk. Şimdi örnek bir WEB API projesinde middleware ekleyelim.

    Basit Middleware Örneği

    Kullanıcının en fazla dakikada 100 kez istek atabilmesini sağlayalım.

    Middleware classlarına otomatik olarak bir parametre ekleniyor:
    RequestDelegate next

    middleware’i işleme alırken bu parametre üzerinden mevcut isteği tanıyabiliyoruz. Hemen örneğimize geçelim.

    Middlewares/RateLimitingMiddleware.cs

    C#
    // Middlewares/RateLimitingMiddleware.cs
    
    using Microsoft.AspNetCore.Http;
    using System.Collections.Concurrent;
    using System.Net;
    
    public class RateLimitingMiddleware
    {
        private readonly RequestDelegate _next;
        private static readonly ConcurrentDictionary<string, List<DateTime>> _requestLog = new();
    
        private const int LIMIT = 100; // max istek
        private static readonly TimeSpan WINDOW = TimeSpan.FromMinutes(1); // süre
    
        public RateLimitingMiddleware(RequestDelegate next)
        {
            _next = next;
        }
    
        public async Task InvokeAsync(HttpContext context)
        {
            string ip = context.Connection.RemoteIpAddress?.ToString() ?? "unknown";
    
            var now = DateTime.UtcNow;
    
            var requests = _requestLog.GetOrAdd(ip, _ => new List<DateTime>());
    
            lock (requests)
            {
                // Eski kayıtları temizle
                requests.RemoveAll(r => r < now - WINDOW);
    
                if (requests.Count >= LIMIT)
                {
                    context.Response.StatusCode = (int)HttpStatusCode.TooManyRequests;
                    context.Response.Headers["Retry-After"] = "60";
                    return;
                }
    
                requests.Add(now);
            }
    
            await _next(context);
        }
    }
    

    Bu örnekde dikkat etmemiz gereken bir nokta var o da next isimli bir sonraki request’i temsil eden ve context isimli mevcut request’in objenin sınıfımıza inject edilmiş olması.

    C#
    public RateLimitingMiddleware(RequestDelegate next)
    {
          _next = next;
    }
    

    Ve sonrasında InvokeAsync isimli fonksiyonumuzun içerisinde isteğimiz ile ilgili gerekli işlemleri yaptıkdan sonra context’i bir sonraki işleme göndermek.

    C#
    public async Task InvokeAsync(HttpContext context)
    {
        await _next(context);
    }

    Bu yapı bizim sistemimizin omurgası, bu yapı tüm pipeline da mevcut ve tüm middleware lar birbirine bu şekilde isteği iletiyor.

    Diğer kod kısımları örneğimize ait kısımlar.

    Şimdi sırada bu oluşturduğumuz middleware’i Program.cs dosyamızda sıraya almak var.

    Bunun için basitçe program.cs dosyamızı şu şekilde düzenliyoruz.

    C#
    // Program.cs
    
    using YourNamespace.Middlewares; // doğru namespace olduğuna dikkat et
    
    var builder = WebApplication.CreateBuilder(args);
    var app = builder.Build();
    
    // Rate Limiting Middleware en başta olabilir
    app.UseMiddleware<RateLimitingMiddleware>();
    
    app.MapControllers();
    app.Run();

    Burada birden fazla middleware eklersek eğer bilmemiz gereken bu middleware’lerin sırasının önemli olduğudur.

    Birden fazla middleware olduğunu varsayalım bu durumda akış şu şekilde olacaktır.

    C#
    İstek Akışı
    [C öncesi]
      [B öncesi]
        [A öncesi]
          [KULLANICI → RESPONSE]
        [A sonrası]
      [B sonrası]
    [C sonrası]

    Bu yazıda middleware’nin ne olduğunu inceledik ve basit bir örnek yapmış olduk. Bir sonraki yazıda görüşmek üzere.

    Video Özet

  • ASP.NET Core Web API: Pagination, Filtering ve Sorting Yapısını En İyi Şekilde Kurmak

    Modern web API’lerinde kullanıcıya büyük veri kümeleri sunarken sayfalama (pagination), filtreleme (filtering) ve sıralama (sorting) işlemleri neredeyse kaçınılmaz hale gelmiştir. Özellikle veritabanı sorgularının verimli yönetilmesi, istemci tarafında performansın artırılması ve kullanıcı deneyiminin iyileştirilmesi açısından bu yapıların doğru şekilde uygulanması oldukça kritiktir.

    Bu yazıda, .NET Web API projelerinde pagination, filtering ve sorting işlemlerini en iyi uygulama prensipleriyle (best practices) nasıl hayata geçirebileceğimizi adım adım ele alacağız. Başlamadan önce bu kavramların ne anlama geldiğini ve neden bu kadar önemli olduklarını birlikte inceleyelim.

    Entity Framework

    ASP.NET Core Web API projelerinde kullandığımız veritabanı iletişimini sağlayan pakete verilen isimdir, bir çok ilişkisel veritabanı ile tam uyumludur fakat biz bu yazımızda özellikle MSSQL için kodlama yaparken kullanacağız.

    Pagination (Sayfalama)

    Pagination, bir web sayfasının daha hızlı açılması ve sunucuya aşırı yük bindirmemesi için bölümlere ayrılması ve kayıtların belirli limlitlerle sayfalara ayrılmış olarak görüntülenmesine verilen isimdir.

    Veritabanından veri çekerken bu limitin uygulanması için offset yani verinin başlangıç sırası ve limit yani kaç adet veri çekileceği belirtilerek bu işlem gerçekleştirilir.

    Filtering(Filtreleme)

    Filtreleme veritabanındaki kayıtları listelerken belirli kriterleri baz alarak eleme işlemine verilen isimdir. Örneğin 1995 ve üzeri model araçlar gibi filtreler veritabanı sorgularına eklenerek bu kriter ile uyumlu olmayan kayıtlar elenir.

    Sorting(Sıralama)

    Sıralam işlemi web api’lar da sıkça kullanılan bir yöntemdir, bu yöntem ile kayıtlar belli bir kritere göre sıralanır, bazen son eklenen önce bazen ise farklı sütunlara dayalı olarak bir sıralama gerçekleştirilir, örneğin yıl sutunu için azalandan artana şeklinde sorgular yazılır.

    Kavramlarımızdan sade bir şekilde bahsettiğimize göre şimdi sırada EF ile .NET Core Web API projesinde CarController içerisindeki GetAll endpoint’i kullanarak bu işlemleri nasıl gerçekleştirdiğimize bakalım.

    Bu yazımızın konusu olmadığı için sıfırdan veritabanı bağlantısı ve kontroller oluşturma ve endpoint ile map’leme işlemine odaklanmayacağım, direk controller üzerinden EF kullanarak bu işlemleri CarController üzerinden gerçekleştireceğim.

    Siz veritabanı bağlantısını vs zaten kurduysanız bu adımları atlayabilirsiniz.

    Veritabanı Bağlantısının Sağlanması

    Öncelikle veritabanınıza ait connection string’i projemize dahil editoruz.

    Data/AppDbContext.cs

    C#
    using Microsoft.EntityFrameworkCore;
    using CarAPI.Models;
    
    namespace CarAPI.Data
    {
        public class AppDbContext : DbContext
        {
            public AppDbContext(DbContextOptions<AppDbContext> options)
                : base(options)
            {
            }
    
            public DbSet<Car> Cars => Set<Car>();
        }
    }

    appsettings.json

    C#
    {
      "ConnectionStrings": {
        "DefaultConnection": "Server=.;Database=CarDb;Trusted_Connection=True;"
      },
      "Logging": {
        "LogLevel": {
          "Default": "Information",
          "Microsoft.AspNetCore": "Warning"
        }
      },
      "AllowedHosts": "*"
    }
    

    Sonrasında veritabanı context nesnesini Dependency Injection ile kullanabilmek için Program.cs dosyasına builder oluşturulduktan sonraki satıra veritabanı için DI yapımızı ekliyoruz. Buna ek olarak gerekli bazı using direktifleri de ekliyoruz.

    Program.cs

    C#
    using CarAPI.Data;
    using Microsoft.EntityFrameworkCore;
    
    // var builder = ile başlayan satırın hemen soonrasına
    builder.Services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

    Proje Dosya Yapısına Genel Bir bakalım.

    /CarAPI
    
    ├── Controllers
    │   └── CarController.cs
    
    ├── DTOs
    │   └── CarQueryParameters.cs
    
    ├── Data
    │   └── AppDbContext.cs
    
    ├── Models
    │   └── Car.cs
    
    ├── Program.cs
    ├── CarAPI.csproj
    └── appsettings.json
    

    Controller

    Dependency injection ile veritabanı bağlantı nesnemizi inject ediyoruz.

    Belirli konulara odaklanacağımız için Repository – Service pattern’i kullanmayacağım.

    Sorgulara pagination, sorting, filtering eklemek için kullandığımız başlıca EF metodları var

    • .AsQueryable daha sonra build etmek için query başlatıyoruz.
    • .Where ile herhangi bir sütun için filtreleme ekleyebiliyoruz.
    • OrderBy ile sorgumuza istediğimiz şekilde sıralama ekleyebiliyoruz
    • CountAsync ile toplam kayıt sayısını alıyoruz, bu işlem sayfalama için gerekli oluyor.

    Temelde yapmak istediğimiz işlemlere bu dördü yetiyor.

    Controllers/CarController.cs

    C#
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using CarAPI.Data;
    using CarAPI.Models;
    using CarAPI.DTOs;
    
    namespace CarAPI.Controllers
    {
        [ApiController]
        [Route("api/[controller]")]
        public class CarController : ControllerBase
        {
            private readonly AppDbContext _context;
    
            public CarController(AppDbContext context)
            {
                _context = context;
            }
    
            /*
             * Tüm araçları listeleyen endpoint
             * Bu metod, filtreleme, sıralama ve sayfalama işlemlerini destekler
             */
            [HttpGet]
            public async Task<IActionResult> GetAll([FromQuery] CarQueryParameters queryParams)
            {
                // Veritabanındaki Cars tablosu üzerinden sorguya başlanır
                var query = _context.Cars.AsQueryable();
    
                // FILTRELEME ADIMI - MinYear değeri varsa, bu yıldan küçük olanlar elenir
                if (queryParams.MinYear.HasValue)
                    query = query.Where(c => c.Year >= queryParams.MinYear.Value);
    
                // FILTRELEME ADIMI - MaxYear değeri varsa, bu yıldan büyük olanlar elenir
                if (queryParams.MaxYear.HasValue)
                    query = query.Where(c => c.Year <= queryParams.MaxYear.Value);
    
                // SIRALAMA ADIMI - Kullanıcı hangi kolona göre sıralama istediğini belirtmişse, ona göre sıralanır
                if (!string.IsNullOrEmpty(queryParams.SortBy))
                {
                    // IsDescending true ise azalan, değilse artan sıralama yapılır
                    query = queryParams.IsDescending
                        ? query.OrderByDescending(e => EF.Property<object>(e, queryParams.SortBy))
                        : query.OrderBy(e => EF.Property<object>(e, queryParams.SortBy));
                }
    
                // TOPLAM KAYIT SAYISI - Sayfalama için kaç toplam kayıt olduğunu hesaplıyoruz
                var totalItems = await query.CountAsync();
    
                // SAYFALAMA ADIMI - Sayfa numarasına ve sayfa boyutuna göre veriler bölünür
                var cars = await query
                    .Skip((queryParams.Page - 1) * queryParams.ValidPageSize) // Kaç kayıt atlanacak
                    .Take(queryParams.ValidPageSize) // Kaç kayıt alınacak
                    .ToListAsync();
    
                // SAYFALAMA METADATASI - frontend’e bilgi vermek için header üzerinden metadata gönderiyoruz
                var metadata = new
                {
                    queryParams.Page,
                    PageSize = queryParams.ValidPageSize,
                    TotalItems = totalItems,
                    TotalPages = (int)Math.Ceiling(totalItems / (double)queryParams.ValidPageSize)
                };
    
                // Header'a özel bilgi ekleniyor, genelde frontend tarafında pagination bilgisi burada okunur
                Response.Headers.Add("X-Pagination", System.Text.Json.JsonSerializer.Serialize(metadata));
    
                // İstenilen filtre/sıralama/sayfalama işlemlerine göre sonuçlar döndürülür
                return Ok(cars);
            }
        }
    }
    


    Model

    Models/Car.cs

    C#
    namespace CarAPI.Models
    {
        public class Car
        {
            public int Id { get; set; }
            public string Brand { get; set; } = string.Empty;
            public string Model { get; set; } = string.Empty;
            public int Year { get; set; }
            public bool IsDeleted { get; set; } = false;
        }
    }

    DTO

    DTOs/CarQueryParameters.cs

    C#
    namespace CarAPI.DTOs
    {
        public class CarQueryParameters
        {
            public int? MinYear { get; set; }
            public int? MaxYear { get; set; }
            public string? SortBy { get; set; } = "Id";
            public bool IsDescending { get; set; } = false;
            public int Page { get; set; } = 1;
            public int PageSize { get; set; } = 10;
    
            private const int MaxPageSize = 50;
    
            public int ValidPageSize => (PageSize > MaxPageSize) ? MaxPageSize : PageSize;
        }
    }