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 字典也是一样的,因为它意味着装箱。 DateTimeStopwatch 对象都是 64 位大小,因此在分配方面,DateTimeStopwatch 选项都是相等的。

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 属性。如果是,请跳过日志记录代码。