Are ActionFilterAttributes reused across threads? How does that work?
Asked Answered
I

1

48

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?

Illiterate answered 20/1, 2012 at 5:39 Comment(0)
G
90

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
    }
}
Gow answered 20/1, 2012 at 6:54 Comment(14)
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.Illiterate
@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.Gow
Fine, but then how can OutputCache (which is an action filter) have public properties such as Duration, CacheProfile, VaryByCustom etc.?Illiterate
@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.Gow
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?Illiterate
@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, ...Gow
@DarinDimitrov but filterContext.HttpContext.Items["private_value"] is shared between different instances of this action filter. So the value can be overwritten by others.Twayblade
@Daniel Surely they'd be specific to the filterContext passed in? The action getting called has its own filterContext specific to that request.Prolong
@Prolong 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.Twayblade
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 HttpContextKnitter
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?Knitter
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 ?Knitter
@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?Acus
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 learn.microsoft.com/en-us/dotnet/api/… this is the purpose of the HttpContext.Items, to share data within the SAME requestCoessential

© 2022 - 2024 — McMap. All rights reserved.