MVC Filters Nedir ? ActionFilter Kullanımı
Mvc ActionFilter ActionFlterCustomAttribute işlemleri
MVC ActionFilter ActionFilterAttribute CustomActionResultAttribute Model-View-Controller katmanı üzerinde belirli olayların rutin halde işlenmesi, kod fazlalıklarının giderilmesi, okunabilirlik, uygulanabilirlik başlıklarının bence tam anlamıyla hakkını veren ve uygulamanıza esneklik katacak değerlerin daha az kod daha çok iş mantığıyla işlendiği yapıdır. Filtreler çeşitli Attribute ve Interface’ler üzerinde belirli bölümlere ayrılmışlardır.
Genel hatlarıyla 4 ana başlık ile inceleyebiliriz , kullanımları aynı veya birbirine çok yakındır.
- Authorization Filter
- Action Filter
- Result Filter
- Exception Filter
Yukarıda bahsettiğim gibi MVC Filtreleri çeşitli Interface ve Attribute’lar üzerinden kullanıma sunulmuştur.Aşağıdaki tablo’da basit bir gösterimini bulabilirsiniz.
AuthorizationFilter | IAuthorizationFilter | AuthorizeAttribute |
ActionFilter | IActionFilter | ActionFilterAttribute |
ResultFilter | IResultFilter | ActionFilterAttribute |
ExceptionFilter | IExceptionFilter | HandleErrorAttribute |
Belirttiğim Filtrelerin hepsi FilterAttribute sınıf üzerinden miras almış ve ilgili Interface ile zorunlu metotları implement edilmiştir. Şimdi Filtreleri genel hatlarıyla açıklamadan önce ActionFilterAttribute özelliğimizi ele alıp sınıf (class) yapısına bir göz atalım.
1 2 3 4 5 6 |
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] public abstract class ActionFilterAttribute : FilterAttribute, IActionFilter, IFilter |
ActionFilterAttribute metadata incelemesinde sınıfın abstract olduğunu görüyoruz. O halde bu sınıf üzerinden miras alarak isteklerimizi özelleştirdiğimiz Attribute sınıflar yazabiliriz. Sınıf içerisinde .Net ‘ in farklı Attribute’larının kullanıldğını görüyoruz Örneğin AttributeUsage ile ActionFilterAttribute özelliğinin sınıf ve method’larda kullanılabileceği belirtilmiştir. Türetildiği FilterAtrribute üzerinden AllowMultiple ve Order gibi özellikler almış. AllowMultiple ile birden fazla instance için Filtre uygulamayı Order ile filtrenin öncelik sırasını set etmek için kullanılır. Henüz hiçbir metodu override etmedik. Şimdi bu kısmı ele alalım. ActionFilterAttribute sınıfında IActionFilter ve IResultFilter Interfacelerini görüyoruz. Bizim kodlarımızı yazacağımız çeşitli filtreler eklememizi sağlayan metotlar aşağıdaki gibidir.
IActionFilter
1 2 3 4 5 6 7 |
public virtual void OnActionExecuted(ActionExecutedContext filterContext); public virtual void OnActionExecuting(ActionExecutingContext filterContext); |
IResultFilter
1 2 3 4 5 6 7 |
public virtual void OnResultExecuted(ResultExecutedContext filterContext); public virtual void OnResultExecuting(ResultExecutingContext filterContext); |
Şimdi yukarıda tablo görünümünde verdiğimiz filtreler için kısa açıklamalar ekleyelim.Dip notumuzla beraber örneğimize geçebiliriz.
AuthorizationFilter
IAuthorizationFilter Interface’inden implement edilmiştir. Güvenlikle ilgili karar mekanızması oluşturmada bize yardımcı olur. Bu filtre diğer fitrelerle beraber kullanılsa bile ilk sırada çalışacaktır.
ActionFilter
IActionFilter Interface’inden implement edilmiştir. ActionMethodlar çalışırken şlkem yaparlar. Interface üzerinden farklı metot alır. OnActionExecuting ve OnActionExecuted. ActionResult objesi çalışmadan önce OnActionExecuting çalıştıktan sonra OnActionExecuted metodu çalışır. Bu sayede metodun çalışıp çalışmayacağına karar verebilir.
ResultFilter
IResultFilter Interface’inden implement edilmştir. OnResultExecuting ve OnResultExecuted metodları vardır. HTTP Respons’ a göre çeşitli filtreler kullanmamızı sağlar.
ExceptionFilter
Hata durumlarında çalışan filtredir. Hata durumlarında bazı loglar almak yada kullanıcı dostu bir hata sayfası gösterimi için kullanılabilir.
Not : Yukarıdaki filtreler üç farklı şekilde uygulanabilmektedir.
Global.asax ile tüm projede kullanılabilir
Controller kullanımı ile uygulanan controllerda tüm metotlarda kullanılabilir
ActionResult metot bazında kullanılabilir
Şimdi örnek senaryomuzu oluşturup kodlarımızı yazabiliriz.
Senaryomuzu ActionFilter üzerinden oluşturacağız. Kullanıcı ve Öğrenci bilgilerinin olduğu bir sistemde oluşturacağımız CustomFilterAttribute’lar ile görüntüleme ve silme yetkilerini oluşturacağız. Kayıt silme için bir iş kuralımız olacak. Her kullanıcı kendi oluşturduğu kaydı silebilecek. Şimdi kodlarımızı yazmaya başlayalım.
bilisimIOActionFilter adında MVC projesi oluşturuyoruz.
Projede kullanacağımız modelleri oluşturalım. Aşağıda oluşturduğumuz model’ler ile Öğrenci kullanıcı ve kullanıcı yetki bilgilerini saklayacağız.
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 |
public class HomeVM { public List<User> Users { get; set; } [DisplayName("Kullanıcı Seç :")] public int SelectedUser { get; set; } } public class Student { public int Id { get; set; } public string Name { get; set; } public int InsertedUserId { get; set; } } public class User { public int UserId { get; set; } public string UserName { get; set; } public UserAuthority Authority { get; set; } } public class UserAuthority { public int Id { get; set; } public int UserId { get; set; } public bool CanShow { get; set; } public bool CanDelete { get; set; } } |
Data Sınıfı
Projemizde database gibi kullanacağımız bir sınıf oluşturalım. Kullanıcı , yetki ve yetki detaylarını bellek üzerinde saklayıp işlem yapacağız.
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
public class DummyData { public static List<User> Users { get; set; } public static List<Student> Students { get; set; } public DummyData() { Users = GetUsers(); Students = new List<Models.Student>(); } private List<User> GetUsers() { return new List<User>() { new User { UserId = 1, UserName = "Burak" , Authority = new UserAuthority() { Id = 1, UserId = 1, CanDelete = true, CanShow = true } }, new User { UserId = 2, UserName = "Test1", Authority = new UserAuthority() { Id = 2, UserId = 2, CanDelete = false, CanShow = true } }, new User { UserId = 3, UserName = "Test2" , Authority = new UserAuthority() { Id = 3, UserId = 3, CanDelete = false, CanShow = false } }, }; } } |
Projemiz çalıştığında sınıftan bir instance alınıp kullanıma hazır olabilmesi için Global.asax dosyasında Application_Start metodunda sınıfımızdan yeni bir instance ile yapıcı metodu tetikleyip test datalarımızı kullanıma hazır hale getireceğiz.
1 2 3 4 5 6 7 8 9 |
protected void Application_Start() { //Diğer tanımlar DummyData dData = new DummyData(); } |
Projemize SessionHelper sınıfı ekliyoruz. Kayıtlı kullanıcı bilgisini burada tutacağız.
1 2 3 4 5 6 7 8 |
public class SessionHelper { public static int UserId { get; set; } } |
View
Senaryomuzun tam anlamıyla oturması için birkaç view ekleyeceğiz. View klasörü altında Student klasörü oluşturuyoruz. Klasöre sağ tıklayıp Add View ile CreateStudent , Delete , StudentList view’larını oluşturuyoruz. CreateStudent sayfamız için Create Template, StudentList için List template ve Delete sayfamız için Delete template ‘kullanıp Model class kısmında Student model seçiyoruz. Bu şekilde MVC bizim için en temel özellikleriyle işimizi görecek sayfaları otomatik oluşturuyor.
Viewların son hali aşağıdaki gibi olacaktır.
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
*********** Home - Index.cshtml **************** @{ ViewBag.Title = "Home Page"; } @model bilisimIOActionFilter.Models.HomeVM <div class="row"> <div style="clear:both"> <p>@TempData["Message"]</p> <p>@TempData["ResultMessage"]</p> @using (Html.BeginForm()) { <div> <div> @Html.LabelFor(x => x.SelectedUser) @Html.DropDownListFor(x => x.SelectedUser, new SelectList(Model.Users, "UserId", "UserName")) </div> <div> <input type="submit" value="Kullanıcı Seç" /> </div> </div> } </div> </div> *********** Student - CreateStudent.cshtml **************** @model bilisimIOActionFilter.Models.Student @{ ViewBag.Title = "CreateStudent"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>CreateStudent</h2> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-horizontal"> <h4>Student</h4> <hr /> @Html.ValidationSummary(true, "", new { @class = "text-danger" }) <div class="form-group"> @Html.LabelFor(model => model.Name, htmlAttributes: new { @class = "control-label col-md-2" }) <div class="col-md-10"> @Html.EditorFor(model => model.Name, new { htmlAttributes = new { @class = "form-control" } }) @Html.ValidationMessageFor(model => model.Name, "", new { @class = "text-danger" }) </div> </div> <div class="form-group"> <div class="col-md-offset-2 col-md-10"> <input type="submit" value="Create" class="btn btn-default" /> </div> </div> </div> } <div> @Html.ActionLink("Back to List", "Index") </div> @section Scripts { @Scripts.Render("~/bundles/jqueryval") } *********** Student - StudentList.cshtml **************** @model IEnumerable<bilisimIOActionFilter.Models.Student> @{ ViewBag.Title = "StudentList"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>StudentList</h2> <p> @Html.ActionLink("Create New", "CreateStudent") </p> <table class="table"> <tr> <th> @Html.DisplayNameFor(model => model.Name) </th> <th></th> </tr> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Name) </td> <td> @Html.ActionLink("Delete", "Delete", new { id = item.Id }, null) </td> </tr> } </table> *********** Student - Delete.cshtml **************** @model bilisimIOActionFilter.Models.Student @{ ViewBag.Title = "Delete"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h2>Delete</h2> <h3>Are you sure you want to delete this?</h3> <div> <h4>Student</h4> <hr /> <dl class="dl-horizontal"> <dt> @Html.DisplayNameFor(model => model.Name) </dt> <dd> @Html.DisplayFor(model => model.Name) </dd> </dl> @using (Html.BeginForm()) { @Html.AntiForgeryToken() <div class="form-actions no-color"> <input type="submit" value="Delete" class="btn btn-default" /> | @Html.ActionLink("Back to List", "Index") </div> } </div> |
Son olarak Controller tanımlarımız ve Atribute sınıfımızı ekleme işlemleri kaldı.
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
*********** HomeController ************* public class HomeController : Controller { [HttpGet] public ActionResult Index() { HomeVM homeVM = new HomeVM { Users = DummyData.Users }; return View(homeVM); } [HttpPost] public ActionResult Index(HomeVM homeVM) { homeVM.Users = DummyData.Users; SessionHelper.UserId = homeVM.SelectedUser; TempData["Message"] = "Seçilen Kullanıcı : " + homeVM.Users .FirstOrDefault(p => p.UserId == homeVM.SelectedUser).UserName; TempData.Keep(); return RedirectToAction("Index"); } } ************ StudentController ************** public class StudentController : Controller { [HttpGet] public ActionResult CreateStudent() { return View(); } [HttpPost] public ActionResult CreateStudent(Student studentVM) { Random rnd = new Random(); DummyData.Students.Add(new Models.Student() { Id = rnd.Next(1, 10000), Name = studentVM.Name, InsertedUserId = SessionHelper.UserId }); return RedirectToAction("StudentList"); } [HttpGet] [CanShow] public ActionResult StudentList() { List<Models.Student> stdList = DummyData.Students; return View(stdList); } [HttpGet] public ActionResult Delete(int id) { Student currentStudent = DummyData.Students.FirstOrDefault(p => p.Id == id); return View(currentStudent); } [HttpPost] [CanDelete] public ActionResult DeleteStudent(int id) { Student currentStudent = DummyData.Students.FirstOrDefault(p => p.Id == id); if (currentStudent != null) DummyData.Students.Remove(currentStudent); return RedirectToAction("StudentList"); } } |
Student Controller içinde CanDelete ve CanShow Attribute’larını ActionResult metodlar üzerinde görmüşsünüzdür. Şimdi ActionFilterAttribute üzerinden türettiğimiz CanDelete ve CanShow Filter attribute’larımızı 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
************* CanDelete ************** public class CanDelete : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { bool isCorrect = true; string resultMessage = string.Empty; int currentUserId = SessionHelper.UserId; var student = DummyData.Students .FirstOrDefault(p => p.Id == Convert.ToInt32(filterContext.ActionParameters["id"])); var userInfo = DummyData.Users.FirstOrDefault(p => p.UserId == currentUserId); if (!userInfo.Authority.CanDelete) { isCorrect = false; resultMessage = "Kullanıcının Silme yetkisi yok"; } else if (student.InsertedUserId != currentUserId) { isCorrect = false; resultMessage = "Bu kayıt başka bir kullanıcı tarafında oluşturuldu," + " Kaydı silemezsiniz..Kullanıcılar kendi oluşturdukları kayılar üzerinde " + "işlem yapabilir."; } if (!isCorrect) { filterContext.Controller.TempData["ResultMessage"] = resultMessage; filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary( new { controller = "Home", action = "Index" })); filterContext.Controller.TempData.Keep(); } base.OnActionExecuting(filterContext); } } ************* CanShow ************** public class CanShow : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext filterContext) { var userInfo = DummyData.Users.FirstOrDefault(p => p.UserId == SessionHelper.UserId); if (!userInfo.Authority.CanShow) { filterContext.Controller.TempData["ResultMessage"] = "Bu Kullanıcının görüntülenme yetkisi yok..."; filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary( new { controller = "Home", action = "Index" })); filterContext.Controller.TempData.Keep(); } base.OnActionExecuting(filterContext); } } |
ActionFilterAttribute CanShow CanDelete
Yukarıda tanımladığımız CanShow ve CanDelete sınıfları ActionFilterAttribute sınıfından türetilmiş ve metodlarını miras almıştır. Yazımızın başında belirttiğimiz OnActionExecuting metodu override edilip attribute’un tanımlandığı action çağırılmadan önce yetki kontrollerimizi yapacaktır. CanDelete FilterAttribute ile Sistemde tanımlı kullanıcının silme yetkisi kontrol ediliyor.Silme yetkisi yoksa anasyfaya dönüp ekrana uyarı mesajı veriyoruz. Silme yetkisine sahip bir kullanıcı başka bir kullanıcının oluşturduğu bir kaydı silmeye çalışıyorsa burada da bir kısıtlama getirip sadece kendi oluşturduğu kayda silme yetkisi veriyoruz. Aynı şekilde CanShow attribute ile görüntüleme yetkisine sahip olmayan bir kullanıcı sistemdeki öğrencileri göremeyecektir.
Bu yazımda MVC ActionResultFilter konusunu gerçekçi bir örnekle ele almaya çalıştım. Umarım yararlı olmuştur.
Örnek proje kodlarına aşağıdan ulaşabilirsiniz.
MVC Filters – ActionFilter Kullanımı
Referans