Yazar: gokhan

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

  • YAPAY ZEKA İLE KOD YAZMAK NE KADAR GÜVENLİ VE HIZLI?

    Yapay zeka ile kod yazmak günümüzün gerçeği ve bunun dışında kalmayı kimseye öneremem fakat çok fazla güvenlik açığı bıraktığını ve bunların test & kontrol edilmesi gerektiğini bilmemiz gerekiyor.

    Bugün ihtiyacımı olan bir link kısaltma sistemini 45dk da cursor ile geliştirdim. Normalde hiç cursor kullanmadan geliştirmek istesem 1-2 günümü alacak bir sistem ayrıntılı istatistik, grafikler barındırıyor.

    Sistemi geliştirdikten sonra fark ettim ki çok basit bir uygulama olmasına rağmen yetkilendirme ve güvenlik konusunda yapay zekanın aklı çok karışık. Başlangıçta belirtmeme rağmen kullanıcılar arası yetki geçişi çok kolay ve kendi üye formunu manipüle etmeyi bilen biri admin yetkisini çok kolay bir şekilde elde edebilir.

    Özellikle böyle sistemler kolay geliştirildiği için ücretsiz olarak forumlarda vs. paylaşılıyor ve insanlar indirip kullanıyor.

    Bu konuda çok dikkatli olmamız ve özellikle güvenlik anlamında kodları incelememiz, kullanıcıların yetki geçişlerini veya sistemin dosya yükleme gibi özelliklerini manipüle etmesine izin vermeyecek şekilde düzenlememiz gerekiyor.

    NOT:
    Biliyorum şimdi çıkıp prompt oluşturuken bunları dökümante etmem gerektiğini söyleyenler olacak… Haklısınız ama prompt içerisinde belirtmiş olsanız bile yapay zeka söylediğinizi yapmayabiliyor, özellikle context arttığında çok öncelikli bir prompt bile girmiş olsanız istediğiniz sonucu vermiyor veya bir özellik eklemek istediğinizde başka bir özellik için güvenlik açığı doğurabiliyor.

    Kısacası yapay zeka süreci kısaltsa da projemize yeni bir gereksinim ekliyor, iyi bir kod review gerektiriyor.

    Projenin süresini kısaltıp kısaltmadığına siz karar verin…

    Şikeyet etmek değil, önlem almak gerekli. Yapay zeka artık yazılım dünyasının bir gerçeği. Kod yazmayı bilmeyenler için değil de zaten kod yazmayı bilip işleri otomatize etmek isteyenler için çok faydalı.

  • 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;
        }
    }
  • Merhaba Dünya

    Merhaba Dünya, adetini bozmadan bu blog sitemde neler olacak sizlerle paylaşmak istiyorum.

    Websitemin amacı bildiklerimi ve öğrendiklerimi hem bana hem de size kaynak olacak şekilde özetleyerek saklamak.

    Bir çok chatgpt sohbeti ve internet araştırmasında edindiğim bilgileri parça parça bir yerlere not ediyorum, bu blog sitesi ile birlikte artık bu parça parça metinleri birleştirip bir başvuru kaynağı oluşturmaya çalışacağım.

    Websitem ile ilgilendiğiniz ve yazımı okuduğunuz için teşekkür ederim.