Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Web API caches action filter attributes across requests

There seems to be some weird behavior in ASP.NET Web API (4.0.30506) that I haven't witnessed before.

What I'm seeing is that the same action filter attribute instance is reused over Web API requests. This is especially a problem in case this attribute gets dependencies injected to it, since those dependencies might be specific to the web request. I'm aware that it's better for attributes to be passive, but my assumption was that action filter attributes where not cached.

I searched for any articles, blog posts or Microsoft change logs that described this and the reason behind this, but I couldn't find a single thing. That makes me wonder whether there is something wrong with my configuration that makes this happening. Thing is however, that I'm able to reproduce this issue in a new and empty Visual Studio 2012 Web API project.

What I did was create a new empty project using the Visual Studio 2012 ASP.NET MVC 4 Web Application project with the "Web API" template. It comes with the Web API 4.0.20710.0 NuGet package. After that I added the following attribute:

[DebuggerDisplay("{id}")]
public class TestFilterAttribute : System.Web.Http.Filters.ActionFilterAttribute {
    private static readonly List<int> used = new List<int>();

    private static int counter;
    private readonly int id;

    public TestFilterAttribute() {
        this.id = Interlocked.Increment(ref counter);
    }

    public override void OnActionExecuting(HttpActionContext actionContext) {
        // Just for testing: I would expect this line never to throw, but it does.
        if (used.Contains(this.id)) throw new Exception("WAT?");
        used.Add(this.id);
        base.OnActionExecuting(actionContext);
    }
}

And I add this attribute to the ValuesController (part of the default template):

public class ValuesController : ApiController {
    // GET api/values
    [TestFilterAttribute]
    public IEnumerable<string> Get() {
        return new string[] { "value1", "value2" };
    }

    // ...
}

Now when I start the project, go to the /api/values in the browser and refresh that page a few times, the "WAT?" exception is thrown.

Is this normal behavior of Web API and if so, what's the reasoning about this? Or Did I miss some memo about this change somewhere? Does this make Web API attributes extra unsuited for doing dependency injection? Or am I'm doing something wrong?

like image 281
Steven Avatar asked Dec 25 '14 10:12

Steven


People also ask

Does Net Web API support action filter?

The ASP.NET MVC framework supports four different types of filters: Authorization filters – Implements the IAuthorizationFilter attribute. Action filters – Implements the IActionFilter attribute. Result filters – Implements the IResultFilter attribute.

What is difference between middleware and action filter?

Middleware vs Filters Filters are a part of MVC, so they are scoped entirely to the MVC middleware. Middleware only has access to the HttpContext and anything added by preceding middleware. In contrast, filters have access to the wider MVC context, so can access routing data and model binding information for example.

What step is required to apply a filter globally to every action in your application?

You need to add your filter globally, to add your filter to the global filter. You first need to add it on Global. asax file. You can use FilterConfig.


1 Answers

Web API is built on top of MVC, thus it uses a lot of it's functionality.

Attribute instance re-usability is part of the aggressive caching introduced by MVC 3. This means that the same Attribute instance will most likely be used with all the Actions it is applied on. MVC pipeline will do it's best at trying to treat your Attribute class like a Singleton.

Because the same Attribute instance is reused, it's Constructor is not called and id is not incremented. If, for example, you increment id inside OnActionExecuting, all will work well.

You can still do everything you want with your Attribute. You only need to keep in mind that you are not guaranteed to always get a new instance created. The constructor shouldn't contain anything but initial initialization.

public TestFilterAttribute() {
    // Instance will be reused thus this will not be called for each Action
}

public override void OnActionExecuting(HttpActionContext actionContext) {
    // Called on each Action
}
like image 73
Mihai Dinculescu Avatar answered Sep 21 '22 18:09

Mihai Dinculescu