Yazılım Şehrinin Giriş Kapısı: Adını API Koydum

Kardel Rüveyda ÇETİN
13 min readFeb 11, 2024

Eveet… O beklenen ana geldik. Biraz hafızaları tazeleyelim isterseniz. Daha önce aşağıdaki yazılardan oluşan bir seriye başlamıştım. Ancak bu serinin son yazısı olan API katmanı ile ilgili olan kısmı bir türlü odaklanıp bitiremedim. Onu da bitirebilmek bugüne kısmetmiş. :)

Bugün sizlere daha önceki serilerde anlatmış olduğum katmanlar içerisindeki son katmanı aktarmaya çalışacağım. Yani sunum katmanı olarak geçen Presentation katmanı. Şimdi diyeceksiniz, tamam iyi hoş da bu başlık da ne alaka? Evet çok da haklısınız. Hemen açıklayayım; bu katman farklı isimlendirmelerle karşımıza çıkabiliyor, biz bu proje kapsamında sunum katmanını “API” olarak adlandıracağız. O yüzden bu katmanın adını API koyduk, diyebiliriz. :) Ee bundan yaklaşık 10 sene önce yayınlanan Adını Feriha Koydum dizisinden esinlenmedim değil. Saçma yönleri de olsa lisedeyken cuma günleri okuldan geldiğimde akşamları izlediğim bir diziydi. Ne diyorlar buna Guilty Pleasure mı? Heh benimkisi de tam ondan işte, benim de Guilty Pleasure’m klişe konulu Türk dizilerini veya filmlerini izlemiş olmam sanırım. ( Murat Soner bu makaleye yolun düşerse beni mazur gör. Hatasız kul olmaz derler,yaptım birtakım hatalar…) . Bunun temeli Yeşilçam sevdama dayanıyor ama o konulara hiç girmeyeyim yoksa çıkamayız…

Bu arada 10 yıl önce dizilerin afiş tasarımları biraz zayıfmıymış ne? Adını Feriha Koydum’un afişini Canva sağolsun 5 dakikada yaptım. Yetkililere duyurulur… Afişin orijinal hali de bu, ikisini de dene tarafını seç! :))) ( Bu arada bu Ücretli Sponsorlu Reklam değildir, Canva’yı övmek istedim, işte no kadar)

Haydi Başlayalımmm !

Presentation Layer neydi?

Presentation (API) katmanı, bir yazılım projesinde kullanıcı ve sistem arasındaki etkileşimi yöneten ana katmandır. Bu katman, genellikle web API’leri veya kullanıcı arayüzleri ile ilgili işlevleri içerir. API katmanında, genellikle Middlwares ve Filterlar gibi yapılar kullanılarak gelen isteklerin ve cevapların işlenmesi yönetilir. Middleware’ler, gelen isteği ve giden cevabı manipüle etmek, ek işlemler eklemek veya hata yönetimini ele almak için kullanılır. Filterlar ise genellikle isteğin veya cevabın belirli koşullara göre filtrelenmesi veya değiştirilmesi amacıyla kullanılır. Bu katman, kullanıcı arayüzleri veya dış sistemlerle etkileşimde bulunarak uygulamanın dış dünya ile iletişimini sağlar.

Biz Neler Yapacağız?

  • CustomBaseController Tanımlanması
  • TeamController,UserController,UserProfileController Oluşturulması
  • CRUD Metotlarının Detaylı İncelenmesi
  • Global Exception Handler
  • Autofac
  • JWT Entegrasyonu(BONUS)

CustomBaseController Oluşturulması

Aşağıdaki kod, özel bir temel denetleyici olan CustomBaseController sınıfını oluşturur. Bu temel denetleyici, tüm diğer denetleyicilerin bu sınıftan türetilmesini sağlar. CreateActionResult adlı bir metot içerir, bu metot , genel bir sonuç nesnesini alır ve buna göre bir IActionResult döndürür. Bu yöntem, belirli bir HTTP durum koduna göre uygun bir yanıt oluşturur. Örneğin, eğer durum kodu 204 ise, bir içeriği olmayan bir yanıt döndürür; aksi halde, genel sonuç nesnesini içeren bir yanıt döndürür. Bu yapı, kodun tekrarlanmasını azaltır, kodun daha temiz ve bakımının daha kolay olmasını sağlar. Ayrıca, tüm denetleyicilerin aynı yapıyı paylaşmasını sağlayarak uygulamanın tutarlılığını artırır. Bu şekilde, yazılım geliştirme sürecini optimize ederken kodun yeniden kullanılabilirliğini artırır ve geliştirme sürecini hızlandırır.

