AspNetCore Controller Yapısı
Güncelleme tarihi: 5 May 2020
Controller Nedir?
Herkese merhaba. Bu yazım da size AspNetCore.Mvc frameworkü içerisindeki Controllerın farklı yönleri hakkında biraz bilgi vermek istiyorum. Bu bilgiler Controller: "View ile Model arasında bağlantıyı sağlayan class'tır" şeklinde ki bir bilgiden çok, Controller nedir, bize neler vadediyor, neler yapabiliriz şeklindeki bilgiler.
Çoğu kişinin neden Controller yapısını View ile Model arasındaki katmandır şeklinde yorumladığını bilmiyorum. Belkide bana öyle geliyordur, öyle yorumla mıyorlardır. Controllerın Mvc içerisinde böyle bir misyonu var. Fakat bu Controller'ın tek olayı değil. Peki Controller ile neler yapabiliriz. Öncelikle biraz Controllerın anlam olarak ifade ettiklerinden bahsedelim.
Controller basit anlamda bir iletişim kanalı olarak kullanılmakta. Bir nevi Web ile uygulama arasındaki köprü. Uygulamanızda View ile Model olmasa da Controller kullanarak Web ortamına bir iletişim kanalı oluşturabiliriz. Bu iletişiminde kendine has bir adı var o da REST. Buradaki konu Controller olduğu için REST içeriğine girmeden basitçe değinecek olursak: Birbiri ile iletişime geçecek iki uygulamanın kullanacağı dilin kurallarını belirten yönergeler diyebiliriz. Buradaki iletişimi betimleyecek olursak: Bir uygulama diyorki "Ben REST kurallarına göre seninle konuşuyorum" diğeri de diyor ki "Seni anlayabiliyorum konuşmaya devam edebiliriz".
Burada bahsedilen iletişim aslına bakarsanız daha çok tek yönlü bir iletişim. Çünkü REST yapısında işler Request ve Response şeklinde adlandırılan istekler ve cevaplar şeklinde yürüyor. Siz Controller a bir istek yolluyorsunuz eğer karşılığı varsa dönüyor. Siz istek yaptığımızda Controller tam olarak şunu diyor: "Sen bana bir istek yaptın bende buna karşılık bir cevap varsa sana yollarım senle işim biter. Kim olduğun umurum da değil. Sana daha sonra ulaşmak gibi bir derdim yok".
Kısaca Controller lar yapılan bu isteklerin şeklini ve karşılıklarını tanımladığımız denetleyici yapılar diyebiliriz. Peki bu yapılar nasıl tanımlanıyor. Bu kısımda Controller(Denetleyici) classının nasıl tanımlandığından ve anatomisinden bahsedelim. Bunun için bir AspNetCore Web Application diyerek bir WebAPI projesi oluşturuyorum. Burada kullandığım Visual Studio versiyonu 2019 Comminty.

Şu ana kadar Controller ların genel yapısından bahsettik fakat AspNetCore.Mvc içerisinde Controller ların ne olduğundan pek bahsetmedik. Umarım buraya kadar anlattıklarımın sadece AspNetCore.Mvc içerisindeki Controller olduğunu düşünerek okumamışsınızdır. Aslında şu ana kadar bahsettiğim Denetleyici(Controller) mekanizmasının çalışma şekliydi. Burada anlatmak istediğim olay bütün dillerdeki Controller yapılarının benzer şekilde çalıştığıdır.
Denetleyiciler(Controller) genellikle AspNetCore.Mvc frameworkü içerisindeki ControllerBase abstract classından miras alınarak oluşturulmaktadır. Bu kullandğınız frameworkün versiyonuna göre değişebilir. Bazı versiyonlarda ApiController şeklinde de geçer. Bu abstract class içerisindeki bir çok yardımcı method bize bir denetçi oluştururken oldukça fayda sağlayan özellikler vermektedir. Fakat bu her zaman bu şekilde olmak zorunda değildir. AspNetCore.Mvc frameworkü kişi ihtiyaçlarına göre esneyebilecek şekilde oluşturulduğu için kendi Denetleyici(Controller) sınıfınızı da yazabilirsiniz.
AspNetMvc Controller
Şimdi gelin örneklerle AspNetCore.Mvc içerisindeki Controller sınıfının yapısını incelemeye başlayalım. Bir Controller oluşturmak için ControllerBase abstract sınıfından miras almamız yeterlidir. Bunun temel nedenilerinden biri yazdığımız classın bir Controller olduğunu ApplicationServise bildirmiş olmak. Böylece uygulama ApplicationService başladığında Assembly den calassları yüklerken bu classtan türemiş olan classları birer Controller olduğunu anlayacaktır. Fakat bu classtan türemesi tek başına yeterli değildir. Gelen isteğin bu Controllere yönlendirilmesi için Route ayarlamasının da yapılması gerekmektedir. Bu ikisi ortak çalışarak bir Controllerı meydana getirmektedir.


