Yazar: gokhan

  • RabbitMQ Nedir? Hangi Problemi Çözüyor?

    RabbitMQ Nedir? Hangi Problemi Çözüyor?

    RabbitMQ nedir sorusunu sormadan önce Event-driven architecture nedir sorusu ile yazıma başlamak istiyorum. RabbitMQ bu sistemde kullandığımız bir araçtır çünkü.

    Event Driven Architecture Nedir?

    EDA bir yazılım dizayn pattern’i dir ve yazılımımızın event yani olaylara vereceği reaction yani tepkileri vurgulayan ve bu şekilde ölçeklenebilir sistemler dizayn etmemizi sağlar.

    Bu yöntem çok popüler bir yöntemdir ve yazılımınızı alt bölümlere ayırmanızı birbirine bağlı olmadan bu bölümlerin çalışmasını sağlar.

    Projemiz büyüdüğünde bu sistemi geleneksel yazılım tasarımlarından daha iyi yönetilebilir projeler oluşturmamızı sağlar.

    Kısaca yazılımımızdaki olaylara verilecek tepkiler üzerinden sistemimizi tasarlamamıza verilen isimdir.

    Yazılımımızın başka başka parçaları farklı cihazlarda bile çalışabilir ve birbirlerinden haberdar olmalarına çok da gerek kalmaz, örneğin tek bilmeleri gereken yeni bir kullanıcının kaydoluğudur ve burumda gereken işlemi birbirlerinden haberleri olmadan gerçekleştirirler.

    Biz yazılımımızda üye kaydolma event’i gerçekleştiğinde trigger’lanacak sistemleri kaydederiz ve onlar da ayrı ayrı çalışır!

    Bu sistemlerden biri fail olursa diğeri zarar görmeden hayatına devam eder!

    Aynı kaynakları paylaşmadıkları için de birbirlerini bloklamazlar!

    Kullanıcı bir butona bastı ve bu butonun yapması gereken diyelim ki sipariş oluşturmak, fatura oluşturmak, stok düşmek ve aynı zamanda bunları gerekli yerlere mail göndermek diyelim. Bu durumda normal geleneksel uygulamalarda bunları sırayla yapar ve ziyaretçilerimizi bekletiriz. Ama event-driven architecture’da “order.created” event’i order bilgisi ile birlikte bu işlemleri yapan ayrı ayrı sistemlere mesaj gönderir, yeni sipariş oluştu her biriniz üzerinize düşenleri gerçekleştirin der ve kullanıcıya da siparişşin oluşturulduğu cevabını döner, kullanıcı bilgisayarını kapatır arkaplanda sistem faturayı, stok düşme işlemini, email ile bilgilendirme işlemlerini yapar ve ziyaretçinin bundan haberi bile olmaz!

    Evet bu sistemler birbiri ile nasıl haberleşecek sorusu aklına gelmiştir diye düşünüyorum, hemen burada çevreye neredeyse piyasa standartı olmuş bir sistem giriyor. RabbitMQ!

    RabbitMQ Nedir?

    RabbitMQ açık kaynak kodlu bir message broker yazılımıdır, bu event-driven architecture içerisinde sistemlerin birbiri ile iletişim kurmasını sağlar, bir event gerçekleştiğinde bu event’den haberdar olması gereken, o event’e abone olmuş consumer yani tüketicileri haberdar eder.

    RabbitMQ mesajları öncelikle “exchange” dediğimiz depolara gönderir ve ondan sonra da route key lerine göre abonelerine ulaştırır!

    RabbitMQ “First-in, First-out algoritmasına göre çalışır, ilk giren event ilk işlenir,

    RabbitMQ özellikle micro-servis mimarisinde oldukça popülerdir!

  • 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:

  • CURSOR İLE MVP OLUŞTURURKEN NASIL BİR YOL İZLİYORUM?

    Cursor gibi yapay zeka araçları ile MVP geliştirirken verim almak için şu yolu izliyorum:

    Öncelikle tüm veritabanı tasarımını, migration dosyasını ve modellerini kendim oluşturuyorum.

    İkinci adım olarak controller dosyalarını oluşturup örnek API response’larını, status code’ları tanımlıyorum.

    Proje içerisinde bırakabildiğim kadar yorum bırakıyorum.
    Rule dosyalarına gerekli kodlama ve dosya yapısı açıklamalarını ekliyorum. Bunlardan en önemlisi, Cursor’un her özellikten sonra o özellik için .md dosyası oluşturarak kod ve özellik yapısını anlatması.

    Veritabanı yapısı ve controller’ları belirlenmiş hiçbir projede Cursor ile sorun yaşamadım.

    Bu adımlardan sonra gerekli özellikleri, ekranları, tasarım dosyalarını da tanıtıyorum ve bundan sonrasını tabii ki yapay zekâya bırakıyorum.
    Yapay zekâ ile MVP oluşturmak çok zevkli ve hızlı, projenin özellikle veritabanı yapısı ile ilgili kararları kendiniz verirseniz MVP noktasında hiçbir sorun yaşamadan hızlı sonuç alabiliyorsunuz.

    Tabii yayınlamadan önce, XSS ve form manipülasyonunu önlemek için kodu review’den geçirmek gerekiyor. Bununla ilgili örnek controller’larda eğer önlem aldıysanız yapay zeka genelde aynı yöntemi kullanıyor ve sorunsuz sonuç verebiliyor. Tabii yine de bir incelemek lazım.

    Peki ya sonrası?

    Bundan sonraki süreçte, back-end noktasında sadece code-completion özelliğini kullanmak lazım. Agent modu çok tehlikeli olabiliyor. Ben özellikle tasarımcı ile çalışacak bütçem olmadığı için back-end’den ziyade front-end’de yapay zekâya başvuruyorum.

    Sizlerin Cursor gibi agent’ler ile deneyiminiz nasıl?