[Route("api/[controller]")]: Bu öznitelik, bir denetleyici sınıfının hangi istek yoluşunun bu denetleyiciyle eşleşeceğini belirtir. api/[controller] ifadesi, örneğin http://localhost:5000/api/MyController gibi bir isteğin bu denetleyiciye yönlendirileceğini gösterir. [controller] yer tutucusu, denetleyici sınıfının adını temsil eder ve çalışma zamanında denetleyici adıyla değiştirilir.

[ApiController]: Bu öznitelik, denetleyicinin bir Web API denetleyicisi olduğunu belirtir. Bu öznitelik, özellikle ASP.NET Core Web API uygulamalarında kullanılır ve denetleyicinin bazı davranışlarını ve varsayılan yapılandırmalarını etkinleştirir. Örneğin, model bağlama, model doğrulama, HTTP isteklerinin otomatik doğrulama ve yanıt olarak otomatik olarak JSON dönüşü gibi bazı özelliklerin varsayılan olarak etkinleştirilmesini sağlar. Bu öznitelik, denetleyicinin bir Web API'ye hizmet verdiğini açıkça belirtir ve uygun varsayılan ayarları sağlar.

    [Route("api/[controller]")]
[ApiController]
public class CustomBaseController : ControllerBase
{
[NonAction]
public IActionResult CreateActionResult<T>(GlobalResultDto<T> response)
{
if(response.StatusCode == 204)
{
return new ObjectResult(null)
{
StatusCode = response.StatusCode
};
}

return new ObjectResult(response)
{
StatusCode = response.StatusCode
};
}
}

Controller(Denetleyicilerin) Oluşturulması

TeamController

TeamController sınıfı, özel bir temel denetleyici olan CustomBaseController sınıfından türetilmiştir ve bu sayede tüm denetleyicilerde tekrarlanan kodları azaltır. Bu denetleyici, bir takım hizmeti sunar ve bu hizmeti kullanarak takımlarla ilgili işlemleri gerçekleştirir. IMapper ve ITeamService arayüzlerine bağımlılıklarını enjekte eder, böylece bağımlılık enjeksiyonu kullanarak bu hizmetleri kullanabilir.

Bu yapı, kodun yeniden kullanılabilirliğini artırır, kod tekrarını azaltır ve uygulamanın genel tutarlılığını sağlar. Ayrıca, bağımlılıkların enjekte edilmesi ve DTO’ların kullanılması, kodun temiz ve bakımı kolay olmasını sağlar.

    public class TeamController:CustomBaseController
{
private readonly IMapper _mapper;
private readonly ITeamService _teamService;
public TeamController(IMapper mapper, ITeamService teamService)
{
_mapper = mapper;
_teamService = teamService;
}
// api/Team/
[HttpGet]
public async Task<IActionResult> All()
{
var teams = await _teamService.GetAllAsync();
var teamsDto = _mapper.Map<List<TeamDto>>(teams.ToList());
return CreateActionResult(GlobalResultDto<List<TeamDto>>.Success(200, teamsDto));
}

[HttpGet("{id}")]
// Get api/team/3
public async Task<IActionResult> GetById(int id)
{
var team = await _teamService.GetById(id);
var teamDto = _mapper.Map<TeamDto>(team);
return CreateActionResult(GlobalResultDto<TeamDto>.Success(200, teamDto));
}

[HttpPost]
public async Task<IActionResult> Save(TeamDto teamDto)
{
var team = await _teamService.AddAsync(_mapper.Map<Team>(teamDto));
var teamDtos = _mapper.Map<TeamDto>(team);
return CreateActionResult(GlobalResultDto<TeamDto>.Success(201, teamDtos));
}

[HttpPut]
public async Task<IActionResult> Update(TeamDto teamDto)
{
await _teamService.UpdateAsync(_mapper.Map<Team>(teamDto));
return CreateActionResult(GlobalResultDto<NoContentDto>.Success(204));
}

[HttpDelete("{id}")]
public async Task<IActionResult> Remove(int id)
{
var team = await _teamService.GetById(id);
await _teamService.RemoveAsync(team);

return CreateActionResult(GlobalResultDto<NoContentDto>.Success(204));
}
}

UserController

