RabbitMQ nedir sorusunu sormadan önce Event-driven architecture nedir sorusu ile yazıma başlamak istiyorum. RabbitMQ bu sistemde kullandığımız bir araçtır çünkü.
Event Driven Architecture Nedir?
EDA bir yazılım dizayn pattern’i dir ve yazılımımızın event yani olaylara vereceği reaction yani tepkileri vurgulayan ve bu şekilde ölçeklenebilir sistemler dizayn etmemizi sağlar.
Bu yöntem çok popüler bir yöntemdir ve yazılımınızı alt bölümlere ayırmanızı birbirine bağlı olmadan bu bölümlerin çalışmasını sağlar.
Projemiz büyüdüğünde bu sistemi geleneksel yazılım tasarımlarından daha iyi yönetilebilir projeler oluşturmamızı sağlar.
Kısaca yazılımımızdaki olaylara verilecek tepkiler üzerinden sistemimizi tasarlamamıza verilen isimdir.
Yazılımımızın başka başka parçaları farklı cihazlarda bile çalışabilir ve birbirlerinden haberdar olmalarına çok da gerek kalmaz, örneğin tek bilmeleri gereken yeni bir kullanıcının kaydoluğudur ve burumda gereken işlemi birbirlerinden haberleri olmadan gerçekleştirirler.
Biz yazılımımızda üye kaydolma event’i gerçekleştiğinde trigger’lanacak sistemleri kaydederiz ve onlar da ayrı ayrı çalışır!
Bu sistemlerden biri fail olursa diğeri zarar görmeden hayatına devam eder!
Aynı kaynakları paylaşmadıkları için de birbirlerini bloklamazlar!
Kullanıcı bir butona bastı ve bu butonun yapması gereken diyelim ki sipariş oluşturmak, fatura oluşturmak, stok düşmek ve aynı zamanda bunları gerekli yerlere mail göndermek diyelim. Bu durumda normal geleneksel uygulamalarda bunları sırayla yapar ve ziyaretçilerimizi bekletiriz. Ama event-driven architecture’da “order.created” event’i order bilgisi ile birlikte bu işlemleri yapan ayrı ayrı sistemlere mesaj gönderir, yeni sipariş oluştu her biriniz üzerinize düşenleri gerçekleştirin der ve kullanıcıya da siparişşin oluşturulduğu cevabını döner, kullanıcı bilgisayarını kapatır arkaplanda sistem faturayı, stok düşme işlemini, email ile bilgilendirme işlemlerini yapar ve ziyaretçinin bundan haberi bile olmaz!
Evet bu sistemler birbiri ile nasıl haberleşecek sorusu aklına gelmiştir diye düşünüyorum, hemen burada çevreye neredeyse piyasa standartı olmuş bir sistem giriyor. RabbitMQ!
RabbitMQ Nedir?
RabbitMQ açık kaynak kodlu bir message broker yazılımıdır, bu event-driven architecture içerisinde sistemlerin birbiri ile iletişim kurmasını sağlar, bir event gerçekleştiğinde bu event’den haberdar olması gereken, o event’e abone olmuş consumer yani tüketicileri haberdar eder.
RabbitMQ mesajları öncelikle “exchange” dediğimiz depolara gönderir ve ondan sonra da route key lerine göre abonelerine ulaştırır!
RabbitMQ “First-in, First-out algoritmasına göre çalışır, ilk giren event ilk işlenir,
RabbitMQ özellikle micro-servis mimarisinde oldukça popülerdir!
Bir önceki yazımda asp.net core projesine basitçe fluentvalidation ekledim ve request’leri valide ettim. Burada bir ufak sorun var oda model binding hataları, bu şekilde manuel validasyon yapmadan önce DTO’ request body den değerler alır ve nesne oluşturur.
Tip uyumsuzluğu
Eksik property
Farklı veri
Sebepleri ile bu DTO nesnesi oluşamaz bile ve uygulamamız validasyondan önce crush olur.
Bunu çözmek için uygulamamızı önceden middleware ile valide etmemiz gerekir ki bunun için ek tek bir class yeterlidir.
Bu yazıda ek bir middleware ekleyerek global bir validasyon sağlayacağım ve sorunumuz ortadan kalkmış olacak
📄 Middlewares/ValidationMiddleware.cs
C#
using FluentValidation;using FluentValidation.Results;using System.Text.Json;namespace FluentValidationDemo.Middlewares{publicclassValidationMiddleware{privatereadonly RequestDelegate _next;publicValidationMiddleware(RequestDelegate next){_next=next;}publicasync Task InvokeAsync(HttpContext context, IServiceProvider serviceProvider){var method =context.Request.Method;// Sadece POST ve PUT isteklerde çalışsınif(method!=HttpMethods.Post&&method!=HttpMethods.Put){await_next(context);return;}var endpoint =context.GetEndpoint();if(endpointisnull){await_next(context);return;}context.Request.EnableBuffering();usingvar reader =new StreamReader(context.Request.Body, leaveOpen:true);var body =awaitreader.ReadToEndAsync();context.Request.Body.Position=0;if(string.IsNullOrWhiteSpace(body)){await_next(context);return;}try{var controllerActionDescriptor =endpoint.Metadata.OfType<Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor>().FirstOrDefault();if(controllerActionDescriptor==null){await_next(context);return;}foreach(var parameter incontrollerActionDescriptor.Parameters){var validatorType =typeof(IValidator<>).MakeGenericType(parameter.ParameterType);var validator =serviceProvider.GetService(validatorType)as IValidator;if(validator==null)continue;object? model =null;try{var options =new JsonSerializerOptions{PropertyNameCaseInsensitive=true,NumberHandling=System.Text.Json.Serialization.JsonNumberHandling.Strict};model=JsonSerializer.Deserialize(body,parameter.ParameterType,options);}catch(JsonException){awaitWriteErrorResponse(context,StatusCodes.Status400BadRequest,new[]{new{field="body",message="Invalid JSON format or wrong data types."}});return;}if(model==null){awaitWriteErrorResponse(context,StatusCodes.Status400BadRequest,new[]{new{field="body",message="Request body could not be deserialized."}});return;} ValidationResult result =awaitvalidator.ValidateAsync(new ValidationContext<object>(model));if(!result.IsValid){awaitWriteErrorResponse(context,StatusCodes.Status400BadRequest,result.Errors.Select(e =>new{field=e.PropertyName,message=e.ErrorMessage}));return;}}}catch(Exception ex){awaitWriteErrorResponse(context,StatusCodes.Status500InternalServerError,new[]{new{field="internal",message=$"Validation middleware error: {ex.Message}"}});return;}context.Request.Body.Position=0;await_next(context);}privatestaticasync Task WriteErrorResponse(HttpContext context,int statusCode, IEnumerable<object> errors){context.Response.StatusCode=statusCode;context.Response.ContentType="application/json";var response =new{success=false,errors};awaitcontext.Response.WriteAsync(JsonSerializer.Serialize(response));}}publicstaticclassValidationMiddlewareExtensions{publicstatic IApplicationBuilder UseValidationMiddleware(this IApplicationBuilder app){returnapp.UseMiddleware<ValidationMiddleware>();}}}
Bu middleware tanıladıktan sonra Program.cs dosyamıza da ekliyoruz.
Program.cs’ middleware tanımlamak için tek satır kod eklememiz yetiyor bildiğiniz gibi.
C#
app.UseValidationMiddleware();
Tam olarak bu kadar.
Ve tabi program.cs son hali
C#
using FluentValidation;using FluentValidationDemo.Middlewares;using FluentValidationDemo.Validators;var builder =WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddControllers();builder.Services.AddOpenApi();// Swagger.NET 9’da manuel açılmalı:builder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();// ✅ FluentValidation ayarları builder.Services.AddValidatorsFromAssemblyContaining<RegisterValidator>();var app =builder.Build();// Configure the HTTP request pipeline.if(app.Environment.IsDevelopment()){app.MapOpenApi();app.UseSwagger();app.UseSwaggerUI(options =>{options.SwaggerEndpoint("/swagger/v1/swagger.json","ECommerce API V1");options.RoutePrefix=string.Empty;});}app.UseHttpsRedirection();app.UseValidationMiddleware();app.UseAuthorization();app.MapControllers();app.Run();
Artık controller’da tekrar valide etmene gerek yok, controller’daki validasyon kodlarını silebilirsin.
Bu yazımızdan önce aşağıdaki yazıyı incelemeni tavsiye ederim.
Bu yazıda asp.net core web api projemizde fluentvalidation ile veri doğrulama işlemini gerçekleştireceğim, adım adım en basit haliyle anlatacağım ki siz de projenize entegre ederken sorun yaşamayın. Sadece validasyon kısmına odaklanacağım.
Annotation tabanlı validasyona göre çok daha esnek olan FluentValidation paketi .net dünyasında en çok kullanılan validasyon yöntemi.
En önemli faydalarından biri validasyon mantığını kurallar ile ayrı bir dosyada tanımlayabiliyorsunuz ve böylelikle kodunuz daha temiz ve yönetilebilir hale geliyor. İleride validasyon kuralı eklemek isterseniz ilgili dosyayı açarak ekleyebilirsiniz.
Hali hazırda bir .net 9 asp.net web api projesi başlattığınızı varsayacağım ve hemen paketlerimi ekleyerek başlayacağım.
FluentValidation paketini test etmek için ek olarak swagger’de kuracağım. Bildiğiniz gibi .net 9 ile birlikte default olarak entegre gelmiyor ve basit bir kaç adımla projemize dahil edebiliyoruz.
Ben CLI kullanacağım fakat siz isterseniz nuget paket yöneticisi ile de dahil edebilirsiniz aynı kapıya çıkar.
Paketlerimzi eklediğimize göre hemen DTO’ları tanımlayarak başlayalım.
Projemizde tek endpoint olacak kullanıcı kayıt endpointi ve nested(iç içe) DTO objelerini valide etmemiz gerekecek, RegisterDTO içerisinde AddressInfoDTO olacak.
namespace FluentValidationDemo.DTOs{publicclassAddressInfoDTO{publicstring? Phone {get;set;}=string.Empty;publicstring? Zip {get;set;}=string.Empty;}}
Sırada validasyon sınıfımız var, projemiz içinde istediğmiiz yerde kullanabilmek için validasyonumuzu ayrı bir sınıf olarak kodluyoruz.
Öncelikle alt nesne olan AddressInfoValidator sınıfını kodluyoruz.
📄 Validators/AddressInfoValidator.cs
C#
using FluentValidation;using FluentValidationDemo.DTOs;namespace FluentValidationDemo.Validators{publicclassAddressInfoValidator: AbstractValidator<AddressInfoDTO>{publicAddressInfoValidator(){RuleFor(x =>x.Phone).NotEmpty().WithMessage("Telefon numarası zorunludur.").Matches(@"^\+?\d{10,15}$").WithMessage("Telefon numarası formatı geçersiz.");RuleFor(x =>x.Zip).NotEmpty().WithMessage("Posta kodu zorunludur.").Length(4,10).WithMessage("Posta kodu 4 ile 10 karakter arasında olmalıdır.");}}}
Sınıfımız AbstractValidator sınıfından türetilmiş olacak ve <AddressInfoDTO> valide ettiğimiz sınıfı belirtmiş olacağız.
Sırada ana validasyon sınıfımız var, aynı şekilde AbstractValidator<RegisterDTO> üzerinden türeterek kurallarımızı tanımlıyoruz.
📄 Validators/RegisterValidator.cs
C#
using FluentValidation;using FluentValidationDemo.DTOs;namespace FluentValidationDemo.Validators{publicclassRegisterValidator: AbstractValidator<RegisterDTO>{publicRegisterValidator(){RuleFor(x =>x.FirstName).NotEmpty().WithMessage("Ad alanı boş olamaz.").MinimumLength(2).WithMessage("Ad en az 2 karakter olmalıdır.");RuleFor(x =>x.LastName).NotEmpty().WithMessage("Soyad alanı boş olamaz.");RuleFor(x =>x.Email).NotEmpty().WithMessage("Email zorunludur.").EmailAddress().WithMessage("Email formatı geçersiz.");// Nested validator kullanımı:RuleFor(x =>x.AddressInfo).SetValidator(new AddressInfoValidator()!).When(x =>x.AddressInfo!=null);}}}
Controller’da kullanımı
controller’da kullanırken önce DI ile enjecte ettiğimiz sınıfı property olarak tanımlıyoruz.
Program.cs dosyamızda aşağıdaki satır ile tanımladığımız validasyon sınıfını DI ine enjecte ediyoruz ve tabi opsiyonel swagger ayarları var ben test etmek için swagger’da kurdum siz kurmak zorunda değilsiniz.
using FluentValidation;using ValidationProjecr.Validators;var builder =WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddControllers();builder.Services.AddOpenApi();// Swagger.NET 9’da manuel açılmalı:builder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();// ✅ FluentValidation ayarları builder.Services.AddValidatorsFromAssemblyContaining<RegisterValidator>();var app =builder.Build();// Configure the HTTP request pipeline.if(app.Environment.IsDevelopment()){app.MapOpenApi();app.UseSwagger();app.UseSwaggerUI(options =>{options.SwaggerEndpoint("/swagger/v1/swagger.json","ECommerce API V1");options.RoutePrefix=string.Empty;});}app.UseHttpsRedirection();app.UseAuthorization();app.MapControllers();app.Run();
Bu videoda DI ile FluentValidation injecte ettik ve ilgili endpoint içeri,sinde çağıarark hataları kullanıcıya gösterdik.
Bir sonraki yazıda görüşmek üzere.
Örnek test sonucu ve api response
Form verisini doğruladık herşey çok güzel ama bitmedi, dto oluşmadan önce tip uyumsuzlukları varsa eğer DTO hiç oluşmaz ve biz validasyonu hiç gerçekleştiremeyiz. Bu nedenle DTO oluşmadan önce bir global validasyon middleware’e ihtiyacımız var, bize ulaşmdan önce valide edecek nesnelerimizi ve biz her adımda tek tek uğraşmayacağız.
Swagger’da yaptığımız varsayılan konfigürasyon ile sadece swagger dökümanları oluşuyor JWT desteği için küçük bir ayar gerekli.
Öncelikle Program.cs dosyasının bir önceli versiyonunu baz alarak güncellenmiş halini paylaşıyorum.
C#
using Microsoft.OpenApi.Models;var builder =WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddControllers();// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapibuilder.Services.AddOpenApi();// Swagger servisini eklebuilder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen(options =>{// 🔒 Swagger'da Bearer token giriş alanı tanımlaoptions.AddSecurityDefinition("Bearer",new OpenApiSecurityScheme{Name="Authorization",Type=SecuritySchemeType.Http,Scheme="Bearer",BearerFormat="JWT",In=ParameterLocation.Header,Description="Lütfen 'Bearer {token}' formatında giriniz. (örnek: Bearer eyJhbGciOiJIUzI1...)"});// Her endpoint'te Bearer zorunlu olsunoptions.AddSecurityRequirement(new OpenApiSecurityRequirement{{new OpenApiSecurityScheme{Reference=new OpenApiReference{Type=ReferenceType.SecurityScheme,Id="Bearer"}},Array.Empty<string>()}});});var app =builder.Build();// Configure the HTTP request pipeline.if(app.Environment.IsDevelopment()){app.MapOpenApi();app.UseSwagger();app.UseSwaggerUI(options =>{options.SwaggerEndpoint("/swagger/v1/swagger.json","ECommerce API V1");options.RoutePrefix=string.Empty;});// set options to root directory of api}app.UseHttpsRedirection();app.UseAuthorization();app.MapControllers();app.Run();
Burada yaptığım tek değişiklik DI ile AddSwaggerGen metoduna bazı ayarlar eklemek oldu.
Bu satırı:
C#
builder.Services.AddSwaggerGen();
Bunun ile değiştirdim:
C#
builder.Services.AddSwaggerGen(options =>{// 🔒 Swagger'da Bearer token giriş alanı tanımlaoptions.AddSecurityDefinition("Bearer",new OpenApiSecurityScheme{Name="Authorization",Type=SecuritySchemeType.Http,Scheme="Bearer",BearerFormat="JWT",In=ParameterLocation.Header,Description="Lütfen 'Bearer {token}' formatında giriniz. (örnek: Bearer eyJhbGciOiJIUzI1...)"});// Her endpoint'te Bearer zorunlu olsunoptions.AddSecurityRequirement(new OpenApiSecurityRequirement{{new OpenApiSecurityScheme{Reference=new OpenApiReference{Type=ReferenceType.SecurityScheme,Id="Bearer"}},Array.Empty<string>()}});});
Ek olarak sayfanın başına :
C#
using Microsoft.OpenApi.Models;
Paketimi dahil ettim ekledim (eğer yoksa)
Sonuç:
Peki biz burada ne yaptık.
Kısaca AddSecurityRequirement ve AddSecurityDefinition kullanarak endpointlerimize header üzerinden JWT gönderimini mümkün kıldık.
Automapper, adından da anlayabileceğiniz gibi bir objeyi başka bir objeye dönüştüren bir yapıdır. Hemen basit kullanım örneği ile sizlere bunu anlatalım.
Katmanlı bir mimari oluştururken benzer verileri tutan bir çok nesne ile uğraşırız, request gelir UserRequestDTO ile request içeriğini alırız, sonrasında ise User Enttiy oluşturur bunu veritabanına eklemeye çalışırız.
Eğer manuel bir eşleme yapacak olursak her property’i teker teker birbirine eşitleriz ve yeni nesnemizi oluştururuz.
User ve UserDto entity’leri arasında dönüşüm yapmak için şu şekilde manuel bir kodlama yapmamız gerekiyordu.
C#
var user =new User {Id=1,FullName="Gökhan Çelik",Email="[email protected]"};var dto =new UserDto{FullName=user.FullName,Email=user.Email};
Böylelikle User modelinden yeni bir UserDto oluşturmuış oluyorduk.
AutoMapper ile birlikte her kullanımda bu şekilde manuel uğraşmak ve kod tekrarına sebep olmak yerine bir ara metod oluşturuyoruz ve bu metod bize dönüştürüp yeni nesneyi veriyor.
Öncelikle automapper için paketimizin kurulumunu yapalım.
Şimdi ise User ve UserDto sınıfları arası nesneleri otomatik çevirecek mapper sınıfımızı yazalım.
📁Mappings/UserProfile.cs
C#
using AutoMapper;using BookStore.Api.Models;// örnek namespaceusing BookStore.Api.DTOs;namespace BookStore.Api.Mappings{publicclassUserProfile: Profile{publicUserProfile(){CreateMap<User, UserDto>();CreateMap<UserDto, User>();// iki yönlü istersen}}}
Burada CreateMap ile hangi tür sınıfları birbirine dönüştürmek istediğimizi belirtiyoruz ve gerisi ile mapper ilgileniyor.
Peki diyelim ki sınıflarımızın property isimleri birbiri ile uymuyor. Bu durumda ne yapacağız?
Bu durumda da aşağıdaki gibi özelleştirmeler yapabiliriz.
C#
// Bu tamamen opsiyonal bir özelleştirmeCreateMap<User, UserDto>().ForMember(dest =>dest.Name, opt =>opt.MapFrom(src =>src.FullName));
Automapper sınıfımızı kullanmadan önce DI ile projemize tanıtmamız gerekiyor.
📁Program.cs
C#
builder.Services.AddAutoMapper(typeof(Program));
Peki şimdi sırada gerçek kullanım örneği var. Mapper sınıfımızı oluşturduk. Nesneleri birbirine nasıl dönüştüreceğimizi tanımladık, şimdi bunu contoller içinde kullanalım.
C#
using AutoMapper;using Microsoft.AspNetCore.Mvc;[ApiController][Route("api/[controller]")]publicclassUsersController: ControllerBase{privatereadonly IMapper _mapper;publicUsersController(IMapper mapper){_mapper=mapper;}[HttpGet]public IActionResult GetUser(){var user =new User {Id=1,FullName="Gökhan Çelik",Email="[email protected]"};var dto =_mapper.Map<UserDto>(user);returnOk(dto);}}
Bu şekilde basitçe User sınıfımızın nesnesini USerDto olarak dönüştürmüş olduk…
Buraya kadar yazımı okuduysanız düşüncelerinizi yorum olarak bırakmayı unutmayın.
Özet
Automapper basitçe sınıfların nesnelerini birbirine dönüştüren bir ara sınıf diyebiliriz.
Swagger’ın değerini sadece kullananlar bilir, developer dostudur swagger, yarı yolda bırakmaz. Müşteriye & kullanıcıya ulaşmadan 1 tıkla test ettirir endpointleri, kabul edilen json yapısını hazır bekletir. Sen sadece TRY’a tıklarsın sonucu görürsün. Bazen crash olur hatanı görürsün bazen success olur mutlu olursun. Dürüst adamdır neyse sonuç onunla yüzleştirir seni…
Bu yazımızın konusu ASP.NET CORE projesi için swagger entegrasyonu. Bildiğin gibi swagger artık direk kurulu gelmiyor ama hala iki tık uzağımızda.
Nedir bu swagger?
Swagger bir api dökümantasyon ve endpoint test aracıdır. Bir web api geliştirdiğinde onu otomatik olarak dökümante eder, ve sana tüm endpointlerin bir listesini çıkartır. Swagger bir OpenAPI Specification (OAS) standartıdır. Test arayüzü sunar, annotation ve metod isimlerinden ve parametlererinden otomatik girdileri field olarak karşına çıkartır.
Otomatik kodları tarar ve endpointleri oluşturur.
Test arayüzü sunar.
Authorize butonuna tıklayarak JWT girebilirsin ve bu sayede korumalı endpoinleri de test edebilirsin.
ASP.NET Core ‘da Swagger kuralım (.net 9 ve üzeri)
Öncelikle paketlerimizi ekleyelim projemize, arayüz kullananlar direk nuget üzerinden de yapabilir.
PowerShell
dotnet add package Swashbuckle.AspNetCore
Bu kodu proje dizininde çalıştırırsan swagger projene konuk oyuncu olarak katılır. Öyle misafir gibi de davranmaz hemen birkaç komutla işe koyulur…
Paketimizi kurduktan sonra tek bir dosya üzerinden development ortamımıza dahil edebiliriz swagger’ı adım adım anlatacağım ve en sonda Program.cs’ın son halini vereceğim.
Ekleyeceğimiz kod satırları sırayla şu şekilde.
Program.cs Son hali
C#
var builder =WebApplication.CreateBuilder(args);// Add services to the container.builder.Services.AddControllers();// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapibuilder.Services.AddOpenApi();// Swagger servisini eklebuilder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();var app =builder.Build();// Configure the HTTP request pipeline.if(app.Environment.IsDevelopment()){app.MapOpenApi();app.UseSwagger();app.UseSwaggerUI(options =>{options.SwaggerEndpoint("/swagger/v1/swagger.json","ECommerce API V1");options.RoutePrefix=string.Empty;});// set options to root directory of api}app.UseHttpsRedirection();app.UseAuthorization();app.MapControllers();app.Run();
Şimdi adım adım nasıl entegre ettiğimizi anlatalım, entegrasyonumuz iki adımdan oluşuyor öncelikle dependency injection kısmı sonrasında ise option ayarları.
“builder.Services.AddOpenApi();” satırından sonra ve “var app = builder.Build();” satırından önce swagger servislerini eklemiş olduk.
C#
builder.Services.AddOpenApi();// bu satırdan sonra// Swagger servisini eklebuilder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();var app =builder.Build();// bu satırdan önce
sonrasında sadece development mode da çalışması için:
C#
// Configure the HTTP request pipeline.if(app.Environment.IsDevelopment()){app.MapOpenApi();}
bu if blogu içeriğini şu şekilde düzenledik:
C#
// Configure the HTTP request pipeline.if(app.Environment.IsDevelopment()){app.MapOpenApi();app.UseSwagger();app.UseSwaggerUI(options =>{options.SwaggerEndpoint("/swagger/v1/swagger.json","ECommerce API V1");options.RoutePrefix=string.Empty;});// set options to root directory of api}
Burada swagger’ın gerekli option’larını ve ana dizinde açılması için gerekli ayarlarını yapmış olduk.
sonrasında projemizi çalıştırdığımızda ana dizine gittiğimizde direk swagger gökümantasyonu bizi karşılıyor:
Bir sonraki yazımızda swagger ile JWT authorized api testi sizlerle olacak.
Her yeni SQL Server + EF Core projesi oluştururken tekrar – tekarar connection string oluşturmam gerekiyor ve bu gerçekten sıkıcı bir hal aldı, bu yazıca Connection String nasıl oluşturulur ve proje içinde appsettings.json içindeki bu connection string ve diğer değerlere nasıl erişilir bundan kendi dilimce bahsedeceğim ve ihtiyaç oldukça ben de sen de bu kaynağı takip ederek kullanabiliriz.
Connection String Nedir?
Connection String uygulamaların veritabanına nasıl bağlanacağı bilgisini saklayan bir metin değeridir aslında. İçerisinde Server, veritabanı adı, şifre, gibi bilgiler barındırır.
İçeriği şu bileşenlerden oluşabilir:
Server(Sunucu adresimiz)
Database(Veritabanı adımız)
Trusted_Connection(Windows oturumu ile bağlan:Şifre gerektirmez)
Burada “Server Name” yazan yerde hali hazırda conneciton string için “Server” Kısmı bulunmaktadır ve gördüğünüz gibi Authenticationkısmı da “Windows Authentication” yani şifre gerektirmeyen bir yöntemdir.
2.Veritabanı Adı
Veritabanı adına da listelenen veritabanları üzerinden ulaşabilirsiniz. Aynı şekilde veritabanına sağ tıklayarak Properties > View Connection Properties bağlantısı üzerinden de veritabanı adına ve server adresine ulaşabilirsiniz.
Ekranda Server Name ve Database bilgileri yer alıyor. bunları kullanarak connection string deki istediğiniz alanları düzenleyebilirsiniz.
Örnek Kullanıcı Adı ve Şifre Girişi için Connection String
Eğer veritabanınızı özel kullanıcı adı ve şifre ile oluşturduysanız örnek connection string şu şekilde olacak
Yeni bir asp.net core projesi oluşturduğunuzda büyük ihtimalle ilk yapmanız gerekenlerden biri “EF configuration” ile veritabanı bağlantısı kurmak ve modellerinizi EF’e tanıtmak olacaktır.
Bu yazıda sıfırdan bir projede basitçe model oluşturup bir api endpoint üzerinden EF ile MSSQL veritabanına bağlanarak veri çekip kullanıcının tarayıcısına ileteceğiz. Sadece EF kısmına odaklanacağız ve diğer best practice’lere çok takılmayacağım.
Bu yazıda EF : Microsoft Entity Framework kısaltmasıdır.
Projemiz basit bir blog sistemi. Post ve Category modellerimiz bulunuyor.
Yazımız başlangıç düzeyi bir yazıdır ve temellere odaklanmakta best practice örneği değil. Bilginize.. Sadece veritabanı konfigüraasyonuna odaklanıyoruz.
Projemiz MyBlogApi namespace’i kullanıyor. Yazı boyunca “MyBlogApi” yerine kendi namespace’nizi yapıştırabilirsiniz. Eğer örneğe bağlı kalmak isterseniz projenizi “MyBlogApi” ile oluşturabilirsiniz.
MSSQL – EF Core için Connection String Nasıl Oluşturulur? yazımı da inceleyerek bu string’i nasıl oluşturduğumu öğrenebilirsiniz. Örnekde herhangi bir kullanıcı adı ve şifre doğrulaması olmadan veritabanına bağlanmaktadır. Sizin durumunuzda farklı bir connection string gerekebilir.
Model Tanımlama
Modellerimizi tanımlayalım:
Models/Post.cs
C#
namespace MyBlogApi.Models{publicclassPost{publicint Id {get;set;}publicstring Title {get;set;}=null!;publicstring Content {get;set;}=null!;publicint CategoryId {get;set;}public Category Category {get;set;}=null!;}}
Models/Category.cs
C#
namespace MyBlogApi.Models{publicclassCategory{publicint Id {get;set;}publicstring Name {get;set;}=null!;public ICollection<Post> Posts {get;set;}=new List<Post>();}}
Veritabanı Sınıfı ve Dependency Injection
Veritabanı yapımızı ve modellerimizi EF’e tanıtmak için AppDbContext sınıfımızı oluşturalım.
Sıradaki adımımız Program.cs dosyasına veritabanı giriş bilgilerimizi ve bağlantı sınıfımızı “Dependency Injection” ile tanıtmak.
Bunun için “connection string” değerimizi okuyacağız ve AddDbContect extension method ile sınıfımızı DI’a ekleyeceğiz.
Ekleyeceğimiz satırlar
C#
// Connection stringvar connectionString =builder.Configuration.GetConnectionString("DefaultConnection");// EF Core servisini eklebuilder.Services.AddDbContext<AppDbContext>(options =>options.UseSqlServer(connectionString));
Program.cs son hali:
C#
using Microsoft.EntityFrameworkCore;using MyBlogApi.Data;var builder =WebApplication.CreateBuilder(args);// Connection stringvar connectionString =builder.Configuration.GetConnectionString("DefaultConnection");// EF Core servisini eklebuilder.Services.AddDbContext<AppDbContext>(options =>options.UseSqlServer(connectionString));builder.Services.AddControllers();builder.Services.AddEndpointsApiExplorer();builder.Services.AddSwaggerGen();var app =builder.Build();// Swaggerif(app.Environment.IsDevelopment()){app.UseSwagger();app.UseSwaggerUI();}app.UseHttpsRedirection();app.MapControllers();app.Run();
Son hali paylaşma sebebim, nereye ekleyeceğinizi tam anlamanız için bunu yapıyorum.
Tabloları Oluşturalım
EF sistemi “Code First” bir yaklaşım içerisindedir ve tanımladığımız modellerden otomatik olarak veritabanı tablolarımızı oluşturur. Bunun için aşağıdaki iki komut ile migration oluşturup veritabanımızı güncellememiz gerekir.
Bu yazıda yer alan bilgiler .net 9 için test edilmiştir. Farklı sürümlerde farklı sonuçlar oluşyabilir. Dersimizin amacı başlangıç düzey bir belge oluşturmak.
Bu yazı dizimizde asp.net core web api projemizde jwt kullanarak aşağıdaki işlemleri gerçekleştireceğiz.
Kurulum(Bu yazı): Bu kısımda temel EF Core configurasyonunu ve JWT configurasyonunu yapacağız ve amacımız ilk olarak JWT kısmını çalışır hale getirmek.
Refresh Token sistemi: bu bölümde uygulamamıza refresh token sistemini ekleyeceğiz
Policy & Role bazlı yetkilendirme: Bu bölümde yetkilendirmeye ve rollere odaklanacağız, bu sistemi nasıl daha performanslı hale getirebileceğiz buna kafa yoracağız.
Konumuza JWT Nedir? Sorusu ile başlayalım bununile ilgili daha ayrıntılı bir yazı da yazacağız ama şimdilik kısaca değinelim
JWT’nin açılımı Json Web Token, aslında bildiğimiz bir string’den ibaret ama amacı kullanıcı hakkınca server’a bilgi vermek.
JWT kimliği doğrulanmış kullanıcılar için server tarafından oluşturulan bir string’dir bu string içinde server’in eklediği bilgileri içerir ve kullanııcya iletilir.
Kullanıcı her istek attığında kimliğini doğrulamak için bu tokeni de istekle birlikte sunucuya göndermelidir.
Sunucu tarafında şifrelenmiş olan bu tokenin anahtarı sadece sunucuda vardır ve sunucu açar içini bakar bu token içerisinde kullanıcı ile ilgili hangi bilgiler var.
Ardından eğer valid yani geçerli bir token ise kullanıcının kimliği ve yetlikeri doğrulanır ve bu çerçevede istekler atmasına izin verilir.
Geçersiz ise oturumun geçersiz olduğu kullanıcıya bildirilir.
JWT’nin Faydaları
JWT’nin faydaları saymakla bitmez ama en önemli faydası stateless bir yapıya sahip olmasıdır, ölçeklediğiniz bir sistemde hangi sunucuya istek attığınızın bir önemi kalmaz çünkü jwt her node tarafından doğrulanabilir. Bu JWT nin en önemli faydasıdır, bunun dışında performans konusunda da bizlere yardımcı olur çünkü token valid ise ve cachelenmiş şekilde geçerli olduğunu sorgulayabiliyorsam tekrar veritabanına gitmeme kullanıcının yetkilerini ve kimliğini sorgulamama gerek kalmaz.
Yani hem stateless hem de performanslı bir yapı kurmamızı sağlar.
JWT’nin ne olduğunu ve faydalarını bilmemiz bizim açımızdan önemliydi, bilmediğimiz bir şeyi sistemimize dahil edersek karmaşa dışında bir işe hizmet etmeyecektir.
JWT sistemini anladığımıza göre şimdi yavaştan ASP.NET Core projemizde JWT kurulumuna başlayalım!
JWT KURULUMU
JWT kurulumu için öncelikle bir webapi projesi oluşturuyoruz. Ben konsol üzerinden anlatacağım işlemleri siz isteseniz Visual Studio ile de aynı işlemleri gerçekleştirebilirsiniz.
Proje oluşturma komutu
PowerShell
dotnet new webapi -n JwtAuthDemo
Öncelikle EF Core paketlerini projemize dahil edelim
namespace JwtAuthDemo.Models{publicclassUser{publicint Id {get;set;}publicstring Username {get;set;}=string.Empty;publicstring PasswordHash {get;set;}=string.Empty;publicstring Role {get;set;}="User";// Admin / User gibi roller}}
Sırada DBContext var EF Core ile kullanmak için DBContext sınıfımızı oluşturalım
Dosya:Data/AppDbContext.cs
C#
using Microsoft.EntityFrameworkCore;using JwtAuthDemo.Models;namespace JwtAuthDemo.Data{publicclassAppDbContext: DbContext{publicAppDbContext(DbContextOptions<AppDbContext> options):base(options){}public DbSet<User> Users =>Set<User>();}}
JWT sistemimizin çalışabilir olması için bazı gerekli configurasyonlar var bunları appsettings.json’a ekleyelim.
AuthController sayesinde token oluşturma login/register işlemleri için gerekli endpointlerimizi oluşturmuş olduk.
JWT mizi test etmek için bir test controller oluşturalım
Dosya:Controllers/TestController.cs
C#
using Microsoft.AspNetCore.Authorization;using Microsoft.AspNetCore.Mvc;namespace JwtAuthDemo.Controllers{[ApiController][Route("api/[controller]")]publicclassTestController: ControllerBase{[Authorize][HttpGet("secure")]public IActionResult SecureEndpoint(){returnOk(new{message="You are authorized!"});}[AllowAnonymous][HttpGet("public")]public IActionResult PublicEndpoint(){returnOk(new{message="Anyone can access this"});}}}
Deploy etmeye hazır olsun diye migration larımızı oluşturalımı
PowerShell
dotnet ef migrations add InitialCreatedotnet ef database update
Buraya kadar geldiyseniz artık projemiz hazır ve test edilebilir durumda Postman veya direk tarayıcı üzerinden sistemimizi test edebilirsiniz.
Özet
Bu yazıda sıfırdan bir projeye JWT ile kullanıcı kimliğini doğrulamayı entegre ettik. Herkese açık ve token korumalı endpointler oluşturarak bu özelliğimizi test ettik.
Bir sonraki yazımızda Refresh Token Sistemini bu projemize ekleyeceğiz.
Eğer swagger kullanarak JWT testi yapmak isterseniz:
Cursor gibi yapay zeka araçları ile MVP geliştirirken verim almak için şu yolu izliyorum:
Öncelikle tüm veritabanı tasarımını, migration dosyasını ve modellerini kendim oluşturuyorum.
İkinci adım olarak controller dosyalarını oluşturup örnek API response’larını, status code’ları tanımlıyorum.
Proje içerisinde bırakabildiğim kadar yorum bırakıyorum. Rule dosyalarına gerekli kodlama ve dosya yapısı açıklamalarını ekliyorum. Bunlardan en önemlisi, Cursor’un her özellikten sonra o özellik için .md dosyası oluşturarak kod ve özellik yapısını anlatması.
Veritabanı yapısı ve controller’ları belirlenmiş hiçbir projede Cursor ile sorun yaşamadım.
Bu adımlardan sonra gerekli özellikleri, ekranları, tasarım dosyalarını da tanıtıyorum ve bundan sonrasını tabii ki yapay zekâya bırakıyorum. Yapay zekâ ile MVP oluşturmak çok zevkli ve hızlı, projenin özellikle veritabanı yapısı ile ilgili kararları kendiniz verirseniz MVP noktasında hiçbir sorun yaşamadan hızlı sonuç alabiliyorsunuz.
Tabii yayınlamadan önce, XSS ve form manipülasyonunu önlemek için kodu review’den geçirmek gerekiyor. Bununla ilgili örnek controller’larda eğer önlem aldıysanız yapay zeka genelde aynı yöntemi kullanıyor ve sorunsuz sonuç verebiliyor. Tabii yine de bir incelemek lazım.
Peki ya sonrası?
Bundan sonraki süreçte, back-end noktasında sadece code-completion özelliğini kullanmak lazım. Agent modu çok tehlikeli olabiliyor. Ben özellikle tasarımcı ile çalışacak bütçem olmadığı için back-end’den ziyade front-end’de yapay zekâya başvuruyorum.
Sizlerin Cursor gibi agent’ler ile deneyiminiz nasıl?