0

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.ActionFilterAttributeand 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:

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);
    }
}
jlavallet
  • 1,267
  • 1
  • 12
  • 33
  • 1
    An attribute is complie-time object. That means the attribute instance is part of the type definition, not of the custom object instance. – ZorgoZ Aug 22 '18 at 16:32
  • 1
    I'm not clear why you want to pass those 2 properties along at all. While the attribute is going to be reused, those properties will be preserved per instance. – DavidG Aug 22 '18 at 16:32
  • I suppose that you are right that the parameters are set at compile time. That's good. I use what is compiled in – namely the parameterized message string and the Boolean indicator that tells me how much to log in the context of the controller action I am calling to produce a contextualized log message. So then my real question is – how do I get at those two parameter values in my `OnActionExecuted ` method? Can I just access the public property? Note that I use this attribute with different parameters on a multitude of WebAPI controller actions. Am I confusing myself? – jlavallet Aug 22 '18 at 16:40
  • 1
    Yes, you're confusing yourself! Just use the properties. – DavidG Aug 22 '18 at 16:41
  • Okay so just to give you a little more background, I had another public property at one point that I set to hold the contextualized message. I was creating the contextualized message in the `OnActionExecuting ` method instead of in the `OnActionExecuted ` method. I suppose that that property – which was dynamically set based on what happened in the context of the `OnActionExecuting ` method is what was subject to each invocation and was producing results from prior calls.. Okay, thank you @DavidG. I think I am good. – jlavallet Aug 22 '18 at 16:46

0 Answers0