Ana içeriğe atla

C# ve Asp.net MVC'de Çok katmanlı Soğan mimarisinde (Onion Architecture) Asp.Net Identity Kullanımı



Bir önceki makelemde (Buraya Tıklayınız)  Soğan mimarisini oluşturduk ve Ninject kullarak Katmanlar arasında bağımsızlığı sağladık. Bu makalede ise ASP.NET Identity 2.x kullanarak Güvenlik ve Üye yönetimini katmanlı mimaride nasıl sağlayabileceğimizi göstereceğim. bu makalede amaç ASP.NET Identity hakkında bilgi vermek veya nasıl kullanıldığını anlatmaktan ziyade, bir katmanlı mimaride katmanlar arası ilişkiyi bozmadan ASP.NET Identity'yi kullanıma sunulmasıdır.

Sorun Nedir?
Yeni bir web uygulaması oluşturulduğunda ve üye yönetimi olarak ASP.NET Identity kullanıldığında varsayılan olarak veri tabanı erişimini doğrudan PL katmanından ayarlamaktadır, ancak katmanlı mimaride PL katmanın veri tabanına veyahut DAL katmanına doğrudan erişmesi yasaklanmıştır. ASP.NET Identity'nin çalışabilmesi için IdentityDbContext'i geliştirmiş bir Context (Entity Framework) sınıfına ihtiyaç duyar.

Önemli Not: ASP.NET Identity varsayılan olarak EF kullanarak veri tabanına erişim sağlar, eğer EF kullanmak istenmiyorsa IUserStore interface'ini istediğiniz veri tabanı erişim yönetimi ile geliştirmeniz gerekmektedir. örneğin eğer BLL katmanı ve PL katmanı arasında Service Proxy katmanı varsa (katmanlar arası fiziksel ayrıştırma ihtiyaç olduğu zaman kullanılır), bu makaledeki yöntem ile identity kullanımını sağlayamazsınız, servis aracılığıyla BLL'e veri akışı sağlamak için PL katmanında IUserStore'u Servis aracılığıyla veriye ulaşmasını sağlayacak şekilde geliştirmeniz gerekmektedir.

Çok katmanlı Soğan mimarisine uygun olarak geliştirdiğimiz projeye github adresinden ulaşabilirsiniz.
kaynak kodlarına ulaşabilmek için buraya tıkalyınız.

ASP.NET Identity paketini Projeye Ekleme
ASP.NET Identity'yi mevcut bir projeye eklemenin en kolay yöntemi, yeni bir web uygulaması oluşturup ve o uygulama için Authentication seçeneklerinde Individual Authentication seçip daha sonra o projede Identity ile ilgili olan tüm paketleri mevcut projeye kurmak ve dosyaları mevcut projeye kopyalamaktır, bu makalede sadece ihtiyacımız olan NuGet paketlerini kuracağız, ve diğer dosyaları yeni oluşturulan bir web uygulamasından alacağız.

Projeye ASP.NET Indentity eklemek için Solution üzerinde sağ tıklayarak Manage NuGet Packages for Solution' a tıklayınız.


Açılan Ekranda, Browse tabında Identity terimini aratın, çıkan sonuçlarda Microsoft.AspNet.Identity.EntityFramework Paketini seçip kurunuz. bu paket diğer ihtiyaç olan paketleride kuracaktır.


ASP.NET Identity, OWIN tabanlı güvenlik sistemi olduğu için Owin paketinede ihtiyacımız vardır. OWIN'in amacı uygulamayı web sunucusundan bağımsız hale getirmektir. OWIN hakkında daha fazla bilgi almak için Buraya ve Buraya bakınız.
Owin için Microsoft.AspNet.Identity.Owin paketini kurunuz. bu paket Owin için gerekli diğer paketleri indirip kuracaktır.