UserController sınıfı, kullanıcı işlemlerini yönetmek için tasarlanmış bir denetleyicidir. Bu sınıf, kullanıcıları listeleyen, belirli bir kullanıcıyı alıp gösteren, kullanıcı bilgilerini güncelleyen ve kullanıcıları silen işlevleri içerir.

  • SignUp metodu, yeni bir kullanıcı oluşturur ve başarılı bir yanıt oluşturmak için CreateActionResult yöntemini kullanarak bir HTTP 201 yanıtı döndürür. Bu metot, bir kullanıcının sisteme kaydolmasını sağlar.
  • Login metodu, kullanıcı girişi için kullanılır. Kullanıcı adı ve parola ile giriş yapılır ve bu bilgilere göre bir yanıt döndürülür. Eğer giriş başarılı ise HTTP 200 yanıtı dönerken, başarısız ise HTTP 401 yanıtı döner.

Bu yapı, kullanıcı yönetimi işlemlerini tek bir yerde toplar ve bu işlemleri gerçekleştirmek için bir arayüz sağlar. Bu sayede, kod tekrarını azaltır, uygulamanın genel tutarlılığını sağlar ve bakımını kolaylaştırır.

    public class UserController : CustomBaseController
{
private readonly IMapper _mapper;
private readonly IUserService _userService;
public UserController(IMapper mapper, IUserService userService)
{
_mapper = mapper;
_userService = userService;
}
// api/Team/
[HttpGet]
public async Task<IActionResult> All()
{
var users = await _userService.GetAllAsync();
var userDto = _mapper.Map<List<UserDto>>(users.ToList());
return CreateActionResult(GlobalResultDto<List<UserDto>>.Success(200, userDto));
}

[HttpGet("{id}")]
// Get api/user/3
public async Task<IActionResult> GetById(int id)
{
var user = await _userService.GetById(id);
var userDto = _mapper.Map<UserDto>(user);
return CreateActionResult(GlobalResultDto<UserDto>.Success(200, userDto));
}

[HttpPut]
public async Task<IActionResult> Update(UserDto userDto)
{
await _userService.UpdateAsync(_mapper.Map<User>(userDto));
return CreateActionResult(GlobalResultDto<NoContentDto>.Success(204));
}

[HttpDelete("{id}")]
public async Task<IActionResult> Remove(int id)
{
var user = await _userService.GetById(id);
await _userService.RemoveAsync(user);

return CreateActionResult(GlobalResultDto<NoContentDto>.Success(204));
}

[HttpPost("Signup")]
public async Task<IActionResult> SignUp(AuthRequestDto authDto)
{
var user = _userService.SignUp(authDto);
var userDto = _mapper.Map<UserDto>(user);
return CreateActionResult(GlobalResultDto<UserDto>.Success(201, userDto));
}

[HttpPost("Login")]
public IActionResult Login(AuthRequestDto authDto)
{
var result = _userService.Login(authDto);
if (result.User != null)
return CreateActionResult(GlobalResultDto<AuthResponseDto>.Success(200, result));
else
return CreateActionResult(GlobalResultDto<AuthResponseDto>.Success(401, result));
}
}

UserProfileController

UserProfileController sınıfı, kullanıcı profilleriyle ilgili işlemleri yönetmek için tasarlanmış bir denetleyicidir. Bu sınıf, kullanıcı profillerini listeleme, belirli bir kullanıcı profiline erişme, yeni bir kullanıcı profili oluşturma, bir kullanıcı profili güncelleme ve bir kullanıcı profili silme işlevlerini içerir. Bu yapı, kullanıcı profilleriyle ilgili işlemleri tek bir yerde toplar ve bu işlemleri gerçekleştirmek için bir arayüz sağlar. Bu sayede, kod tekrarını azaltır, uygulamanın genel tutarlılığını sağlar ve bakımını kolaylaştırır.

    public class UserProfileController : CustomBaseController
{
private readonly IMapper _mapper;
private readonly IUserProfileService _userProfileService;
public UserProfileController(IMapper mapper, IUserProfileService userProfileService)
{
_mapper = mapper;
_userProfileService = userProfileService;
}
// api/Team/
[HttpGet]
public async Task<IActionResult> All()
{
var userProfiles = await _userProfileService.GetAllAsync();
var userProfilesDto = _mapper.Map<List<UserProfileDto>>(userProfiles.ToList());
return CreateActionResult(GlobalResultDto<List<UserProfileDto>>.Success(200, userProfilesDto));
}

[HttpGet("{id}")]
// Get api/user/3
public async Task<IActionResult> GetById(int id)
{
var userProfile = await _userProfileService.GetById(id);
var userProfileDto = _mapper.Map<UserProfileDto>(userProfile);
return CreateActionResult(GlobalResultDto<UserProfileDto>.Success(200, userProfileDto));
}

[HttpPost]
public async Task<IActionResult> Save(UserProfileDto userProfileDto)
{
var userProfile = await _userProfileService.AddAsync(_mapper.Map<UserProfile>(userProfileDto));
var userProfileDtos = _mapper.Map<UserProfileDto>(userProfile);
return CreateActionResult(GlobalResultDto<UserProfileDto>.Success(201, userProfileDtos));
}

[HttpPut]
public async Task<IActionResult> Update(UserProfileDto userProfileDto)
{
await _userProfileService.UpdateAsync(_mapper.Map<UserProfile>(userProfileDto));
return CreateActionResult(GlobalResultDto<NoContentDto>.Success(204));
}

[HttpDelete("{id}")]
public async Task<IActionResult> Remove(int id)
{
var userProfile = await _userProfileService.GetById(id);
await _userProfileService.RemoveAsync(userProfile);

return CreateActionResult(GlobalResultDto<NoContentDto>.Success(204));
}
}

