Action Filter

by Sachin Singh


Posted on Monday, 06 April 2020

Tags: Action Filter in mvc Custom Action Filter How to create dynamic Layout in mvc

This article is continuation of filters series ,so if you haven't read the previous articles I will strongly recommend you to go through each of them. In previous articles ,I have discussed different action filters available in Asp.Net MVC. In this article we will learn, how to create custom action filters for specific requirements.

As, we know action filters are used to inject pre-processing logic or post processing logic in Asp.Net MVC pipeline meaning if we want to execute some logic before executing the Action Method or if we want to execute some logic after executing the Action method ,then we can use Action Filters.

In order to make our own custom action filters we have to extend the methods of ActionFilterAttribute which actually implements the IActionFilter Interface.

ActionFilterAttribute class has four virtual methods as shown in figure.

Action Filter
Action Filter

It should be clear from the above figure that ActionFilterAttribute class has four virtual methods which can be overridden to fulfill our requirement.

• The first method is OnActionExecuted, where we can write logic that we want to execute just after an action method.
• The second method is OnActionExecuting, where we can write logic that must be executed before executing the Action method.
• The third method is OnResultExecuted, meaning if we want some logic to inject after getting the result from the Action method then this is the place for it.
• The fourth method is OnResultExecuting ,meaning if we want to alter the result of Action method before rendering it to screen ,we can do so.

Now let's take some real life scenario where we need to override these methods.

Example 1.We want a filter which restricts any user who is not in Admin role ,yes we have Authorize attribute but mechanism of fetching role is complex ,it uses UserIsInRole method internally, but we want to save roles simply in session variable for a logged in user.

Solution:-Obviously, here we want some logic to be executed before executing the Action result ,so we will have to override OnActionExecuting method.

Step 1.First create a Folder and name it Filter in your solution.

Add Folder
Add Folder

Step 2.Add a class in the created folder and name it as AdminFilter. Inherit it from ActionFilterAttribute and Override the OnActionExecuting method. Don't forget to import the following namespace.


    using System.Web.Mvc;
    using System.Web.Routing;


    namespace MVCExample.Filters
   {
    public class AdminFilter:ActionFilterAttribute
    {
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            bool isAdmin=Convert.ToBoolean(filterContext.HttpContext.Session["IsAdmin"]);
            string ReturnUrl=null;
           if(!isAdmin){
               filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary{{ "controller", "Home" },  
                                          { "action", "Login" } ,{"ReturnUrl",  
            filterContext.HttpContext.Request.Url.GetComponents(UriComponents.PathAndQuery, UriFormat.SafeUnescaped)}
  
                                         });
                ReturnUrl = filterContext.HttpContext.Request.Url.GetComponents(UriComponents.PathAndQuery, UriFormat.SafeUnescaped);
           }
          
         }
      }
    }

Step 3.Create Login get and Login Post method like we had created in previous articles.


     [HttpGet]
        public ActionResult Login(string ReturnUrl)
        {
            ViewBag.ReturnUrl = ReturnUrl;
            return View();
        }
        [HttpPost]
  public ActionResult Login(string UserName, string Password, bool RememberMe, string ReturnUrl)
        {
            Session["IsAdmin"] = false;
            if (UserName == "sachin" & Password == "sachin@7777")
            {
                Session["IsAdmin"] = true;
                return Redirect(ReturnUrl);
            }
            return View("Login");
        }


        @{
    ViewBag.Title = "Login";
       }
  <h2>Login</h2>
  <form action="/Home/Login" method="post">
    User Name:@Html.TextBox("UserName")
    Password :@Html.Password("Password")
    Remember Me:@Html.CheckBox("RememberMe")
   @Html.Hidden("ReturnUrl", new { @value = ViewBag.ReturnUrl})
  
    <button type="submit">Login</button>
    </form>

Step 4.Mark any method which you think ,should only be accessible to Admin with AdminFilter ,but don't forget to import the namespace that is the folder name where you have created the Admin Filter.


      using MVCExample.Filters;
       [AdminFilter]
          public ActionResult Post()
          {
              return View();
          }

Now, Run the application and try to access the post action method it will redirect you to Login Page after logging in with the correct username and password only, you will be redirected to the Post Action method.

Example 2.suppose our layout is dynamic and the List of chapters is coming from the database in the sidebar menu, As sharpencode.com has, suppose this Layout is being used by multiple views. how will you achieve such functionality?

Solution:-you will create a Base class and all your child class will inherit from it. Now in each Action method you have to do two things at first you have to populate the child class object so that it could be passed in the view and second, you will also have to populate the Base class object which is being used by Layout otherwise the Layout will throw a runtime exception.

Let's demonstrate the problem with an example.

