Reputation: 8695
I want to create a simple, global ActionFilterAttribute
that will tell me the resource requested and how long it took to run. Here is the code so far:
public class APITraceAttribute : ActionFilterAttribute
{
private readonly Stopwatch timer;
public APITraceAttribute()
{
timer = new Stopwatch();
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
timer.Restart();
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
timer.Stop();
trace(actionExecutedContext.ActionContext, timer.Elapsed);
base.OnActionExecuted(actionExecutedContext);
}
private static void trace(HttpActionContext actionContext, TimeSpan duration)
{
HttpMethod method = actionContext.Request.Method;
string path = actionContext.Request.RequestUri.PathAndQuery;
string controllerName = actionContext.ControllerContext.ControllerDescriptor.ControllerName;
string actionName = actionContext.ActionDescriptor.ActionName;
double totalSeconds = duration.TotalSeconds;
ILogger logger = new Logger("trace");
logger.Trace("{0} {1}: {2}Controller.{3} ({4:N4} seconds)...", method, path, controllerName, actionName, totalSeconds);
}
}
As you can see, I am using a Stopwatch
as a backing field that I Restart
when the action starts and Stop
when the action ends. I don't need great precision, so this is fine.
However, I wire this up to run for all requests, by editing the /App_Start/WebApiConfig.cs
file:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.Filters.Add(new APITraceAttribute());
}
}
I noticed that this takes a constructed attribute. This makes me concerned that it is going to reuse the same attribute instance across-the-board and that two concurrently processing requests might try to use the same Stopwatch
instance. Is this the case? How can I make sure each request gets its own instance of Stopwatch
?
Upvotes: 1
Views: 365
Reputation: 301
You can use HttpContext.Current.Items
i think its safe to say that it will be store per request.
Try this
public override void OnActionExecuting(HttpActionContext actionContext)
{
var timer = HttpContext.Current.Items["timer"] as Stopwatch;
if (timer == null)
{
timer = new Stopwatch();
HttpContext.Current.Items["timer"] = timer;
}
timer.Restart();
base.OnActionExecuting(actionContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var timer = HttpContext.Current.Items["timer"] as Stopwatch;
timer.Stop();
trace(actionExecutedContext.ActionContext, timer.Elapsed);
base.OnActionExecuted(actionExecutedContext);
}
i'm creating the Stopwatch object inside OnActionExecuting because as you said the instance of the filter may be shared between requests and it wont be safe to create it in the constructor, so each time OnActionExecuting fires it will use HttpContext.Current wich is request independant.
Upvotes: 1