【问题标题】:ASP.NET Core Filters - ignore for defined methodsASP.NET Core 过滤器 - 忽略已定义的方法
【发布时间】:2018-07-10 17:56:47
【问题描述】:

我们在我们的数据库中实现了一个日志信息。 我将为它使用过滤器(IActionFilter)功能。 我写了以下课程:

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 以忽略方法?

提前致谢。

【问题讨论】:

    标签: c# asp.net asp.net-core asp.net-core-mvc custom-attributes


    【解决方案1】:

    felix-b 关于命名的注释很不错。

    我想再做一个笔记。以这种方式注册时,不应将状态存储在过滤器中。由于是属性,所以只实例化一次!所以你在那里有一个巨大的比赛条件。一种选择是使用:

    services.AddMvc(o =>
    {
        o.Filters.Add(new ServiceFilterAttribute(typeof(LoggingActionFilter)));
    });
    

    并将其注册为瞬态:

    services.AddTransient<LoggingActionFilter>();
    

    现在每次需要时都会实例化该属性,因此您可以安全地存储状态。

    如果存在 marker 属性,也可以将其配置为忽略该操作:

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

    【讨论】:

    • @junnas 感谢您注意到比赛条件! (我忽略了它)虽然我不得不不同意属性只实例化一次——每次查询类型时都会得到一个新实例。但是属性对象可能会被查询它们的代码缓存。所以你是对的,让属性保持可变状态是一种非常糟糕的做法。
    • 是的,在这种情况下,由于您实际上将它作为类型提供给 MVC,它负责实例化它。但我相信它仍然只创建一次。
    【解决方案2】:

    首先,我强烈建议将您的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;
        }
    }
    

    “忽略”属性(我将其命名为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)
        {
        }
    }
    

    现在您只需在必要时应用“忽略”属性:

    [HttpGet("{id}")]
    [SuppressLogging]
    public string Get(int id)
    {
        return "value";
    }
    

    性能考虑

    与@junnas 的回答相比,我的代码不使用反射 (MethodInfo.CustomAttributes),因此它的运行速度更快。

    如果有人质疑Stopwatch 的使用:是的,Stopwatch.StartNew() 会在每个请求的堆上分配一个新的Stopwatch 对象。但是将DateTime 分配给HttpContext.Items 字典的作用相同,因为它意味着装箱。 DateTimeStopwatch 对象都是 64 位大小,因此在分配方面,DateTimeStopwatch 选项是相等的。

    【讨论】:

    • 感谢您的回答。不幸的是,我得到了主管的强烈指示——使用一种忽略属性。
    • 哦,我明白了。在答案中实施了一个附加版本。
    • 请注意,这种方法有一个缺点,如果您的操作过滤器使管道短路(例如某种请求验证),它就不起作用,因为过滤器的执行顺序(全局过滤器在控制器过滤器之前执行)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-01-02
    • 2018-04-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多