OWIN ve Identity kullanıma açmak
OWIN'i kullanabilmemiz ve dolayısıyla Identity'yi kullanabilmek için Startup.cs sınıfını eklememiz gerekiyor, her OWIN uygulaması, uygulama Pipeline'nindak kullanılacak bileşenlerin (Component) tanıtmak amacıyla bir başlama sınıfına ihtiyacı vardır. bu sınıfın ismi startup.cs olmak zorunda değildir, ancak genelde bu isimde olması bu sınıfın amacını da tek bakışta belli ettiği için kullanılır. Startup.cs sınıfını PL katmanına ister örnek web uygulamasından ekleyiniz yada isterseniz kendiniz de yeni dosya ekleme ekranından yapabilirsiniz, bunun için PL katmanında yeni öğe ekleme ekranını açınız, ve arama çubuğuna startup yazınız, çıkan sonuçlarda OWIN Startup Class 'i seçiniz.


sınıf ilk eklendiği aşağıdaki gibidir.


Burada Configuration fonksiyonun içinde Identity'yi ayarlayabiliriz. bunun için öncelikle örnek web uygulamasından gerekli dosyaları projemize dahil edelim, daha sonra bu dosyaları düzenleyerek katmanlı mimariye uygun hale getirebiliriz.

dosyalar aşağıdaki gibidir:
1- App_Start klasörü içinde, Startup.auth.cs dosyası.
2- App_Start klasörü içinde, IdentityConfig.cs dosyası.
3- Controllers klasörü içinde, AccountController.cs ve ManageController.cs
4- Models klasörü içinde, AccountViewModels ve ManageViewModels
5- Views klasörü içinde, Account klasörü tamamı, Manage klasörü tamamı, ve Shared klasörü içinde, _LoginPartial.cshtml, Lockout.cshtml.


Identity ve Owin Config Sınıflarının Eklenmesi

Startup.Auth.cs ve IdentityConfig sınıflarını ekledikten sonra, Namespace'leri mevcut projeye uygun isimlendirme yapıyoruz. Namespace'ler düzenlendikten sonra, hem Startup.Auth.cs hemde IdentityConfig.cs sınıflarında bazı hatalar kalacaktır ki onları çözdüğümüzde aslında bu makalenin amacına ulaşmış olacağız.


birinci hata, Startup.Auth.cs sınıfında Owin context'inin daha sonra ihtiyaç durumunda kullanacağı ApplicationDbContext sınıfını bulamadığı için oluşan bir hatadır. Bu satırda aslında amaç daha sonra istenildiğinde Owin Context'inin Get metodunu kullanarak alabilinecek kaynakları ayarlamaktır, ki kullanıcı verisine ulaşabilmek için bir veritabanı bağlantı sınıfına ihtiyaç duyuluyor.


Bu noktada, Veri tabanı erişim sınıfını enjekte etmemiz gerekir, ancak PL katmanında doğrudan veri tabanı context sınıfına erişim mevcut değildir. bu hatayı çözmek için BLL katmanından context'i enjekte edebileceğimiz bir sınıf oluşturmamız gerekir, Factory Pattern kullanarak, DbContext'i create edip Owin'e enjekte etmemiz gerekir.

BLL katmanından aşağıdaki resimde olduğu gibi yeni bir sınıf oluştururuz. DbContextFactory isimli bu sınfın tek bir görevi var, ihtiyaç duyulduğunda bir DbContext sınıfı oluşturup geri dönmek.


bu sınıfın kodunu aşağıdaki gibi yazalım:

using MultiLayer.Infrasturcture;
 
namespace MultiLayer.Services
{
    public class DbContextFactory
    {
        public static MultilayerDbContext Create()
        {
            return new MultilayerDbContext();
        }
    }
}
 
 Şimdi Starup.auth.cs sınıfını aşağıdaki gibi düzeltelim:


DbContextFactory.Create eklediğimizde, bu kez visual studio, MultiLayerDbContext sınıfını tanımadığını ve MultiLayer.Infrastructure projesini referans olarak eklemeniz gerektiği hatası göstermektedir. Bu referansi eklersek, PL'den Doğrudan DAL katmanına referans eklemiş olacağız, ki doğru değildir. bu durumu çözmek için, öncelikle DbContext'imizin bir interface'i geliştirmesini sağlamalıyız ve bu interface'i de  tüm katmanlardan erişilebilinen Domain katmanında oluşturmalıyız. daha sonra, DbContextFactory sınıfımızda da değişiklik yaparak bu interface'in dönemesini sağlamalıyız. bu interface'in içeriğinde herhangi bir kod olmayacaktır. sadece referans eklemeden Run time'da DbContext'i enjekte edebilmemiz için gereklidir.