CRUD Metotlarının Detaylı İncelenmesi

CRUD (Oluştur, Oku, Güncelle, Sil) işlemleri, birçok web uygulamasında temel işlevler arasında yer alır ve çoğu durumda tekrar tekrar kullanılır. Bu işlemler, genellikle veritabanı kayıtlarını yönetmek veya kaynakların (örneğin kullanıcılar, takımlar veya kullanıcı profilleri) işlemlerini gerçekleştirmek için kullanılır. Her üç denetleyici sınıfında da bu CRUD işlemleri yer almaktadır.

  • Oluştur (Create): Yeni bir kayıt oluşturma işlemidir. Örneğin, yeni bir kullanıcı oluşturmak veya yeni bir takım ekleme gibi işlemlerdir.
  • Oku (Read): Mevcut kayıtları görüntüleme veya belirli bir kaydı görüntüleme işlemidir. Örneğin, tüm kullanıcıları listeleme veya belirli bir kullanıcının profili gibi işlemlerdir.
  • Güncelle (Update): Mevcut bir kaydı güncelleme işlemidir. Örneğin, bir kullanıcının bilgilerini güncelleme veya bir takımın adını değiştirme gibi işlemlerdir.
  • Sil (Delete): Mevcut bir kaydı silme işlemidir. Örneğin, bir kullanıcıyı veya bir takımı silme gibi işlemlerdir.

Bu işlemler, uygulamanın farklı bölümlerinde veya farklı veri türlerinde kullanılsa da temel olarak aynı kalır. Bu nedenle, bu işlemleri tekrar tekrar yazmak yerine, temel bir denetleyici sınıfında toplamak ve diğer denetleyicilerden bu temel sınıfı türetmek daha verimli olabilir. Bu yaklaşım, kod tekrarını azaltır, bakımı kolaylaştırır ve uygulamanın genel tutarlılığını sağlar.

Temel bir denetleyici sınıfında CRUD işlemlerini merkezi bir yerde toplamak ve tekrar tekrar bu metotları yazmamak için ne yapabilirdik?

  • CrudBaseController, iki generic parametre alır: T ve TU. T, işlenen varlığı temsil ederken, TU bu varlığın DTO (Veri Transfer Nesnesi) temsilcisidir.
  • IMapper ve IService<T> bağımlılıkları enjekte edilir. IMapper, varlık ve DTO arasında dönüşümleri yönetirken, IService<T>, belirli bir varlık türü üzerinde işlem yapmak için gerekli olan genel bir hizmet arabirimini temsil eder.
  • All metodu, tüm varlıkları alır, bunları DTO'ya dönüştürür ve başarılı bir yanıt oluşturmak için CreateActionResult yöntemini kullanarak bir HTTP 200 yanıtı döndürür.
  • GetById metodu, belirli bir varlığı kimliğine göre alır, bu varlığı DTO'ya dönüştürür ve başarılı bir yanıt oluşturmak için CreateActionResult yöntemini kullanarak bir HTTP 200 yanıtı döndürür.
  • UpdateById metodu, belirli bir varlığı kimliğine göre günceller ve başarılı bir yanıt oluşturmak için CreateActionResult yöntemini kullanarak bir HTTP 200 yanıtı döndürür.
  • DeleteById metodu, belirli bir varlığı kimliğine göre siler ve başarılı bir yanıt oluşturmak için CreateActionResult yöntemini kullanarak bir HTTP 200 yanıtı döndürür.
  • AddAsync metodu, yeni bir varlık oluşturur ve başarılı bir yanıt oluşturmak için CreateActionResult yöntemini kullanarak bir HTTP 200 yanıtı döndürür.

