I have a custom attribute extension of System.Web.Http.Filters.ActionFilterAttribute
that I am using for logging with Web API controllers. I am experiencing an issue that indicates that the attribute object is being reused call to call. Data in my public properties from an initial call will appear in the logged information for a subsequent call and so on.
I read in this post that I "should never store instance state in an action filter that will be reused between the different methods." He goes on to say,"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."
My custom attribute is apparently "break" ing. Thus began my search to answer the question …
How do you pass thread safe data between the methods of a System.Web.Http.Filters.ActionFilterAttribute?
An example is given in the post I referenced above of how data should be passed method to method using the HttpContext.Items
dictionary. That's great and I can see how that would help but I'm not using ASP.net MVC'sSystem.Web.Http.Mvc.ActionFilterAttribute
– which the poster uses in his answer. I'm doing Web API and the context object passed into the OnActionExecuting
method is of type HttpActionContext
and not of type ActionExecutingContext
. I do not have access to the HttpContext.Items
dictionary through the passed context, however, I believe that it is safe to access the HttpContext
like this:
HttpContext.Current.Items[key]
Is that safe?
I do not have access to that dictionary in the constructor however and since that is where I receive my parameterized message string as a positional parameter, I am seemingly dependent on stored instance state.
So what to do?
In this post – also dependent on ASP.net MVC's System.Web.Http.Mvc.ActionFilterAttribute
and its ActionExecutingContext
– the poster uses the ActionParameters
property of that context object to get at the parameters passed to the attribute, but I cannot find any equivalent in Web API's HttpActionContext
. If I could, this would seem to be the answer! But alas…
How can I safely get to the positional parameter value passed into my constructor and the named parameter value passed in through a public property within the OnActionExecuting
method?
Posts I have researched:
- Are ActionFilterAttributes reused across threads? How does that work?
- MVC Action Filter and Multiple Threads
- passing action method parameter to ActionFilterAttribute in asp.net mvc
- Why is my ASP.NET Web API ActionFilterAttribute OnActionExecuting not firing?
- System.Web.Mvc.ActionFilterAttribute vs System.Web.Http.Filters.ActionFilterAttribute
- Web Api 2 HttpContext or HttpActionContext
Background: In the constructor, I pass a parameterized message string that includes placeholders for the values of arguments passed to the method the attribute is applied to. I also have an optional LogAllResponses property that is set through a named parameter to the attribute that I use to decide how much information I will log. The public properties that receive these values are set through the constructor and attribute invocation like this:
[LogAction("Retrieve information for all ad week items with storeId: {storeId}.", LogAllResponses = false)]
The important parts of the implementation of my action filter appear below:
public class LogActionAttribute : ActionFilterAttribute
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public string ParameterizedMessage { get; set; }
public bool LogAllResponses { get; set; } = true;
public LogActionAttribute(string parameterizedMessage)
{
ParameterizedMessage = parameterizedMessage;
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
HttpContext.Current.Items["__Parameterized_Message__"] = ParameterizedMessage;
HttpContext.Current.Items["__Log_All_Responses__"] = LogAllResponses.ToString();
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var contextualizedMessage = HttpContext.Current.Items["__Parameterized_Message__"] as string ?? "";
var logAllResponsesAsString = HttpContext.Current.Items["__Log_All_Responses__"] as string ?? "";
var logAllResponses = logAllResponsesAsString.CompareIgnoreCase("true") == 0;
// convert argument values with ID suffixes to identifiable names
var arguments = actionExecutedContext.ActionContext.ActionArguments;
//foreach (var arg in arguments)
// ...
// replace the placeholders in the parameterized message string with actual values
// log the contextualized message
//Log.Debug(...
base.OnActionExecuted(actionExecutedContext);
}
}