How do I pass a variable from an ActionFilter to a Controller Action in C# MVC?
Asked Answered
E

5

11

Taking a simple action filter, which checks if the user is logged in and retrieves their user ID.

public class LoginFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {    
        // Authenticate (somehow) and retrieve the ID
        int id = Authentication.SomeMethod();
        // Pass the ID through to the controller?
        .....
    }
}

How could I then pass this ID through to my controller action?

[LoginFilter]
public class Dashboard : Controller
{
    public ActionResult Index()
    {
        // I'd like to be able to use the ID from the LoginFilter here
        int id = ....
    }
}

Is there an equivalent to the ViewBag that would allow me to do this? Or some other technique that allows me to pass variables and objects between the filter and the controller action?

Elenaelenchus answered 9/8, 2016 at 13:34 Comment(0)
J
10

You can use ViewData/ViewBag like this:

1.) Using ViewData

NOTE: In case of ViewData you need to do one step that is you have to typecast it

public class LoginFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {    
        // Authenticate (somehow) and retrieve the ID
        int idValue = Authentication.SomeMethod();

        // Pass the ID through to the controller?
        filterContext.Controller.ViewData.Add("Id", idValue);
    }
}

And then in Controller function

[LoginFilter]
public class Dashboard : Controller
{
    public ActionResult Index()
    {
        // I'd like to be able to use the ID from the LoginFilter here
        int id = (int)ViewData["Id"];
    }
}

2.) Using ViewBag

public class LoginFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {    
        // Authenticate (somehow) and retrieve the ID
        int idValue = Authentication.SomeMethod();

        // Pass the ID through to the controller?

        filterContext.Controller.ViewBag.Id = idValue; 
    }
}

And then in controller

[LoginFilter]
public class Dashboard : Controller
{
    public ActionResult Index()
    {
        // I'd like to be able to use the ID from the LoginFilter here
        int id = ViewBag.Id;
    }
}
Jeanninejeans answered 9/8, 2016 at 16:28 Comment(1)
Thanks, although I've ended up using TempData (as suggested by @luiso's answer) rather than ViewBag or ViewData, yours gives the best detail on how to actually use it, along with examples, and the technique is the same for the ViewBag as for TempData. I figure that this is Temporary Data, rather than anything intended for the View, so it's more logical to use TempDataElenaelenchus
S
16

I believe ActionExecutingContext contains a reference to the calling controller. Using this mixed with a custom controller class derived from the base Controller class to then store the id as an instance variable of the controller would probably do it.

Custom controller

Public Class MyController : Controller
{
    Public int Id {get;set;}
}

LoginFilter

public class LoginFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {    
        // Authenticate (somehow) and retrieve the ID
        int id = Authentication.SomeMethod();
        ((MyController)filterContext.Controller).Id = id; 
        //Assign the Id by casting the controller (you might want to add a if ... is MyController before casting)
    }
}

Controller

[LoginFilter]
public class Dashboard : MyController
{
    public ActionResult Index()
    {
        //Read the Id here
        int id = this.Id
    }
}
Staff answered 9/8, 2016 at 13:47 Comment(2)
I like the logic of this solution, in that it plants the ID directly into the controller and is quite "neat", but decided not to go with it because the actual implementation seems a bit opaque to anybody maintaining the code later - the ID just seems to be magically set without explanation.Elenaelenchus
If we want to go 100% stateless way, then we should choose this way. +1 vote for your good solution @Francis Lord. Thanks.Sorilda
J
10

You can use ViewData/ViewBag like this:

1.) Using ViewData

NOTE: In case of ViewData you need to do one step that is you have to typecast it

public class LoginFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {    
        // Authenticate (somehow) and retrieve the ID
        int idValue = Authentication.SomeMethod();

        // Pass the ID through to the controller?
        filterContext.Controller.ViewData.Add("Id", idValue);
    }
}

And then in Controller function

[LoginFilter]
public class Dashboard : Controller
{
    public ActionResult Index()
    {
        // I'd like to be able to use the ID from the LoginFilter here
        int id = (int)ViewData["Id"];
    }
}

2.) Using ViewBag

public class LoginFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {    
        // Authenticate (somehow) and retrieve the ID
        int idValue = Authentication.SomeMethod();

        // Pass the ID through to the controller?

        filterContext.Controller.ViewBag.Id = idValue; 
    }
}

And then in controller

[LoginFilter]
public class Dashboard : Controller
{
    public ActionResult Index()
    {
        // I'd like to be able to use the ID from the LoginFilter here
        int id = ViewBag.Id;
    }
}
Jeanninejeans answered 9/8, 2016 at 16:28 Comment(1)
Thanks, although I've ended up using TempData (as suggested by @luiso's answer) rather than ViewBag or ViewData, yours gives the best detail on how to actually use it, along with examples, and the technique is the same for the ViewBag as for TempData. I figure that this is Temporary Data, rather than anything intended for the View, so it's more logical to use TempDataElenaelenchus
M
7

You can use the ViewBag by doing:

filterContext.Controller.ViewBag.Id = id;

that should do it, once you do filterContext.Controller you have access to all fields inside it like TempData as well.

Even so, if you are using OWIN then perhaps to get a user's id you could use the Controller.User which has an extension method to get the Id and properties to get most other standard data like Name etc.

Monadelphous answered 9/8, 2016 at 13:47 Comment(1)
Hmm, this seems like a neat solution. Logically, using the ViewBag probably doesn't make sense: but TempData is probably a good place for it. I'm not using OWIN (or at least, not for this part of the project, as we're mixing authentication techniques)Elenaelenchus
R
6

This is a method I've seen in an older application, avoids the use of viewbag & instead makes the controller parameters specify what they're expecting:

public class LoginFilter : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {    
        // Authenticate (somehow) and retrieve the ID
        int id = Authentication.SomeMethod();
        
        // Check if there's an action parameter on the controller: set it to your ID
        if (filterContext.ActionParameters.ContainsKey("authId"))
        {
            filterContext.ActionParameters["authId"] = id;
        }
    }
}

[LoginFilter]
public class Dashboard : Controller
{
    public ActionResult Index(int authId)
    {
        // authId parameter is available to each action bearing LoginFilter
        // It's instantiated if it's present in the method signature
        ...
    }
}
Rectum answered 17/12, 2020 at 12:8 Comment(1)
This is the most elegant way to do this kind of things i ever see. Thank you.Addle
T
1

I belive, use the Controller - ViewData or ViewBag is the wrong solution.

Define a small class with the needed properties and register this class by the service.

public class LittleClass
{
    public string WhatEver {get;set;}
}

And on the service

//...
services.AddScoped<LittleClass>();
//..

And now you can use the dependency injection on Filter and the Controller

public class YourFilter : ActionFilterAttribute
{
    private LittleClass _c;
    public YourFilter(LittleClass c)
    {
        _c = c;
    }
    
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        //manipulate _c
        _c.WhatEver = "TEST";
        base.OnActionExecuting(context);
    }
}

And the same via dependecy injection in your controller.

public class YourController : Controller
{

    private LittleClass _c;

    public YourController(LittleClass c)
    {
        _c = c;
        Console.WriteLine( _c.WhatEver ); // => TEST
    }
}
Toilet answered 3/11, 2023 at 8:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.