Bu yapı, CRUD işlemlerini gerçekleştirmek için temel metotları sağlar ve bunları farklı varlık türleri ve DTO’lar için genelleştirir. Bu sayede, farklı varlık türleri için özel denetleyiciler oluşturulurken tekrar tekrar aynı temel CRUD işlemlerini yazmak gerekmez. Bu, kodun daha yeniden kullanılabilir, bakımı daha kolay ve genel olarak daha temiz olmasını sağlar.

    public abstract class CrudBaseController<T,TU> : CustomBaseController where T : BaseEntity where TU : BaseDto
{
private readonly IMapper _mapper;
private readonly IService<T> _tService;

public CrudBaseController(IMapper mapper, IService<T> service)
{
_mapper = mapper;
_tService = service;
}

[HttpGet]
public virtual async Task<IActionResult> All()
{
var allData = await _tService.GetAllAsync();
var allDataDto = _mapper.Map<List<TU>>(allData.ToList());
return CreateActionResult(GlobalResponseDto<List<TU>>.Success(200, allDataDto));
}

[HttpGet("{id}")]
public virtual async Task<IActionResult> GetById(Guid id)
{
var data = await _tService.GetByIdAsync(id);
var dataDto = _mapper.Map<TU>(data);
return CreateActionResult(GlobalResponseDto<TU>.Success(200, dataDto));
}

[HttpPut("{id}")]
public virtual async Task<IActionResult> UpdateById(Guid id, TU dataDto)
{
var data = _mapper.Map<T>(dataDto);
await _tService.UpdateAsync(data);

return CreateActionResult(GlobalResponseDto<string>.Success(200, "Kayıt başarıyla güncellendi!"));
}

[HttpDelete("{id}")]
public virtual async Task<IActionResult> DeleteById(Guid id)
{
var data = await _tService.GetByIdAsync(id);
await _tService.RemoveAsync(data);
return CreateActionResult(GlobalResponseDto<string>.Success(200, "Kayıt başarıyla silindi!"));
}

[HttpPost]
public virtual async Task<IActionResult> AddAsync(TU dataDto)
{
var data = _mapper.Map<T>(dataDto);
await _tService.AddAsync(data);
return CreateActionResult(GlobalResponseDto<string>.Success(200, "Kayıt başarıyla oluşturuldu!"));
}
}

Global Exception Handler

Aşağıdakikod, ASP.NET Core uygulamalarında özel bir istisna(exception) işleyicisi kullanımını tanımlar. UseCustomException adlı genişletme yöntemi, IApplicationBuilder arayüzüne eklenir ve özel bir istisna(exception) işleyicisinin uygulanmasını sağlar.

İstisna(Exception) işleyicisi, gelen isteği ele alır ve istisna türüne göre uygun bir yanıt döndürür. Özellikle, istisna türüne bağlı olarak HTTP durum kodunu belirler. Eğer istisna türü ClientSideException ise HTTP 400 (Bad Request) kodunu, NotFoundException ise HTTP 404 (Not Found) kodunu kullanır. Diğer tüm istisna türleri için ise HTTP 500 (Internal Server Error) kodu kullanılır. ( Bu exception türlerini hatırlarsanız servis katmanında yazmıştık.) Daha sonra, uygun durum kodu ve istisna mesajı ile birlikte bir yanıt oluşturulur. Bu yanıt, JSON formatında hata mesajını içerir ve istemciye döndürülmeden önce HTTP yanıt akışına yazılır.