namespace MultiLayer.Domain
{
    public interface IDbContext : IDisposable
    {
    }
}

MultilayerDbContext ve DbContextFactory sınıfları düzenleme sonrası aşağıdaki gibir:





Yukarıdaki işlemler sonucunda, birinci hata giderilmiştir.
Startup.Auth.cs sınıfındaki ikinci hata ise, Identity ile alakalıdır, ve ApplicationUser sınıfı bulunamiyor hatasıdır. bu hatayı gidermek için öncelikle, ASP.NET Identity için bir kullanıcı sınıfı oluşturmamız lazım. Bu sınıf bir entity sınıfı olduğu için Domain katmanında oluşturmamız gerekiyor. ayrıca bu sınıf, IdentityUser sınıfından türemesi lazım. IdentityUser sınıfı ise, Microsoft.AspNet.Identity.EntityFramework paketinde yer aldığından bu paketi Domain katmanımıza NuGet'ten kurmamız gerekir. ApplicationUser sınıfı Domain katmanında Entities klasörü altında oluşturulur. bu sınıf ilk aşamada aşağıdaki gibidir.

using Microsoft.AspNet.Identity.EntityFramework;
 
namespace MultiLayer.Domain.Entities
{
    public class ApplicationUser : IdentityUser
    {
    }
}


Bu sınıfı ekledikten sonra ve gerekli using satırını Startup.Auth.cs sınıfına ekledikten sonra, yeni bir hata görünür, bu hatada ApplicationUser sınıfın GenerateUserIdentityAsync metodunun eksik olduğunu söyluyor. bu metodu ornek oluşturduğumuz web uygulamasında ApplicationUser sınıfına kopyalayıyoruz. ApplicationUser sınıfın son hali aşağıdaki gibidir.



Bu işlem sonucunda, Startup.Auth.cs sınıfı hatalardan arınmış olacaktır. sonraki aşamada, IdentityConfig.cs sınıfında, aşağıdaki hatayı gidermeliyiz.


bu sınıfın Create metodunda, ilk satırda, ApplicationUserManager sınıfı oluşturulmaya çalışılırken OWIN context'inden bir veri tabanı context'i istenmektedir. OWIN context'inin Get metuduna generic olarak gönderilen tip ise, ApplicationDbContext'dir ki PL katmanından erişilemez, bu noktada gene IDbContext interface'i yardımımıza koşacaktır. ApplicationDbContext yerine IDbContext yazıp ve context'ten dönen sonucu DbContext' e cast etmeliyiz, ancak burada DbContext'e cast edersek, Identity fonksiyonlarına ve sınıflarına erişemeyiz, bu sebepten dolayı, DbContext yerine IdentityDbContext<ApplicationUser> tipine cast ediyoruz. sonuç aşağıdaki gibidir.


Gerçi bu aşamada proje hatasız compile olacaktır, ancak biz henüz Identity Entity tiplerini Context'imize dahil etmemişiz ve bu durumda veri tabanımızda Identity ile ilgili olan sınıflar oluşmayacaktır. Bu işlem için MultiLayer.Infrastructure katmanımızdaki DbContext sınıfını, DbContext'ten miras almak yerine onun bir mirasçısı olan IdentityDbContext<ApplicationUser>'dan miras almalıyız. bu sınıf Microsoft.AspNet.Identity.EntityFramework paketinde olduğundan, bu paketi DAL katmanına da NuGet'ten kurmamız lazım. Aynı zamanda bu paketi BLL katmanına da kurmamız lazım, zira DbContextFactory sınıfı içinde artık bu paket ihtiyaç olmuştur. DbContext sınıfının son hali aşağıdaki gibidir.

Identity Entity'lerinin veri tabanında oluşması için Add-Migration komutuyla gerekli migration code'unu oluşturup daha sonra Update-Database komutuyla'da değişikliklerin veritabanına yansımasını sağlıyoruz.


Bu işlem sonucunda veritabanındaki tablolar aşağıdaki resimde gösterilmiştir.