Step 1.Create a Class with some properties and call it a Base View Model as given below.


    public class BaseVM
    {
        public List<link> Links { get; set; }
    }
    public class Link
    {
        public string Name { get; set; }
        public string Path { get; set; }
    }

Step 2.Create a layout and make it strongly typed with BaseVM.


   @using MVCExample.Models
   @model BaseVM

   @using TestFilter.Models
   @model BaseVM

   <!DOCTYPE html>

  <html>
  <head>
    <meta name="viewport" content="width=device-width" />
    <title>@ViewBag.Title</title>
   </head>
   <body>
    <div>
        @foreach (Link link in Model.links)
        {
            <a href="@link.Path">@link.Name</a>
        }
    </div>
    <div>
        @RenderBody()
    </div>
   </body>
   </html>

Step 3. Create two action methods which return strongly typed view of List of Employee and List of Customer ,obviously you will have to create four class in model folder for Employee and Customer and their Viewmodels and their respective view like below.


    public class Employee
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
    public class Customer
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    public class EmployeeVM: BaseVM
    {
      public  List<Employee> employees { get; set; }
    }

    public class CustomerVM: BaseVM
    {
        public List<Customer> customers { get; set; }
    }

    public ActionResult GetEmployee()
        {
            EmployeeVM evm = new EmployeeVM();
            evm.employees = new List<Employee>(){
                new Employee(){Id=1,Name="emp1"},
                new Employee(){Id=2,Name="emp2"},
                new Employee(){Id=3,Name="emp3"},
            };
           evm.links=new List<Link>(){
               new Link(){Name="Link 1",Path="/home/link1"},
                new Link(){Name="Link 2",Path="/home/link2"},
                 new Link(){Name="Link 3",Path="/home/link3"},
                  new Link(){Name="Link 4",Path="/home/link4"},
           };
            return View(evm);
        }
        public ActionResult GetCustomer()
        {
            CustomerVM cvm = new CustomerVM();
            cvm.customers = new List<Customer>(){
                new Customer(){Id=1,Name="customer1"},
                new Customer(){Id=2,Name="customer2"},
                new Customer(){Id=3,Name="customer3"},
            };
            cvm.links=new List<Link>(){
               new Link(){Name="Link 1",Path="/home/link1"},
                new Link(){Name="Link 2",Path="/home/link2"},
                 new Link(){Name="Link 3",Path="/home/link3"},
                  new Link(){Name="Link 4",Path="/home/link4"},
           };

            return View(cvm);
        }

     @using TestFilter.Models
     @model CustomerVM
    @{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
     }

  <h2>Customer</h2>
   <ul>
    @foreach (var customer in Model.customers)
    {
         <li>@customer.Id</li>
        <li>@customer.Name</li>
       
    }
  </ul>

    @using TestFilter.Models
    @model CustomerVM
    @{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
      }

    <h2>Customer</h2>
    <ul>
     @foreach (var customer in Model.customers)
     {
         <li>@customer.Id</li>
        <li>@customer.Name</li>
       
     }
    </ul>

Now run the application it will give below results.

Employees view
GetEmployee Result
Customers view
GetCustomer Result

Everything is fine ,we are getting desired results but there is problem, in each Action method we are bound to populate BaseVM object which is overwhelming.

Wouldn't it be great if third person take responsibility of populating BaseVM and our Action method just focus on populating model which is only of their use. Like GetEmployee should only populate Employee object and must be free from BaseVM.

That third persion is ActionFilter. We can use OnActionExecuted method of ActionFilterAttribute like below.


    namespace MVCExample.Filters
    {
    public class LinkFilter : ActionFilterAttribute
    {
        public override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            ViewResult vr = filterContext.Result as ViewResult;
            if (vr != null)
            {
                BaseVM bvm = vr.Model as BaseVM;
             
                bvm.links = new List<Link>() { 
                    new Link(){Name="Link1",Path="path1"},
                        new Link(){Name="Link2",Path="path2"},
                            new Link(){Name="Link3",Path="path3"},
                    };
            }
        }
       
        }

Now mark the above two methods with Link attribute and remove BaseVM like below.



       [LinkFilter]
        public ActionResult GetEmployee()
        {
            EmployeeVM evm = new EmployeeVM();
            evm.employees = new List<Employee>(){
                new Employee(){Id=1,Name="emp1"},
                new Employee(){Id=2,Name="emp2"},
                new Employee(){Id=3,Name="emp3"},
            };
          
            return View(evm);
        }
        [LinkFilter]
        public ActionResult GetCustomer()
        {
            CustomerVM cvm = new CustomerVM();
            cvm.customers = new List<Customer>(){
                new Customer(){Id=1,Name="customer1"},
                new Customer(){Id=2,Name="customer2"},
                new Customer(){Id=3,Name="customer3"},
            };
       

            return View(cvm);
        }

Now run the application you will get the same result. Now I think you got the point that how powerful Action filters can be.