Intercepting Filter Pattern Ile ASP.NET MVC Controller Action
Selamlar;
Intercepting Filter şablonu ile kullanıcıdan gelen istek (request) işleme alınmadan önce filitreler kullanılarak süzgeçten geçirilir.Örneğin bir filter ile user’ın işlem öncesi hangi ortamdan geldiği kontrol edilebilir. Filitre HttpRequest.Browser’dan user’ın mobile bir araçtan gelip gelmediğini kontrol edip, duruma göre ilgili action’a yönlendirebilir. Böylece mobile’den gelen user için daha farklı ekranlar gösterilir.
Intercepting Filter tasarım şablonunun merkezinde filtreler bulunur. Mvc’de ActionFilter olarak belirlenen classlar, peş peşe dizilerek herbiri için gelen request farklı işleme tabi tutulabilir.
Yukarıdaki diagramda görüldüğü gibi birden fazla filtre peş peşe eklenerek gelen request işleme tabi tutulmuştur. Sıradaki her filtre kendisine yüklenen görevini yerine getirdikten sonra kontrolü FilterManager yardımı ile kendinden sonraki filtreye bırakır. Eğer işlem esnasında filtrelerden birisi hatalı bir duruma düşerse mesela kullanıcı login olmamış ise işlem durdurulur. Bir sonraki filitreye geçilmez ve bu filter için tanımlanmış aksiyon gerçekleştirir. Mesela login sayfasına yönlendirilir.
Mvc’de Interceptor pattern’i incelemeden önce aşağıda görüldüğü gibi mvc’de kullanılan yapılara bir göz atalım.
Mvc’de birçok filter attribute vardır. Örnek vermek gerekirse AuthotizeAttribute, OutputCacheAttribute gibi. Biz de istersek ActionFilterAttribute sınıfından kendimize özel Custom Action Filiterlar yaratabiliriz. Bu konu ile ilgili makaleme buradan ulaşabilirsiniz. Aşağıda görüldüğü gibi tüm bu attributelar System.Web.Mvc.GlobalFilterCollection ile global seviyede kaydedilir.
1 2 3 4 |
void Application_Start() { GlobalFilters.Filters.Add(new HandleErrorAttribute()); } |
Action filterlar IActionFilter interface’inden türetilir. İstresek filitreleri action başlamadan veya bittikten sonra devreye sokabiliriz. Buna pre-action ve post-action logic denmektedir. OnActionExecuting ve OnActionExecuted methodları ile tanımlanırlar.
ASP.NET MVC’de action filterlar ile çalışmak filter provider sayesinde çok rahat ve esnektir. 2 çeşit filter provider vardır. Bunlar FilterAttributeFilterProvider ve ControllerInstanceFilterProvider. Aynı şekilde burda da custom filter provider yazabiliriz. Filitreleme yapılırken action’a yada controller’daki tüm actionlara bağlanabilirler. Aşağıda Custom ConditionalFilterProvider gözükmektedir.
1 2 3 4 5 6 7 8 |
public class ConditionalFilterProvider : IFilterProvider { public IEnumerable<Filter> GetFilters(ControllerContext controllerContext, ActionDescriptor actionDescriptor) { //Burası conditional filter'ın logiciğinin yazıldığı yerdir. } } |
Bu provider’ı aşağıdaki gibi register ederiz.
1 2 |
var provider = new ConditionalFilterProvider(); FilterProviders.Providers.Add(provider); |
Var olan bir provider’ı aşağıdaki gibi kaldırırız.
1 2 3 4 |
var oldProvider = FilterProviders.Providers.Single( f => f is FilterAttributeFilterProvider ); FilterProviders.Providers.Remove(oldProvider); |
Interception Mechanism Nedir?
Bildiğiniz gibi design ortamında gevşek bağlı yapılar hem ilerde genişletebilmek için hemde başka bir yapının siteme eklenmesinde büyük yarar sağlar. Aslında filterlar, bahsettiğimiz bu rahatlığı bize sağlarlar. Fakat daha büyük bir değişikliğe gidilmek istendiğinde yeni custom filterlar oluşturmak zorunda kalabiliriz.
Yeni bir custom filter oluşturulurken ve var olan yapıya eklenirken aşağıdaki konulara dikkat edilmelidir.
- Eğer bir controller içindeki action bir başka controllerdaki action’a call yapıyor ise buna filter konmamalıdır. Filter tek bir controller veya action için tasarlanmalıdır.
- Yaratılan custom filter provider’lar belli bir mantık ile tanımlanan bir class veya yapıdan sisteme dahil edilmelidirler. Mesela dependency injection buna güzel bir örnektir.
- Herzaman action işeltilmeden veya işletildikten sonra yani
OnActionExecuted veya
OnActionExecuting
durumlarında ilgili filterlar işletilmelidir.
Interception 3 temel işleyişe göre çalışmaktadır. Belirlenen koşulları sağlama, Call handlers, and Interceptors.
Örnek olarak bir MVC application’da tüm controller’ları intercept eden bir uygulama yazalım:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class LogAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { Log("OnActionExecuting", filterContext.RouteData); } public override void OnActionExecuted(ActionExecutedContext filterContext) { Log("OnActionExecuted", filterContext.RouteData); } public override void OnResultExecuting(ResultExecutingContext filterContext) { Log("OnResultExecuting", filterContext.RouteData); } public override void OnResultExecuted(ResultExecutedContext filterContext) { Log("OnResultExecuted", filterContext.RouteData); } private void Log(string methodName, RouteData routeData) { var controllerName = routeData.Values["controller"]; var actionName = routeData.Values["action"]; var message = String.Format("{0} controller:{1} action:{2}", methodName, controllerName, actionName); Debug.WriteLine(message, "Action Filter Log"); } } |
Yukarıda görüldüğü gibi controller’ın ilgili action’ında işleme başlamadan önce ve bitiminde; daha sonra sonuç değerlerini almadan önce ve sonrasındaki adımlar controller’ın action’ın ve methodun isimleri yazdırılarak loglanmıştır.
[LogAttribute] attribute şeklinde controllerın veya action’ın başına konarak kullanılır. Controller’ın başına [LogAttribute] filter’ı konur ise controller altındaki tüm actionlara filter atanmış olur.
Ayrıca bu filter’ı global filterlara aşağıdaki gibi eklemek gerekmeketedir.
1 2 3 4 5 |
public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); filters.Add(new LogActionFilter()); } |
Şimdi de mvc’de login olunup olunmadığına bakılıp, duruma göre login ekranına yönlendirilen bir action filter yazalım. Önce aşağıda görüldüğü gibi action’a başlamadan önce pre-action durumunda ilgili koşula bakarak yani Session[“Login”]’in null olup olmadığna bakılır. Koşul sağlanıyorsa filterContext.Result değeri “Login” action olarak akış intercept edilip gidilmek istenen yer engellenir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace InterceptingFilter.Models { [AttributeUsage(AttributeTargets.All)] public class UserLoginAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { if(HttpContext.Current.Session["Login"]==null) { filterContext.Result = new RedirectToRouteResult("Login",new RouteValueDictionary("Login")); } } } } |
HomeController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using InterceptingFilter.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; namespace InterceptingFilter.Controllers { public class HomeController : Controller { // GET: Home [UserLoginAttribute] public ActionResult Index() { return View(); } public ActionResult Login() { return View(); } [HttpPost] public void Login(string name, string password) { Session["Login"] = name; } [HttpPost] public void Logout() { Session["Login"] = null; } } } |
Login.cshtml: Login için Username ve Password girildikten sonra Gönder button’una basılınca Login [Post] action’ına gidilir. Ve yukarıda görüldüğü gibi Session[“Login”] = name; değeri atanır ki ilgili koşul sağlansın ve filter’a takılıp tekrardan login ekranına düşülmesin.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
@{ ViewBag.Title = "Login"; } <script src="~/Scripts/jquery-1.10.2.min.js"></script> <script> $(document).ready(function () { $("#btnSubmit").click(function () { $.post("/Home/Login", { name: $("#txtUsername").val(), password: $("#txtPassword").val() }, function () { location.href = "/Home/Index"; }); }); }); </script> <h2>Login</h2> <table> <tr> <td> Kullanıcı Adı: </td> <td> <input type="text" id="txtUsername" /> </td> </tr> <tr> <td> Şifre: </td> <td> <input type="text" id="txtPassword" /> </td> </tr> <tr> <td></td> <td> <input type="button" value="Gönder" id="btnSubmit" /> </td> </tr> </table> |
Index.cshtml: Aşağıda görüldüğü gibi filter koşulu sağlanmadığı için akış bölünmemiş ve Index.cshtml’e gelinmiştir. Çıkış button’una basılınca HomeControl.cs’deki Logout action’ına gidilip Session[“Login”] = null; değeri atanarak tekrardan girilmeye çalışılınca filiter’a takılıp login ekranına düşülmektedir.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@{ ViewBag.Title = "Index"; } <script src="~/Scripts/jquery-1.10.2.min.js"></script> <script> $(document).ready(function () { $("#btnLogout").click(function () { $.post("/Home/Logout", { }, function () { location.href = "/Home/Index"; }); }); }); </script> <h2>Index</h2> <input type="button" value="Çıkış" id="btnLogout"/> |
RouteConfig.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; namespace InterceptingFilter { public class RouteConfig { public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( name: "Logout", url: "Logout", defaults: new { controller = "Home", action = "Logout" } ); routes.MapRoute( name: "Login", url: "Login" , defaults: new { controller = "Home", action = "Login"} ); routes.MapRoute( name: "Default", url: "{controller}/{action}/{id}", defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } ); } } } |
İlgili video aşağıdadır.
Istenir ise filterlar aşağıda görüldüğü gibi art arda eklenerek kullanılarbilirler.
1 2 3 4 5 6 |
[LogAttribute] [UserLoginAttribute] public ActionResult Index() { return View(); } |
Sonuç olarak günlük hayatımızda özellikle mvc’de farkına varmadan kullandığımız birçok design pattern vardır. İşte Intercepting Filter Pattern’da farkına varmadan kullandığımız design pattern’lardan biridir.
Geldik bir makalenin daha sonuna.
Yeni bir makalede görüşmek üzere hoşçakalın.
Source: https://www.codeproject.com/Articles/469251/ASP-NET-MVC-controller-action-with-Interceptor-pat
Selamlar hocam.
Elinize sağlık harika bir yazı olmuş.
Üniversitelere seminere geliyormusunuz? Eğer geliyorsanız ITU’ye de gelirmisiniz hocam. Öğrenecek daha çok şeyimiz var. Mesela bu konu şu an karşılaştığımız sorunları okadar pratik bir hale getiriyor ki. Kimbilir daha basit ne yollar var hocam…
Daha nice kodlara hocam..
Çok güzel bir yazı olmuş hocam. İlk iş bizim projedeki tüm login ve authentication yapısını buna çeviricem.
Çok teşekkürler.
Selamlar hocam;
User’ın mobile’den mi yoksa web’den mi geldiğini bu filter yöntemi ile belirlemeye karar verdim.
Gerçekten çok işime yaradı. Elinize sağlık hocam.
Çok teşekkürler..