Bu yapı, uygulamada oluşabilecek istisnaları(exceptionları( ele almak için merkezi bir mekanizma sağlar ve istemcilere uygun hata mesajlarını döndürmek için kullanılır. Bu, kodun daha temiz ve bakımının daha kolay olmasını sağlar, ayrıca istisnaların yönetimini kolaylaştırır.

    public static class UseCustomExceptionHandler
{

public static void UseCustomException(this IApplicationBuilder app)
{
app.UseExceptionHandler(config =>
{

config.Run(async context =>
{
context.Response.ContentType = "application/json";

var exceptionFeature = context.Features.Get<IExceptionHandlerFeature>();

var statusCode = exceptionFeature.Error switch
{
ClientSideException => 400,
NotFoundException => 404,
_ => 500
};
context.Response.StatusCode = statusCode;


var response = GlobalResultDto<NoContentDto>.Fail(statusCode, exceptionFeature.Error.Message);


await context.Response.WriteAsync(JsonSerializer.Serialize(response));

});

});
}
}

AutoFac

Autofac, .NET uygulamalarında bağımlılık enjeksiyonunu (DI) yönetmek için kullanılan bir IOC (Inversion of Control) konteyneridir. Bağımlılık enjeksiyonu, bir bileşenin, bağımlı olduğu diğer bileşenleri (hizmetler, veritabanı bağlantıları, yapılandırma ayarları vb.) kendisi oluşturmak yerine dışarıdan enjekte edilmesini sağlayan bir tasarım desenidir. Autofac, bu bağımlılıkların yönetimini kolaylaştırır ve uygulamanın bileşenlerini ve hizmetlerini düzenli bir şekilde bağlar, bu da daha modüler, esnek ve test edilebilir bir kod yazmanıza olanak tanır. Autfac’in amacı, uygulamanın bileşenlerini bağımlılıklardan arındırmak, kodu daha okunabilir ve bakımı daha kolay hale getirmektir. Bu projede AutoFac’i uygulayabilmek için API katmanında aşağıdaki modül yazılmıştır.

 public class RepoModuleService: Module
{
protected override void Load(ContainerBuilder builder)
{

//Generic olduğu için bu şekilde belirttik.
builder.RegisterGeneric(typeof(GenericRepository<>)).As(typeof(IGenericRepository<>)).InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(Service<>)).As(typeof(IService<>)).InstancePerLifetimeScope();
//Generic değil o yüzden type olarak ekledik.
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>();

//Bunun anlamı da bulunduğun klasörde ara demek
var apiAssembly = Assembly.GetExecutingAssembly();
//Repo katmanındaki herhangi bir classı typeof içine verebiliriz. Bu şekilde diğerlerini kendisi bulacaktır.
var repoAssembly = Assembly.GetAssembly(typeof(AppDbContext));
//Service katmanındaki herhangi bir classı typeof içine verebiliriz. Bu şekilde diğerlerini kendisi bulacaktır.
var serviceAssembly = Assembly.GetAssembly(typeof(MapProfile));

//InstancePerLifetimeScope => Scope a karşılık gelir.
//InstancePerDependency => Transient a karşılık gelir.
//Burada şunu demek istiyoruz, bu verilen Assembly'lere git ve sonu Repository ile bitenlerin Scope olarak instance'ını al. builder.Services.AddScoped<IProductRepository, ProductRepository>(); => bu yazdığımızı tüm repository yerine tek tek yazmak yerine bu şekilde bütün hepsi için Scope olarak instance alıyoruz.
builder.RegisterAssemblyTypes(apiAssembly, repoAssembly, serviceAssembly).Where(x => x.Name.EndsWith("Repository")).AsImplementedInterfaces().InstancePerLifetimeScope();

builder.RegisterAssemblyTypes(apiAssembly, repoAssembly, serviceAssembly).Where(x => x.Name.EndsWith("Service")).AsImplementedInterfaces().InstancePerLifetimeScope();
}
}

Kodu Beraber İnceleyelim mi?

  • RepoModuleService sınıfı, Autofac modülüdür ve Module sınıfını miras alır. Bu modül, uygulamanızdaki bağımlılıkları tanımlar ve enjekte edilmesi gereken hizmetleri yapılandırır.
  • Load metodu, ContainerBuilder nesnesini parametre olarak alır ve Autofac container'ına hizmetleri kaydeder.
  • builder.RegisterGeneric metodu, generic bir tür için kayıt yapar. GenericRepository<> türü için IGenericRepository<> arayüzünü kaydeder ve InstancePerLifetimeScope özelliğiyle kaydeder. Bu, her talep başına bir örnek oluşturur ve örnek, talep için bir yaşam döngüsü boyunca kullanılabilir.
  • builder.RegisterType metodu, belirli bir türü kaydeder. Burada UnitOfWork sınıfını IUnitOfWork arayüzüne kaydeder.
  • Assembly.GetExecutingAssembly() metodu, mevcut çalışan assembly'i temsil eder. Bu, uygulamanın çalıştığı assembly'yi alır.
  • Assembly.GetAssembly(typeof(AppDbContext)) metodu, belirtilen türün assembly'sini alır. Bu durumda, AppDbContext türünün assembly'sini alır.
  • builder.RegisterAssemblyTypes metodu, belirtilen assembly'deki türleri kaydeder. Where metodu, belirli bir koşulu sağlayan türleri seçer. EndsWith("Repository") ve EndsWith("Service") ifadeleri, türlerin adının belirli bir desenle bitip bitmediğini kontrol eder.
  • AsImplementedInterfaces metodu, her kaydedilen tür için uygulanan arayüzleri otomatik olarak kaydeder
  • InstancePerLifetimeScope özelliği, her talep başına bir örnek oluşturur ve örnek, talep için bir yaşam döngüsü boyunca kullanılabilir.

Program.cs’de nasıl tanımlama yapmalıyım?

  • Program.cs dosyasında, AutofacServiceProviderFactory kullanarak Autofac'i servis sağlayıcısı olarak ayarlar
  • ConfigureContainer yöntemi, Autofac modülünü uygular. Bu durumda, RepoModuleService modülü uygulanır.
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());