Adında Controller Geçmesi
Burada classın Controller olarak işaretlenmesinden bahsettik. Gelin şimdi biraz daha farklı şeyler deneyelim ve ControllerBase abstract classını silelim. Her örneğin farklı olduğunu göstermek için Foo classlarını çoğaltıyorum.
Burada tanımladığımız Foo2Controller da çalışacaktır. Çünkü isminde "Controller" kelimesi yer almaktadır. ApplicationService ayağa kalkarken bir classın Controller olduğunu anlamak için kullandığı yöntemlerden bir taneside classın son kelimesini kontrol etmektir. Eğer son kelime Controller ise bu bir Controller dir der.


Controller Attribute
Bir diğer yöntemde Attribute kullanmaktır. Aslında ControllerBase classıda temelde bu yöntemi kullanmaktadır. Ne demek mi istiyorum. Eğer bir classa [Controller] Attributesi verirseniz bu class artık bir Controllerdır demektir.
Gördüğünüz gibi Mvc frameworkü oldukça esnek bir yapıdadır ve isterseniz kendi Controller classlarınızı sadece [Controller] Attribute vererek istediğiniz özelliklerde oluşturarak kullanabilirsiniz.


AspNetCore referans dosyalarını indirip Mvc içerisindeki ControllerBase Classını bulursanız, aslında [Controller] Attribute sine sahip olduğunu görebilirsiniz.

NonController Attribute
Bu yöntemlerin dışında bir de [NonController] durumu vardır. Bu da bir Controllerı pasif duruma geçirmek için kullanılabilecek bir özelliktir. Bu attribute sahip Controller artık işlevsiz olacaktır.


Custom Controller
Şimdi de kendi Controller classımınızı oluşturup bu classtan miras almaya çalışalım. Bunu için ControllerBase classını mirasta alabilirdik. Fakat biz tamamen Custom olsun istiyoruz.
Bir MyCustomController adında bir class oluşturup [Controller] Attributesi veriyorum ve bir de rota belirliyorum. Artık bu class bir Controller olarak yaşamına devam edecek. Şimdide bu yeni oluşturduğum Controllerdan miras alarak yeni bir Foo oluşturuyorum.


Kodum çalışıyor fakat çokta efektif bir yaklaşım değil. Çünkü ControllerBase abstract sınıfı bana ihtiyacım olan bir çok methodu hazır veriyor. Benim oluşturduğum Controller sınıfında ihtiyacım olan class ve methodları kendim yazmam gerekecek.
ControllerBase abstract sınıfına girdiğimde birçok ActionResult un hazır olduğunu ve istediğimde ihtiyacıma göre modifiye edilebilir olarak bulunduğunu görüyorum. O yüzden gerçekten özel bir ihtiyaç yoksa bu sınıftan miras alarak yeni bir base sınıf oluşturmak daha mantıklı görünüyor.

Dynamic Controller
AspNetCore.Mvc frameworkünün esneklik tanıdığı bir diğer olayda dinamik Controllerlar oluşturabilmenizdir. Yani bazı durumlarda otomatik Controller oluşturabilirsiniz.
Diyelimki çok basit CRUD işlemleri olan yada sadece localhostta çalışacak bir uygulama geliştiriyorsunuz. Fakat katmanlarla uğraşmak ta istemiyorsunuz. Ben bir veritabanı objesi yapayım bütün olayı bu obje yönetsin istiyorsun. İşte tam da aradığın şey dinamik Controller.

Bu işi yapmadan önce bir kaç kavramdan daha haberdar olmak gerekiyor. Bunlardan bir tanesi ApplicationPart. Bu kavramı yüzeysel açıklamak gerekirse Uygulama kaynakları üzerinde bir soyutlama sağlıyor. Kısaca uygulama ayağa kalkarken bazı parçaların Controller, View, TagHelper veya RazorPage gibi bileşenlerin keşfedilmesini sağlar.
Peki bunu nasıl yapar? ApplicationFeatureProvider adı verilen özellik sağlayıcı sınıfları Assembly(Kaynak) taki classların hangisinin Controller hangisinin View olduğunu keşfeden classlardır. Bu classları manupüle ederek bizim classımızı okuduğunda otomatik bir Controller classı üretmesini sağlayabiliriz.
Bütün bunlardan önce yapmam gereken bir şey var. O da oluşturduğum classı işaretlemek. Çünkü hangi classın otomatik Controller classının oluşturacağını bilmem için ayırıcı bir şeye ihtiyacım var. Bunun içinde bir AutoControllerAttribute adında Attribute tanımlıyorum. Bu Attributeye içerisinde kendi rotamı tanımlayabilmek için bir tanede property veriyorum.