Yukarıda kırmızı çerçeve içine alınan tablolar Identity'ye ait tablolardır.
Bu işlem'den sonra geriye kalan tek şey daha önce bahsettiğimiz ek dosyaları örnek olarak oluşturulan web uygulamasından projeye dahil edip ve namespace'leri düzenlemektir. geri kalan düzenlemeleri aşağıdaki video'dan görüntüleyebilirsiniz.


Yorumlar

  1. Merhaba,
    Bu projede dal katmanında sanırım userlarla ilgili işlem yapamıyoruz. Yine UI katmanında hallediyoruz gerekli işlemleri anladığım kadarıyla.

    YanıtlaSil
    Yanıtlar
    1. User'ile ilgili hangi işlemlerden bahsediyoruz? User ile ilgili tablolar normal entity olduğu için herhangi bir işlem gerçekleştirebilirsiniz, ancak authorization ve authentication için UI'da gerekli işlemler yapılır. fakat bazı durumlarda, business katmanına webapi eklenebilir ki bu durumda webapi UI katmanı gibi düşünmek lazım, authentication ve authorization işlemleri webapi'da yapılması lazım.

      Sil
    2. Demek istediğim şu, mesala kullanıcı silme güncelleme, kullanıcı yönetimi gibi admin işlemleri dal katmanında yapmak lazım, bunuda uı üzerinden busines katmanını kullanarak yönetmek lazım. UI üzerinden direk dal katmanına erişmek zaten bu işin prensibine aykırı. Bende örnek kod ararken sizin makalenize denk geldim. mvc nin kendi Identity sini dal katmanında işletip business katmanında yönetmek çok kolay değil diye düşünüyorum. Projeye ilerde mobil arayüz yazılacak olsa web api katmanınada Identity, owin gibi paketleri yükleyip oradan busines katmanına erişmek gerekiyor.

      Sil
    3. Doğrudur, zaten makalenin amacıda UI'dan Identity'nın ihtiyacı olan DbContext'i DAL'a dll referans eklemeden sağlamaktır. yani dediğiniz gibi kullanıcı işlemleri business katmanı kullanılarak DAL katmanında yapılmaktadır. webapi eklendiğinde aynı ayarlar webconfig katmanında da yapılmalıdır. fakat bu demo'da UserManagement'i business katmanına taşımadım, isterseniz, business katmanında bir UserService oluşturup, UserManager'in işlemlerini oradan çalıştırıtabilirsiniz.

      Sil
    4. Bugun onlara bakacam zaten, ayarlamaya çalışcağım. Bu arada bloğunuzu sık kullanılanlara ekledim. Makale yayınladıkça okur bilginizden faydalanırım :) İyi günler

      Sil
  2. Adsız dedi ki...
    merhaba yaptığınız projenin onion architecture ile alakası yok.
    1) Domain katmanda Microsoft Identity bağımlılığınız olamaz.
    2) Domain katmanda servis katmanını soyutlayan core servisler yok.
    3) Infrastructure katmanında DbContext'in ne işi var.
    4) Domainde datayı soyutlayan servisler yok.

    Bu Talebi Mimarisi olmuş bari adını değiştirin.

    16 Ocak 2018 23:30

    YanıtlaSil
    Yanıtlar
    1. Değerli Adsiz,

      Öncelikle zahmet ederek yorumda bulunduğunuz için teşekkür ederim. yorumunuz yapıcı değil yıkıcı eleştiri olmuş, aslında soru sormamışsınız cevap vermeme gerek yoktu, ancak diğer okurlarım da belki merak eder yazdıklarınızı diye cevap verme gereği duydum.
      sizde bir örnek yapıp bir blog yazısı yazarak daha iyi nasıl yapılacağını paylaşabilirsiniz, yıkıcı eleştiri yapmak yerine birbirimizi ve toplumumuzu geliştirmek için çaba harcarsak daha sağlıklı olacağını düşünüyorum.

      1) Microsoft Identity'inin Domain katmanında olma sebebi, ApplicationUser 'sınıfının Asp.Net Identity'nin IdentityUser class'ından kalıtım alabilmesi içindir. Asp.net Identity'deki Entity'ler de bizim için Domain Entity'sidir.

      2) Domain katmanı POCO Domain entity'lerimizi barındıran ve Mimarinin merkezinde yer alan ve diğer tüm Katmanların referans aldığı katmandır. burada soyutlamak anlamsız ve gerçek haytatta ek iş yükü çıkarmaktan başka bir şey değildir,varsa buna bir örneğiniz kesinlikle öğrenmek isterim, hatta bunun için mantıklı bir açıklamanız varsa, tüm okurlarımızın faydalanması çok yararlı olacaktır.

      3) Infrastructure katmanı Veri Erişimi (DAL) katmanıdır, Proje içinde akan tüm verilerin bu katmandan sağlanması lazım, dolayısıyla veritabanına bağlantı noktasının burada olması gerekiyor.

      4) Domian'de datayı soyutlayan servislerden ne kastettiğinizi anlayamadım.

      Clean Architecture Mimarisi ilk olarak Uncle Bob tarafından önerilmiş ve Onion Architecture (Clean Architecture (Onion View)) Jeffery Palermo tarafından önerilmiştir. bu mimari önerileri kağıt üzerinde ve tüm yazılım dillerine hitap edecek şekilde çizilir ancak gerçek hayatta uygulamaya döküldüğünde verimlilik ve yazılacak dilin sınırları ve koşulları göz önününde tutularak yapılır. Onion Architecture'a uygulamaya dökülmüş farklı şekilleri e ihtiyaca göre katmanların azalıp çoğalması söz konusu olabilir. dolayısıyla benim uygulamaya döktüm şekilde bu şekildedir. siz tabi ki proje büyüklüğüne göre katmanlar arası haberleşme için service (web service veya webapi) katmanları ekleyebilirsiniz.

      Sil
    2. Bu mimaride Core katmanınız sistem bağımsız olmalıdır. Burada ApplicationUser IdentityUser'dan kalıtılamaz. Microsoft.Identity.EntityFramework nesneleri sizin entity nesneniz olamaz.(Diğer katmanlarda kullanabilirsiniz.)
      Bu mimariye aykırı bir durum. Bu yanlış olduğundan diğer katmanlarda zincirleme yanlış yapılanıyor.
      Bu konuda iyi bir makale olan

      https://weblogs.asp.net/imranbaloch/a-sample-of-onion-architecture-with-asp-net-identity

      bakmanızı önerebilirim.
      İyi çalışmalar.


      Sil
    3. Paylaşımınız için teşekkür ederim, paylaştığınız link eminim diğer okurlarım içinde çok faydalı olacaktır. paylaştığınız kodda Identity.EntityFramework'un içini parçalayarak poco class'ları core'a ve diğer kısımlarını ise data katmanına dağıtmıştır, benim şahsi düşüncem buna gerek yoktur, bunu baz alırsak bir modular yapıyı düşündüğümüzde her modulun dll'ini parçalıyıp dağıtmalımıyım?

      Sil
    4. Mesela NLOG kullanılmış, NLOG için neden Entityler Core'a veritabanı işlemleri Data katmanına dağıtılmamıştır?

      Sil
  3. Merhaba tekrardan;
    Burada asıl sorun "Onion" veya "Clean" mimari ile asp.net identity sistemi tam ifade edilemiyor.
    Linkteki projede dahi ciddi sorunlar var.
    * Farklı bir entityde appuser'ı navigation olarak kullanamıyorsunuz.Bu sebeple include işlemi yerine join yapmak zorunda kalıyorsunuz bu durum ayrıca mapping işlemlerinde sizi zora sokuyor.
    * AppUserId'i Base Entity'ye yazıp kalıttırmak çok sınırlı bir çözüm oluyor.
    Bu sebepler başlıca büyük 2 sorun.
    Sizin örneğinizdeki gibi mimari kuralını esnetip çalışılırsa ApplicationUser : IdentityUser dan sorun çıkarmaz gibi duruyor ama örneğin ;
    ApplicationUser : IdentityUser

    gibi bir implementasyonu katmanlara seperate etmeniz kolay görünmüyor.

    Burada illaki asp.net identity kullanılmak zorunda değilsiniz kendi Authentication sisteminiz yazıp mimariye uygun hale getirebilirsiniz.(IPrincipal implement edilebilir.) Dolayısıyla IdentityDbContext 'den de kurtulursunuz.

    Son olarak diğer sınıflardaki bir nesneniz core katmandaki(domain) entitylere referans duyuyorsa bu entity'i core da tanımlamanız gerekir.Bunun dışında serbestsiniz.

    Bu mimaride temel amaç katmanlar arası iletişimin "interface"ler ile yapılmasıdır.Maalesef bu durum sürekli yeni bağımlılıklara yol açıyor.

    İyi çalışmalar.

    YanıtlaSil
    Yanıtlar
    1. Merhaba, bu mimaride illaki identity kullanmak zorunda değilsiniz demişsiniz, bu doğru illaki kullanmak zorunda değiliz, ancak bu makalenin amacı ve konusu Onion Architecture da Asp.net identity kullanmak istediğimizde, DbContext bağımlılığını presentation katmanından kaldırarak nasıl kullanabiliriz için bir örnektir teşkil etmektir, ayrıca kod github'da paylaşılmıştır, doğru olmadığını düşündüklerinizi yapıp bir pull request ile yardımda bulunabilirsiniz. böylece her açıdan doğru bir mimariyi beraber yardımlaşarak kururuz. yorumlarınızı ve kod için desteğinizi her zaman beklerim. teşekkürler.

      Sil