//Buradan Autofac kullanarak yazdığımız RepoServiceModule'ü dahil ediyoruz.
builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder => containerBuilder.RegisterModule(new RepoModuleService()));

JWT Entegrasyonu (BONUS)

JWT (JSON Web Token), kullanıcıların veya cihazların kimlik doğrulamasını sağlamak için kullanılan bir açık standarttır. Bu tokenlar, bilgilerin güvenli bir şekilde taşınmasını ve doğrulanmasını sağlamak için dijital imzalar veya şifreleme kullanır. Genellikle web uygulamaları ve API’lerde kimlik doğrulama, yetkilendirme ve oturum yönetimi için kullanılırlar. Bir JWT, kullanıcı girişi sırasında sunucu tarafından oluşturulur ve bu token, kullanıcının kimliğini sonraki isteklerde doğrulamak için kullanılır. JWT’ler ayrıca kullanıcıların rollerini veya izinlerini taşıyabilir ve bu bilgiler, kullanıcının belirli bir kaynağa erişim yetkisinin olup olmadığını belirlemek için kullanılabilir. Mikro hizmet mimarilerinde de JWT’ler, farklı hizmetler arasında güvenli kimlik doğrulaması sağlamak için kullanılabilir. JWT’lerin kullanımı, güvenli ve ölçeklenebilir kimlik doğrulama ve yetkilendirme mekanizmaları oluşturmanın yaygın bir yoludur, çünkü açık standart olmaları ve yaygın olarak desteklenmeleri, uygulama geliştiricilerine esneklik ve güvenlik sağlar.

JwtAuthenticationManager.cs

Authenticate Metodu:

  • Kullanıcı adı ve parola alır ve JWT tabanlı kimlik doğrulaması yapar.
  • Kullanıcı adını temel alarak bir JWT oluşturur.
  • JWT’nin geçerlilik süresini belirler ve imzalar.
  • Oluşturulan JWT’yi AuthResponseDto içine yerleştirerek döndürür.

ValidateJwtToken Metodu:

  • Bir JWT tokenı alır ve geçerliliğini doğrular.
  • Tokenın null olup olmadığını kontrol eder.
  • Tokenın geçerli olup olmadığını belirlemek için algoritmayı kullanır.
  • Eğer token geçerliyse, token içindeki kullanıcı kimliğini (id) alır ve döndürür.
  • Token geçerli değilse, null döndürür.

Bu modülü servis katmanınızda oluşturabilirsiniz.

    public class JwtAuthenticationManager : IJwtAuthenticationManager
{
private readonly AppSettings _appSettings;

public JwtAuthenticationManager(IOptions<AppSettings> appSettings)
{
_appSettings = appSettings.Value;
}
public AuthResponseDto Authenticate(string userName, string password)
{
AuthResponseDto authResponse = new AuthResponseDto();

try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);

var tokenDescriptor = new SecurityTokenDescriptor
{
Expires = DateTime.UtcNow.AddHours(1),
Subject = new ClaimsIdentity(new Claim[]
{
new Claim(ClaimTypes.Name,userName)
}),
SigningCredentials = new SigningCredentials(
new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};


var token = tokenHandler.CreateToken(tokenDescriptor);
authResponse.Token = tokenHandler.WriteToken(token);

return authResponse;
}
catch (Exception)
{
return authResponse;
}

}
public int? ValidateJwtToken(string token)
{
if (token == null)
return null;

var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_appSettings.Secret);
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = false,
ValidateAudience = false,
// set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later)
ClockSkew = TimeSpan.Zero
}, out SecurityToken validatedToken);

var jwtToken = (JwtSecurityToken)validatedToken;
var userId = int.Parse(jwtToken.Claims.First(x => x.Type == "id").Value);

// return user id from JWT token if validation successful
return userId;
}
catch
{
// return null if validation fails
return null;
}
}

}

IJwtAuthenticationManager.cs

