We implement a log information to our database. I will use a Filters (IActionFilter) functionality for it. I wrote the following class:
public class ActionFilter: Attribute, IActionFilter
{
DateTime start;
public void OnActionExecuting(ActionExecutingContext context)
{
start = DateTime.Now;
}
public void OnActionExecuted(ActionExecutedContext context)
{
DateTime end = DateTime.Now;
double processTime = end.Subtract(start).TotalMilliseconds;
... some log actions
}
}
Then I have added the following code to the Startup.cs:
services.AddMvc(options => {
options.Filters.Add(typeof(ActionFilter));
});
It works fine. I get the breakpoint in ActionFilter for each my method.
But I want to ignore for logging the most part of methods. As I understand, I can do it with my own attribute. I didn't work with own attributes before. Ok, I wrote the following attribute:
public class IgnoreAttribute : Attribute
{
public IgnoreAttribute()
{ }
}
I added the attribute to method:
[Ignore]
[HttpGet]
[Route("api/AppovedTransactionAmountByDays/{daysCount}")]
public JsonResult GetAppovedTransactionAmountByDays(int daysCount)
{
var result = daysCount;
return new JsonResult(result);
}
Of course, there simple actions don't work.
How I must change my attribute or my ActionFilter for ignore of method?
Thanks in advance.
felix-b's note about naming is a good one.
And I want to make another note. You should not store state in the filter when you register it in this way. Since it is an attribute, it is instantiated only once! So you have a massive race condition there. One option would be to use:
services.AddMvc(o =>
{
o.Filters.Add(new ServiceFilterAttribute(typeof(LoggingActionFilter)));
});
And register it as transient:
services.AddTransient<LoggingActionFilter>();
Now the attribute is instantiated every time it is needed, so you can safely store state.
Configuring it so that it ignores the action if the marker attribute is present is also possible:
public class LoggingActionFilter : Attribute, IActionFilter
{
private DateTime start;
private bool skipLogging = false;
public void OnActionExecuting(ActionExecutingContext context)
{
var descriptor = (ControllerActionDescriptor)context.ActionDescriptor;
var attributes = descriptor.MethodInfo.CustomAttributes;
if (attributes.Any(a => a.AttributeType == typeof(SkipLoggingAttribute)))
{
skipLogging = true;
return;
}
start = DateTime.Now;
}
public void OnActionExecuted(ActionExecutedContext context)
{
if (skipLogging)
{
return;
}
DateTime end = DateTime.Now;
double processTime = end.Subtract(start).TotalMilliseconds;
}
}
public class SkipLoggingAttribute : Attribute
{
}
Here we get the action descriptor available from the parameter and find if the method in question has the SkipLogging attribute. If it does, skip the logging code.
First, I would strongly suggest renaming your ActionFilter
to something more specific, like LogActionFilterAttribute
(pay attention -- the action filter is an attribute). Now instead of applying it globally with options.Filters.Add(...)
, apply it only to the actions you want to log:
// an action that must be logged -- apply LogActionFilter attribute
[HttpGet]
[Route("api/....")]
[LogActionFilter]
public JsonResult FirstAction(...)
{
//...
}
// an action that should not be logged -- don't apply LogActionFilter
[HttpGet]
[Route("api/....")]
public JsonResult SecondAction(...)
{
//...
}
If you have an explicit requirement for the "Ignore" attribute, you can implement it as follows.
Leave the global configuration the way you did:
services.AddMvc(options => {
options.Filters.Add(typeof(LoggingActionFilter));
});
The implementation of LoggingActionFilter
should change:
// this filter is applied globally during configuration of web application pipeline
public class LoggingActionFilter : IActionFilter
{
// we use private class types as keys for HttpContext.Items dictionary
// this is better than using strings as the keys, because
// it avoids accidental collisions with other code that uses HttpContext.Items
private class StopwatchItemKey { }
private class SuppressItemKey { }
public void OnActionExecuting(ActionExecutingContext context)
{
// here we save timestamp at the beginning of the request
// I use Stopwatch because it's handy in this case
context.HttpContext.Items[typeof(StopwatchItemKey)] = Stopwatch.StartNew();
}
public void OnActionExecuted(ActionExecutedContext context)
{
// check whether SuppressLoggingAttribute was applied to current request
// we check it here in the end of the request because we don't want to depend
// on the order in which filters are configured in the pipeline
if (!context.HttpContext.Items.ContainsKey(typeof(SuppressItemKey)))
{
// since SuppressItemKey was not set for the current request,
// we can do the logging stuff
var clock = (Stopwatch) context.HttpContext.Items[typeof(StopwatchItemKey)];
var elapsedMilliseconds = clock.ElapsedMilliseconds;
DoMyLoggingStuff(context.HttpContext, elapsedMilliseconds);
}
}
// SuppressLoggingAttribute calls this method to set SuppressItemKey indicator
// on the current request. In this way SuppressItemKey remains totally private
// inside LoggingActionFilter, and no one else can use it against our intention
public static void Suppress(HttpContext context)
{
context.Items[typeof(SuppressItemKey)] = null;
}
}
The "Ignore" attribute (I named it SuppressLoggingAttribute
) will look like this:
// this filter attribute is selectively applied to controllers or actions
// in order to suppress LoggingActionFilter from logging the request
public class SuppressLoggingAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// this will put "suppress" indicator on HttpContext of the current request
LoggingActionFilter.Suppress(context.HttpContext);
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
Now you only need to apply the "ignore" attribute wherever necessary:
[HttpGet("{id}")]
[SuppressLogging]
public string Get(int id)
{
return "value";
}
In contrast to @junnas' answer, my code doesn't use Reflection (MethodInfo.CustomAttributes
), and thus it works faster.
If anyone questions the use of Stopwatch
: yes, Stopwatch.StartNew()
allocates a new Stopwatch
object on the heap every request. But assigning DateTime
to HttpContext.Items
dictionary does the same because it implies boxing. Both DateTime
and Stopwatch
objects are of 64-bit size, so allocation-wise, both DateTime
and Stopwatch
options are equal.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With