Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MVC Action Filter and Multiple Threads

I'm currently having what i think is a threading issue with action filters, on my app i'm using an ActionFilter to perform tracing of each action, this trace will provide statistical information such as the duration of the call and also log the parameters being sent to the action.

The actual trace implementation (which was done by other team) works with an IDisposable object, basically when creating the instance initializes the start time, and when disposing the object sets the end date, both calls create an entry in a custom log, the code is bellow (remove some code for simplicity purposes):

    public class TraceActionAttribute : ActionFilterAttribute
{
    private IDisposable logManagerBeginTrace;

    /// <summary>
    /// Called by the ASP.NET MVC framework before the action method executes.
    /// </summary>
    /// <param name="filterContext">The filter context.</param>
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        List<object> parameters = new List<object>();

        string actionName = filterContext.ActionDescriptor.ActionName;
        Type controllerType = filterContext.Controller.GetType();
        foreach (KeyValuePair<string, object> currentParameter in filterContext.ActionParameters)
        {
            parameters.Add(currentParameter.Value);
        }

        this.logManagerBeginTrace = LogManager.BeginMethodTrace(ApplicationConstants.ApplicationName, controllerType, actionName, Guid.NewGuid(), parameters.ToArray());
    }

    /// <summary>
    /// Called by the ASP.NET MVC framework after the action method executes.
    /// </summary>
    /// <param name="filterContext">The filter context.</param>
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);
        this.logManagerBeginTrace.Dispose();
    }
}

The exception is not telling me much, basically that it is trying to dispose elements while others are still active, i still have to look into the tracer code... but i found this post which says the following:

In previous versions of ASP.NET MVC, action filters were created 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.

Which for me appears very weirds, since action filters should be peer request, that's the reason we place public properties on it, and configure its behavior for specific Actions, don't we?

I appreciate any help on this, Regards.

like image 494
Bongo Sharp Avatar asked Jan 18 '23 22:01

Bongo Sharp


2 Answers

One possible workaround for this is to store the object instance in HttpContext.Items instead of in a private variable on the ActionFilter class.

HttpContext.Items is a per-request storage mechanism, which sounds like what you need.

This is what your modified code would roughly look like:

 public class TraceActionAttribute : ActionFilterAttribute
{
    private IDisposable logManagerBeginTrace;

    /// <summary>
    /// Called by the ASP.NET MVC framework before the action method executes.
    /// </summary>
    /// <param name="filterContext">The filter context.</param>
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);
        List<object> parameters = new List<object>();

        string actionName = filterContext.ActionDescriptor.ActionName;
        Type controllerType = filterContext.Controller.GetType();
        foreach (KeyValuePair<string, object> currentParameter in filterContext.ActionParameters)
        {
            parameters.Add(currentParameter.Value);
        }

        filterContext.HttpContext.Items["TraceActionKey"] = LogManager.BeginMethodTrace(ApplicationConstants.ApplicationName, controllerType, actionName, Guid.NewGuid(), parameters.ToArray());
    }

    /// <summary>
    /// Called by the ASP.NET MVC framework after the action method executes.
    /// </summary>
    /// <param name="filterContext">The filter context.</param>
    public override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);
        ((IDisposable)filterContext.HttpContext.Items["TraceActionKey"]).Dispose();
    }
}
like image 129
DMac the Destroyer Avatar answered Jan 25 '23 07:01

DMac the Destroyer


In MVC 5 at least the same behavior is true, action attributes are cached and reused. BUT not only that, be careful as the same instance can be used in multiple threads simultaneously.

like image 30
Jonas Brandel Avatar answered Jan 25 '23 09:01

Jonas Brandel