Bu arayüz, JWT tabanlı kimlik doğrulama ve yetkilendirme işlemlerini gerçekleştiren sınıflar için bir sözleşme sağlar. Bu sayede, bu işlevselliği uygulayan farklı sınıfların, aynı arayüzü uygulayarak yerine getirebileceği ve değiştirilebilirliği artırabileceği bir yapı oluşturulmuş olur.

    public interface IJwtAuthenticationManager
{
AuthResponseDto Authenticate(string userName, string password);
int? ValidateJwtToken(string token);
}

JwtMiddleware.cs

JwtMiddleware sınıfı, ASP.NET Core uygulamalarında JWT (JSON Web Token) tabanlı kimlik doğrulama ve yetkilendirme işlemlerini yönetmek için bir ara yazılımdır. Invoke metodu, her HTTP isteği geldiğinde çağrılır ve isteği işler.

  • Invoke metodu, HTTP isteği sırasında çağrılır ve isteği işler.
  • HttpContext nesnesi, HTTP isteği ve yanıtıyla ilgili bilgileri içerir.
  • IUserService ve IJwtAuthenticationManager arayüzleri, kullanıcı hizmetlerine ve JWT kimlik doğrulama yöneticisine erişimi sağlar.
  • Authorization başlığındaki JWT token'ını alır ve doğrular.
  • JWT tokenı geçerliyse, token içindeki kullanıcı kimliğini çıkarır ve kullanıcı hizmeti aracılığıyla kullanıcı bilgilerini alır.
  • Kullanıcı bilgilerini, istek üzerindeki HttpContext nesnesinin Items özelliğine ekler, böylece sonraki işlemlerde kullanılabilir.
  • Son olarak, isteği sonraki ara yazılıma veya isteği işleyecek nihai işlemciye iletilir.

Bu middleware, her istek için JWT token’ının doğrulanmasını ve isteğin kullanıcı kimliğiyle ilişkilendirilmesini sağlar. Bu sayede, herhangi bir HTTP isteği sırasında kimlik doğrulama ve yetkilendirme işlemleri otomatik olarak gerçekleştirilir ve isteği yapan kullanıcının kimliği istek işleme sürecinde kullanılabilir hale gelir.

public class JwtMiddleware
{
private readonly RequestDelegate _next;

public JwtMiddleware(RequestDelegate next)
{
_next = next;
}

public async Task Invoke(HttpContext context, IUserService userService, IJwtAuthenticationManager iJwtAuthenticationManager)
{
var authorizationHeader = context.Request.Headers["Authorization"].FirstOrDefault();
string token = null;

if (!string.IsNullOrEmpty(authorizationHeader))
{
var parts = authorizationHeader.Split(" ");
if (parts.Length > 1)
{
token = parts[parts.Length - 1];
}
}

var userId = iJwtAuthenticationManager.ValidateJwtToken(token);
if (userId != null)
{
// attach user to context on successful jwt validation
context.Items["User"] = userService.GetById(userId.Value).Result;
}

await _next(context);
}
}

Program.cs’de tanımlama işlemleri

ASP.NET Core uygulamasında JWT tabanlı kimlik doğrulama ve yetkilendirme işlemlerini yapılandırılması ve etkinleştirilebilmesi için Program.cs’de tanımlamalar yapmak gerekmektedir.

builder.Services.AddScoped<IJwtAuthenticationManager, JwtAuthenticationManager>();:

  • Servislerin yapılandırıldığı bir builder nesnesinin Services özelliği üzerinden bir JWT kimlik doğrulama yöneticisi servisi ekler.
  • AddScoped metodu, her istek için bir örnek oluşturur ve bu örneği tüm aynı kapsamda olan isteklerde kullanır.
  • IJwtAuthenticationManager arayüzü, JwtAuthenticationManager sınıfının servis oluşturma isteği yapıldığında kullanılacak arayüzdür.

app.UseMiddleware<JwtMiddleware>();:

  • Uygulamanın middleware’lerini yapılandırır.
  • JwtMiddleware adlı bir middleware'yi uygulamaya ekler.
  • Bu middleware, HTTP isteklerini işler ve JWT tabanlı kimlik doğrulama ve yetkilendirme işlemlerini gerçekleştirir. Özellikle, her istek üzerinde JWT token’ının doğrulanmasını ve gerekli işlemlerin yapılmasını sağlar.
builder.Services.AddScoped<IJwtAuthenticationManager, JwtAuthenticationManager>();
app.UseMiddleware<JwtMiddleware>();

--

--

Kardel Rüveyda ÇETİN

Expert Software Engineer @DogusTeknoloji ~ All my life I've been over the top. I don't know what I'm doing. All I know is I don't wanna stop. 🤘