70

If I create an object in a Custom Action Filter in ASP.NET MVC in

public override void OnActionExecuting(ActionExecutingContext filterContext)
{
    DetachedCriteria criteria = DetachedCriteria.For<Person>();
    criteria.Add("stuff");

    // Now I need to access 'criteria' from the Action.....

}

is there any way I can access the object from the Action that is currently executing.

reach4thelasers
  • 26,181
  • 22
  • 92
  • 123

4 Answers4

65

The better approach is described by Phil Haack.

Basically this is what you do:

public class AddActionParameterAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        // Create integer parameter.
        filterContext.ActionParameters["number"] = 123;

        // Create object parameter.
        filterContext.ActionParameters["person"] = new Person("John", "Smith");
    }
}

The only gotcha is that if you are creating object parameters, then your class (in this case Person) must have a default constructor, otherwise you will get an exception.

Here's how you'd use the above filter:

[AddActionParameter]
public ActionResult Index(int number, Person person)
{
    // Now you can use number and person variables.
    return View();
}
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
niaher
  • 9,460
  • 7
  • 67
  • 86
  • 2
    Very nice way to do this, I definitely think this is way better than the accepted answer. This should be the accepted answer! Thanks! – avb Jul 24 '14 at 12:02
  • Why is this better than the accepted answer? There seems to be no reason why this would be the case and the additional [AddActionParameter] is clearly additional work that the other method doesn't require to function. – Chris Mar 01 '16 at 09:57
  • @ChrisBertrand It's cleaner and avoids compile-time errors in controller, by using strong-typing, i.e. - `Person` class. It also makes unit-testing much easier. – niaher Mar 01 '16 at 11:25
  • 1
    The only problem with this approach is when you're trying to do this and a post at the same. By adding it to the action parameters, it then participates in model validation, and you can end up with odd situations where the posted data is valid, but `ModelState.IsValid` is still false, because something's not valid on the object you passed in. – Chris Pratt Apr 22 '16 at 13:01
  • 6
    This isn't a great solution because now your api could accept these parameters as querystrings which is an unintended side-effect. It's worse when you have documentation libraries like swagger which will show these parameters are query parameters for the URL when they're strictly for internal use. – arviman Jun 01 '16 at 09:22
  • @ChrisPratt I just ran into this issue when using a Filter to pull in database entities that had `[Required]` attributes. Is there any way to disable model binding and validation for these attributes? Because otherwise this is certainly the neatest solution, – Ryan Jan 24 '17 at 04:18
  • I spent a bunch of time going down this path, but the issue I hit sounds similar to what was described above. If the controller uses the `[ApiController]` attribute, then attempting to use `ActionAttributes` appears to result in always seeing a 415 error due to failed model bindings. I would love to hear if anyone's found an approach to work around that issue. I'm currently using `HttpContext.Items` instead. – Ben Zittlau Nov 08 '19 at 04:32
56

I would recommend putting it in the Route data.

    protected override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.RouteData.Values.Add("test", "TESTING");
        base.OnActionExecuting(filterContext);
    }

    public ActionResult Index()
    {
        ViewData["Message"] = RouteData.Values["test"];

        return View();
    }
Chad Moran
  • 12,834
  • 2
  • 50
  • 72
  • 2
    How long does an item survive for in RouteData? I only need to keep the object for the duration of the currently executing action, or at the most for the current request, if this is how route data works then this is the answer otherwise HttpContext.Items is probably better. – reach4thelasers Nov 29 '09 at 12:07
  • 1
    RouteData is data related to the currently executing route (action). Think it as a container that represents the request url parsed and mapped according to your routing rules. – Neal Jan 13 '10 at 02:09
  • `RouteData` is certainly great for doing this as I've learned today thanks to your answer here. My beautiful black & yellow MVC book here in front of me (fourth edition) doesn't mention anything about it or do anything similar to this at all in the entire chapter on filters (or at least I just haven't found it yet?). Anyway, +1 and thank you! – Funka Mar 07 '13 at 02:25
  • `filterContext.ControllerContext.RouteData.Values.Add("key",value);` is working for me! :) – Nameless Mar 24 '17 at 10:50
43

You could use the HttpContext:

filterContext.HttpContext.Items["criteria"] = criteria;

And you can read it in the action:

[YourActionFilter]
public ActionResult SomeAction() 
{
    var criteria = HttpContext.Items["criteria"] as DetachedCriteria;
}
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 1
    I was thinking about using HttpContext.Items[] and its an acceptable solution as it will be cleared out at the end of the request. I wasn't sure if there was a place I could store stuff that exists only for the duration of the action. – reach4thelasers Nov 29 '09 at 12:08
3

Set item in ViewData or of a viewmodel if you pass it as a parameter into your action. Here I set the property of a ViewModel

public override void OnActionExecuting(ActionExecutingContext filterContext)
 {
     ViewModelBase viewModel = null;
     foreach (object parameter in filterContext.ActionParameters.Values)
     {
         if (parameter is ViewModelBase)
         {
             viewModel = (ViewModelBase)parameter;
             break;
         }
     }
     if(viewModel !=null)
     {
         viewModel.SomeProperty = "SomeValue";
     }
 }


    public ActionResult About(ViewModelBase model)
    {
      string someProperty= model.SomeProperty;
}

Here is the untyped version I think you prefer:

   public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        filterContext.Controller.ViewData.Add("TestValue", "test");

    }

       [FilterWhichSetsValue]
        public ActionResult About()
        {
            string test = (string)ViewData["TestValue"];
            return View();
        }
BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Mathias F
  • 15,906
  • 22
  • 89
  • 159
  • Thanks for your suggestion. My actions don't take ViewModelBase as a parameter though, and I would prefer not to introduce it just to solve my problem. – reach4thelasers Nov 29 '09 at 12:10
  • See the edited post for the untyped version. I would still use the first version. Maybe the parametername just sounds to bad. It can be any class and doesnt have to be the class of the typed view. – Mathias F Nov 29 '09 at 17:36