ASP.NET 核心过滤器 - 忽略定义的方法
ASP.NET Core Filters - ignore for defined methods
我们将日志信息记录到我们的数据库中。
我将为它使用过滤器 (IActionFilter) 功能。
我写了以下 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
}
}
然后我将以下代码添加到 Startup.cs:
services.AddMvc(options => {
options.Filters.Add(typeof(ActionFilter));
});
它工作正常。我在 ActionFilter 中为我的每个方法设置了断点。
但我想忽略大部分方法的记录。
据我了解,我可以用自己的属性来做到这一点。我以前没有使用自己的属性。
好的,我写了以下属性:
public class IgnoreAttribute : Attribute
{
public IgnoreAttribute()
{ }
}
我将属性添加到方法中:
[Ignore]
[HttpGet]
[Route("api/AppovedTransactionAmountByDays/{daysCount}")]
public JsonResult GetAppovedTransactionAmountByDays(int daysCount)
{
var result = daysCount;
return new JsonResult(result);
}
当然,简单的操作是行不通的。
我必须如何更改我的属性或我的 ActionFilter 以忽略方法?
提前致谢。
首先,我强烈建议将您的 ActionFilter
重命名为更具体的名称,例如 LogActionFilterAttribute
(注意——动作过滤器是一个属性)。现在不再使用 options.Filters.Add(...)
全局应用它,而是仅将它应用到您要记录的操作:
// 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(...)
{
//...
}
更新版本
如果您对"Ignore"属性有明确的要求,可以按如下实现。
按照您的方式保留全局配置:
services.AddMvc(options => {
options.Filters.Add(typeof(LoggingActionFilter));
});
LoggingActionFilter
的实现应该改变:
// 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;
}
}
"Ignore" 属性(我将其命名为 SuppressLoggingAttribute
)将如下所示:
// 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)
{
}
}
现在您只需在必要时应用 "ignore" 属性:
[HttpGet("{id}")]
[SuppressLogging]
public string Get(int id)
{
return "value";
}
性能考虑
与@junnas 的回答相反,我的代码没有使用反射 (MethodInfo.CustomAttributes
),因此运行速度更快。
如果有人质疑 Stopwatch
的使用:是的,Stopwatch.StartNew()
会在每次请求时在堆上分配一个新的 Stopwatch
对象。但是将 DateTime
分配给 HttpContext.Items
字典也是一样的,因为它意味着装箱。 DateTime
和 Stopwatch
对象都是 64 位大小,因此在分配方面,DateTime
和 Stopwatch
选项都是相等的。
felix-b 关于命名的注释是一个很好的注释。
我想再做一个说明。以这种方式注册时,不应将状态存储在过滤器中。既然是属性,就只实例化一次!所以你在那里有一个巨大的竞争条件。一种选择是使用:
services.AddMvc(o =>
{
o.Filters.Add(new ServiceFilterAttribute(typeof(LoggingActionFilter)));
});
并将其注册为瞬态:
services.AddTransient<LoggingActionFilter>();
现在每次需要时都会实例化该属性,因此您可以安全地存储状态。
如果 标记属性 存在,也可以将其配置为忽略操作:
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
{
}
这里我们从参数中获取可用的操作描述符,并查找相关方法是否具有 SkipLogging 属性。如果是,请跳过日志记录代码。
我们将日志信息记录到我们的数据库中。 我将为它使用过滤器 (IActionFilter) 功能。 我写了以下 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
}
}
然后我将以下代码添加到 Startup.cs:
services.AddMvc(options => {
options.Filters.Add(typeof(ActionFilter));
});
它工作正常。我在 ActionFilter 中为我的每个方法设置了断点。
但我想忽略大部分方法的记录。 据我了解,我可以用自己的属性来做到这一点。我以前没有使用自己的属性。 好的,我写了以下属性:
public class IgnoreAttribute : Attribute
{
public IgnoreAttribute()
{ }
}
我将属性添加到方法中:
[Ignore]
[HttpGet]
[Route("api/AppovedTransactionAmountByDays/{daysCount}")]
public JsonResult GetAppovedTransactionAmountByDays(int daysCount)
{
var result = daysCount;
return new JsonResult(result);
}
当然,简单的操作是行不通的。
我必须如何更改我的属性或我的 ActionFilter 以忽略方法?
提前致谢。
首先,我强烈建议将您的 ActionFilter
重命名为更具体的名称,例如 LogActionFilterAttribute
(注意——动作过滤器是一个属性)。现在不再使用 options.Filters.Add(...)
全局应用它,而是仅将它应用到您要记录的操作:
// 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(...)
{
//...
}
更新版本
如果您对"Ignore"属性有明确的要求,可以按如下实现。
按照您的方式保留全局配置:
services.AddMvc(options => {
options.Filters.Add(typeof(LoggingActionFilter));
});
LoggingActionFilter
的实现应该改变:
// 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;
}
}
"Ignore" 属性(我将其命名为 SuppressLoggingAttribute
)将如下所示:
// 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)
{
}
}
现在您只需在必要时应用 "ignore" 属性:
[HttpGet("{id}")]
[SuppressLogging]
public string Get(int id)
{
return "value";
}
性能考虑
与@junnas 的回答相反,我的代码没有使用反射 (MethodInfo.CustomAttributes
),因此运行速度更快。
如果有人质疑 Stopwatch
的使用:是的,Stopwatch.StartNew()
会在每次请求时在堆上分配一个新的 Stopwatch
对象。但是将 DateTime
分配给 HttpContext.Items
字典也是一样的,因为它意味着装箱。 DateTime
和 Stopwatch
对象都是 64 位大小,因此在分配方面,DateTime
和 Stopwatch
选项都是相等的。
felix-b 关于命名的注释是一个很好的注释。
我想再做一个说明。以这种方式注册时,不应将状态存储在过滤器中。既然是属性,就只实例化一次!所以你在那里有一个巨大的竞争条件。一种选择是使用:
services.AddMvc(o =>
{
o.Filters.Add(new ServiceFilterAttribute(typeof(LoggingActionFilter)));
});
并将其注册为瞬态:
services.AddTransient<LoggingActionFilter>();
现在每次需要时都会实例化该属性,因此您可以安全地存储状态。
如果 标记属性 存在,也可以将其配置为忽略操作:
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
{
}
这里我们从参数中获取可用的操作描述符,并查找相关方法是否具有 SkipLogging 属性。如果是,请跳过日志记录代码。