Yorum Gönder

Bu blogdaki popüler yayınlar

C# ve Asp.net MVC'de Çok katmanlı Soğan mimarisi (Onion Architecture)

Çok katmanlı mimari, güçlü ve kolay geliştirelebilen ve katmanlarının kolaylıkla değiştirilebilen büyük uygulamalarda çok önemli bir rol oynar. Eski ve en ünlü çok katmanlı mimari, 3 katmandan oluşmakta Data Access Layer - Veri Katmanı Business Process Layer - İş Modeli Katmanı Presentation Layer - Kullanıcı Arayüzü Katmanı  Bu mimaride, Kullanıcı Arayüzü Katmanı sadece ve sadece İş Modeli Katmanıyla iletişimdedir, ve Veri Katmanıyla direk iletişime geçmesine izin verilmiyor, böylece hem güvenlik sağlanıyor, hem de bir katman değiştirilmek istendiğinde diğer katmanlarda minimum değişiklikle bu işlem yapılabiliyor. bu mimari her ne kadar küçük ölçekli uygulamalarda başarılı olsa da, daha büyük ve karmaşık uygulamalarda yetersiz kalmaktadır. Geleneksel Katmanlı Mimari Onion Architecture veya Soğan mimarisi Jeffrey Palermo tarafından onerilmiştir. bu mimaride her katman soğan halkaları gibi düşünülmüş olup kolaylıkla değiştirilebilmesi veya düzenlenmesi amaçlanmıştır. bu mim

