Mvc Rooting & Custom Constraint & Custom Action Filters
Bugünkü makalemizde amaç bir Mvc projesine request geldiği zaman izlenilecek işlem adımları ve gelen bu request’deki parametrelere göre daha en baştan yapılabilecek filitreleme ve kısıtlamalardır.Daha controller veya action’a gelmeden yapılan bu filitrelemeler, uygulamamıza performance ve güvenlik anlamında büyük katkı sağlamaktadır.Şimdi isterseniz routing kavramı ile konuya giriş yapalım.
Routing:
Routing, kullanıcının veya arama motorlarının sitenin URL’inde anlamlı ifadeler görebilmesini ve bu anlamlı ifadelerin, uygulama tarafında hangi controller ve action’a karşılık gelidiğini gösteren bir road mapdir.
Anlamlı ifadede ki amaç, google indexlemelerinde yada sosyal ağlardaki paylaşımda sadelik ve anlaşılabilirliği korumaktır.
Visual Studio’daki yeri, App_Start/RouteConfig.cs olan dosyanın içeriği aşşağıdaki gibidir:
Yukarda klasik bir MVC request’i görmekteyiz. Control, MvcHandler’a eriştiği zaman, MvcHandler RouteData’dan controller’ın parametrelerini ayıklar.Ve gerekli parametreleri controller’a gönderir. Tüm controllelar IController interface’inden türetilmiştir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Filter", // Route name url:"Home/ActionSelector/{id}", // URL with parameters defaults: new { controller = "Home", action = "ActionSelector", id = UrlParameter.Optional } ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } |
Yukarıda görüldüğü gibi Url’den request gelince RouteHandler devreye girer.Routing her zaman RouteConfig’de, istemciden gelen url’e uyan route’u en üstten aşşağıya doğru tarayarak bakar.En sona en genel route yani default route konur.Aranan koşullara uygunluk, url segmesinden bakıldıktan sonra, eğer geçerli koşullar sağlanmış ise ilgli controller’ın ilgili action’a gidilir.Test amaçlı yukarıdaki RoutConfig örneğinde Url kısmına http://localhost:4443/Home/ActionSelector yazarsak Filter Route’una gidecektir. http://localhost:4443/Home/ yazarsak Default route’una gidecektir.
ImageSource: https://www.red-gate.com/simple-talk/wp-content/uploads/imported/1407-img92.jpg
Şimdi RoutConfig dosyamızın başına yani en tepeye alltki yeni rout’u ekleyelim:
1 2 3 4 5 6 |
routes.MapRoute( name: "Filter2", // Route name url: "Home/ActionSelector/{id}", // URL with parameters defaults: new { controller = "Home", action = "ActionSelectorFiltered", id = UrlParameter.Optional }, constraints: new { id = @"[1-6]" } ); |
Yukarıda route.config’e yeni bir constraint ekledik.Bu constraintde regex ile gelen id parametresi 1 ile 6 arasında olması gerektiği belirtilmiştir. Eğer Url’e: http://localhost:4443/Home/ActionSelector/6 yazar isek id değeri olan 6, kural’a uyduğu için ActionSelectorFiltered sayfasına gider. Eğer http://localhost:4443/Home/ActionSelector/8 yazar isek id değeri 8, 1 ile 6 arasında olamığı ve kurala uymadığı için bir altındaki route olan ActionSelector’e gider.
Projeye Install-Package RouteDebugger yüklenirse yazılan url’e göre Route Data’nın hangi rule’uyduğu adım adım görülebilir.
Yukarıda görüldüğü gibi http://localhost:4443/Home/ActionSelector/8 şeklinde bir request id=[1-6]’ya uymamaktadır.Bu nedenle bir aşşağısındaki route olan ActionSelector’e yönlenmiştir.
Custom Route Constraint:
Diyelimki işi daha ileriye götürdük.Siteye girilen browser’a göre filter yapıcaz.Bunu yukarda da bahsettiğim gibi daha en başta yapıcaz.Yani Controller ve action’a gelmeden bir filitrelemede bulunacaz.Bu bize hem zaman hem de performans kazandıracak.Bunun için öncelikle IRouteConstraint‘den türemiş bir sınıfa ihtiyacımız var.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class UserAgentSelector : IRouteConstraint { private string _UserAgentSelector; public UserAgentSelector(string browser) { _UserAgentSelector = browser; } public bool Match(HttpContextBase httpContext, Route route, string parameterName, RouteValueDictionary values, RouteDirection routeDirection) { return httpContext.Request.UserAgent.Contains(_UserAgentSelector); } } |
Daha sonra Firefox’da siteye giriş yapalım.Hangi UserAgent’da bulunduğumuzu http://www.whatsmyuseragent.com/‘ dan bakabiliriz.İçinde Firefox kelimesi arıyacaz.
Şimdi RoutConfig dosyamızın başına yani en tepeye alltki yeni rout’u ekleyelim:
1 2 3 4 5 6 |
routes.MapRoute( name: "Filter3", // Route name url: "Home/ActionSelector/{id}", // URL with parameters defaults: new { controller = "Home", action = "ActionSelectorFilteredFirefox", id = UrlParameter.Optional }, constraints: new { userAgent = new UserAgentSelector("Firefox"), id = @"[1-6]" } ); |
Artık yandaki url’i http://localhost:4443/Home/ActionSelector/6 FireFox’dan açarsak. ActionSelectorFilteredFirefox route’una gittiğini Chrome’dan açarsak ActionSelectorFiltered’a gittiğini görücez.
Istersek bir de Action üstüne yada controller üstüne tanımli attribute yani filter kullanabiliriz.
Örnek amaçlı bir sayfa yapalım.Sadece Explorer’da açılsın.Örnek kodumuz aşşağıdaki gibidir.
1 2 3 4 5 6 7 |
public class Explorer : ActionMethodSelectorAttribute { public override bool IsValidForRequest(ControllerContext controllerContext, System.Reflection.MethodInfo methodInfo) { return controllerContext.HttpContext.Request.UserAgent.Contains("Trident"); } } |
RoutConfig.cs aşşağıdaki kodu ekleyelim:
1 2 3 4 5 |
routes.MapRoute( name: "Filter4", // Route name url: "Home/IndexExplorer/{id}", // URL with parameters defaults: new { controller = "Home", action = "IndexExplorer", id = UrlParameter.Optional } ); |
Şimdi HomeController’ımızdaki IndexExplorer Action’ına gidelim ve aşşağıdaki kodu ekleyelim:
1 2 3 4 5 |
[Explorer] public ActionResult IndexExplorer() { return View("IndexExplorer"); } |
İşte Sadece [Explorer ] ekliyerek bu sayfanın sadece explorerda açılması sağlayabildik.Çeşitli filter interfacelerini aşşağıda görebilirsiniz.
Bir de isterseniz ActionFilterAttribute’una bakalım.OnActionExecuting yani action sırasında ve OnActionExecuted yani action çalıştırıldıktan sonra diye iki method’u vardır.
Alttaki uygulamanın amacı action başlamadan önce ve bittiği zaman bir bildirim vermesidir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[AttributeUsageAttribute(AttributeTargets.All, AllowMultiple = true)] public class WriteSteps : ActionFilterAttribute { public WriteSteps(string IstenenMessage) { Message = IstenenMessage; } public string Message { get; set; } public override void OnActionExecuting(ActionExecutingContext filterContext) { filterContext.HttpContext.Response.Write(Message + " İşlemine Başlandı </br>"); } public override void OnActionExecuted(ActionExecutedContext filterContext) { filterContext.HttpContext.Response.Write(Message + " İşlemi Bitirildi. </br>"); } } |
HomeController’daki kod aşşağıdadır:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[WriteSteps("Anasayfa")] public ActionResult Index() { return View(); } [Explorer] [WriteSteps("Explorer", Order = 1)] [WriteSteps("2.Index", Order = 2)] public ActionResult IndexExplorer() { return View("IndexExplorer"); } public ActionResult ActionSelector() { return View(); } |
Yukarıdaki örnekte görüldüğü gibi WriteSteps attribute eklenmiş Index sayfasında başlangıç ve bitişinde aşşağıda görülen mesajları alırız.
Yukarıda görüldüğü gibi IndexExplorer’da WriteSteps attribute’u iki kere çağrılmıştır.Ve Order ile çalışma sırası belirlenmiştir.
Sonuç olarak IndexExplorer çağrılınca çıkan mesaj aşşağıdaki gibidir.
Sonuç:
Mvc’nin güçlü routing yapısına derinlemesine inceledik.
Görüldüğü üzere routing’de daha controller’a gelmeden yada action zamanında istendiği gibi çeşitli müdahalelerde yani filter constraintlerinde bulunula bilmektedir.
Routing sırasında regular expression ile kendi constraintlerimizi de yazabiliriz.
Bir sonraki makalede görüşmek üzere herkese hoşçakalın.
Source Code:http://www.borakasmer.com/projects/MvcFilter.rar
Source:
Merhabalar, ActionMethodSelectorAttribute’de bir bug mu var? aşağıdaki kodda [AcceptOnlyMain] sadece post actionda ekli olmasında rağmen diğer Get methodda da tetikleniyor.
[HttpGet]
[Route(“”)]
public IActionResult Index()
{
return View();
}
[HttpPost]
[Route(“”)]
[AcceptOnlyMain]
public IActionResult Index(int a)
{
return View();
}
Selamlar,
Acaba [AcceptOnlyMain]’ı bir de Controller’ın da üstüne koymuş olmıyasınız ?
Olmadığına eminim :) her iki url’de aynı olduğundan mı kaynaklanıyor acaba. buradaki yaklaşımı pek anlamış değilim.
Biraz araştırmamın sonucunda çözümü buldum. Problemin sebebi [AcceptOnlyMain] Attribute’nin
[HttpPost] Attribute’den önce çalışıyor olması. Burada order mekanizması devreye girdiği için [AcceptOnlyMain(Order = 1)] şeklinde yazdığımda sorun çözülüyor. fakat çok fazla attribute sahibim ve yanlış bir şey yapmamak adına alternatif yol arayacağım. Kolay gelsin..