47

I have been doing some testing with the following code to try and work out how ActionFilterAttributes works:

public class TestAttribute : ActionFilterAttribute
{
    private string _privateValue;
    public string PublicValue { get; set; }

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        _privateValue = DateTime.Now.ToString();

        base.OnActionExecuting(filterContext);
    }
}

When I run the above code on two parallel threads the _privateValue field gets confused. However the PublicValue property doesn't get confused.

It looks to me like ActionFilterAttributes are reused across threads, but that new instances are created depending on the constants specified to public properties. Am I correct?

Where can I find information on this?

cbp
  • 25,252
  • 29
  • 125
  • 205

1 Answers1

89

This will depend on the version of ASP.NET MVC but you should never store instance state in an action filter that will be reused between the different methods. Here's a quote for example from one of the breaking changes in ASP.NET MVC 3:

In previous versions of ASP.NET MVC, action filters are create per request except in a few cases. This behavior was never a guaranteed behavior but merely an implementation detail and the contract for filters was to consider them stateless. In ASP.NET MVC 3, filters are cached more aggressively. Therefore, any custom action filters which improperly store instance state might be broken.

This basically means that the same instance of the action filter can be reused for different actions and if you have stored instance state in it it will probably break.

And in terms of code this means that you should absolutely never write an action filter like this:

public class TestAttribute : ActionFilterAttribute
{
    private string _privateValue;

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        _privateValue = ... some calculation
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        // use _privateValue here
    }
}

but you should write it like this:

public class TestAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        var privateValue = ... some calculation
        filterContext.HttpContext.Items["__private_value__"] = privateValue;
    }

    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        var privateValue = filterContext.HttpContext.Items["__private_value__"];
        // use privateValue safely here
    }
}
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • 2
    What about the public properties of an Action Filter (e.g. OutputCache's Duration property)? I assume that a new instance is created if the property is different. – cbp Jan 20 '12 at 07:37
  • @cbp, what about them? Don't assume anything about the lifetime of an action filter. As quoted in the release notes this is not something that you should rely upon. – Darin Dimitrov Jan 20 '12 at 07:46
  • 1
    Fine, but then how can OutputCache (which is an action filter) have public properties such as Duration, CacheProfile, VaryByCustom etc.? – cbp Jan 21 '12 at 04:09
  • 2
    @cbp, there is nothing wrong from having instance fields (private or public) on an action filter. It's the way you are using them inside that you should be extremely careful about. – Darin Dimitrov Jan 21 '12 at 07:50
  • 5
    OK but if the ActionFilter gets reused in parallel wouldn't there be the potential for the value of the public properties to be unreliable? For example, using my code above, if two actions execute at the same time - one that sets PublicValue to "a", and one that sets PublicValue to "b" - I would have to be confident that two instances of TestAttribute were created, otherwise I could not rely on the value of PublicValue, no matter how I'm planning on using it. What would be the point of even having public properties if you could never rely on their state being correct for a particular action? – cbp Jan 21 '12 at 10:20
  • 7
    @cbp, in your case you have 2 different actions because on the first you set PublicValue to "a" and on the second you set it to "b". There's no problem in this case because action filters are attributes and attributes are metadata in .NET and metadata is associated to a particular member at compile time. You could encounter problems if it is for the same action and you try to reuse the values of your instance field between the different methods `OnActionExecuting`, `OnActionExecuted`, ... – Darin Dimitrov Jan 21 '12 at 10:30
  • @DarinDimitrov but filterContext.HttpContext.Items["__private_value__"] is shared between different instances of this action filter. So the value can be overwritten by others. – Daniel B Feb 16 '16 at 06:03
  • @Daniel Surely they'd be specific to the `filterContext` passed in? The action getting called has its own `filterContext` specific to that request. – FLGMwt Mar 01 '16 at 17:29
  • @FLGMwt Yes, every action has its own context. But all of those context objects reference the same instance of `HttpContext`. So if you set `filterContext.HttpContext.Items["private_value"]` in one action filter, you can read or overwrite it in a different action filter. I tested it. – Daniel B Mar 02 '16 at 03:14
  • Ok @Daniel that's interesting, so how could you do it in a tamper proof way? But yours describes a different issue, right? Not one caused by parallelism and race conditions, but by conflicting in the variable space of the HttpContext – Nate Anderson Sep 25 '16 at 13:53
  • Darin, your answer says this "same instance of the action filter can be reused for different actions" but your comments you suggest it could be ok to use an attribute and modify instance variable, (public or private), on "2 different actions" without concern. I am confused, aren't those contradictory? – Nate Anderson Sep 25 '16 at 13:59
  • And last question, is there any difference in the lifespan of the attribute depending on my method for applyhing the attribute: 1) Adding to GlobalFilterCollection 2) Decorating 2a) Controllers and/or 2b) Actions ? – Nate Anderson Sep 25 '16 at 14:26
  • @Daniel @TheRedPea if some properties of the `filterContext` are shared among instances of the action filters, could we solve this issue by ensuring a unique key based on the request? Each _request_ should be unique, even between _different instances_ of the action filter, and the `HttpRequestBase` object should be the same among all the methods of the action filter instance, right? So what if we did something like `filterContext.HttpContext.Items[filterContext.HttpContext.Request] = privateValue`? – Seth Dec 08 '16 at 17:45
  • this is kind of VERY old now so I'm not sure if this is the same for all versions of .NET but according to Microsoft docs https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httpcontext.items?view=aspnetcore-2.1 this is the purpose of the HttpContext.Items, to share data within the SAME request – Hassan Khallouf Aug 26 '18 at 05:40