Artık bir ApplicationFeatureProvider tanımlayarak kaynaklar yüklenirken benim Attribute me sahip bir class gördüğünde otomatik bir Controller üretmesini sağlayabilirim.
Bunu yapmadan önce bir tane base classa ihtiyacım var. Bunun için Controllerbase classını miras alan bir class üretebilir ve içerisinde Type tutan bir property tanımlayarak mevcut model classımla ilişkilendirebilirdim. Fakat daha efektif bir kullanım için Generic bir base class daha iyi olacaktır. Böylece mevcut model classımın tipini T tipinden base classıma göndererek içeride istediğim gibi kullanabileceğim.

Şimdi ApplicationFeatureProvider classını oluşturabilirim. Burada PopulateFeature methodu ile kaynaklar yüklenirken benim oluşturduğum Attribute ye sahip bir class geldiğinde Controller listesine bu class tipinden dinamik olarak generic base classımı oluşturarak ekliyorum.

Artık Controller classına sahibim fakat yinede bu Controller classına ulaşamıyorum. Çünkü oluşturduğum dinamik Controllerların rotası hatalı durumda. Bu classı dinamic olarak ve generic yaptığımız için Controller ismi base classımız olan generic tip olarak görünecektir. Yine RouteValues kısmına baktığımızda template olarak kullanılan [controller] değerininde generic classımız olduğu görünmekte.
Bu yüzden oluşturduğumuz Model class adı ile veya Model classa verdiğimiz Route ile biz bu Controllera ulaşamayacağız gibi görünüyor.

Bu durumu aşmak için Controller oluştuktan sonra ilgili Controllerın rotasını yeniden vermemiz gerekiyor ve Controllerın ismini bizim Model classımızın ismi olarak değiştirmeliyiz.
Bunu AspNetCore.Mvc frameworkünde yapabilmek için Convension adı verilen bir yapıyı kullanmamız gerekiyor. Kaynaklar yüklenirken keşfedilen her Controller, TagHelper, View gibi bileşenin metadataları servise eklenmektedir Böylece bu metadata ile servis gelen istekle ne yapılacağını bilir.
Convensionlar ile bu metadataları tekrar yazabiliriz. Yani kaynaklar yüklenirken oluşan Controller rota bilgisine kaynaklar daha yüklenirken müdahale edebiliriz.
Aşağıdaki resimde görüldüğü gibi IControllerModelConvention interface inden bir class türetiyorum. Bu interface de Apply adında bir method bulunmata ve ControllerModel parametresini bana vermekte. ControllerModel parametresi benim oluşturduğum Controller classının Meta veri yani özelliklerini tutan class. Artık burada Controller classımda vermiş olduğum custom rotayı verebilirim. Yada eğer bir rota vermediysem ControllerName meta verisini Generic isimden Model ismime çevirebilirim.

Bunların işe yaraması için Startup sayfamda servise eklemem gerekiyor.

Şimdi de test zamanı. UserModel adında bir class oluşturup AutoController Attributemi veriyorum. Attributeyi tanımlarken bir rota belirtiyorum. Yaptığım geliştirmelerle bu model için artık bir controller classı üretmeme gerek kalmadı çünkü AutoController Attributesine sahip olduğu için bu Modelin Controllerı dinamik olarak oluştu.


Şimdide farklı bir örnek ile gönderdiğimiz Json veriyi T tipine deserialize ederek UserModel class elde edip tekrar geri döndürelim.


Kaynaklar:
https://github.com/dotnet/AspNetCore.Docs/blob/master/aspnetcore/mvc/controllers/routing.md
https://docs.microsoft.com/tr-tr/aspnet/core/mvc/controllers/application-model?view=aspnetcore-3.1
https://github.com/dotnet/aspnetcore
https://www.strathweb.com/2014/06/poco-controllers-asp-net-vnext/
https://www.strathweb.com/2018/04/generic-and-dynamically-generated-controllers-in-asp-net-core-mvc/