Güncel İl, İlçe ve Okullar Listesi (excel ve sql)

Bu Yazımda, en son ve güncel iller, ilçeler ve okullar listesini yayınlıyorum. bu yayında hem excel ve hemde sql sorgularını yayınlanmıştır. NOT1: il ve ilçeler listesi iç işleri bakanlığının sitesinden alınmıştır. eğer değişiklik olursa bu linkten kendiniz de alabilirsiniz, ancak veritabanına kendiniz yazmanız gerekecektir. İÇ İŞLERİ BAKANLIĞI - İL ve İLÇELER LİSTESİ NOT2: Okullar listesi Milli Eğitim Bakalığı sitesinden alınarak Excele aktarılmıştır, daha sonra Excelden Veritabanında eşleşen il ve ilçeri bulunarak doğru bir şekilde kaydedilmiştir. Toplam 3250 Adet okul. Milli Eğitim Bakanlığı - Okullar Ful Listesi Tablo düzeni şu şekildedir. İl, İlçe ve Okul için SQL İlişki diagramı Yukarıdaki Resimde görüldüğü üzere; Her İl'in (City Tablosu) 0 veya birden fazla İlçesi var, ve her İlçenin 0 veya daha Çok Okulu vardır. Gördüğünüz üzere Okul ve İl arasında bağlantı eklenmemiştir, okul olduğu il zaten ilçe tablosu vasitasiyla belirlenebiliniyor, böylece veri taba