【问题标题】:Audit Logging using ASP.NET MVC 4 ActionFilter使用 ASP.NET MVC 4 ActionFilter 审核日志记录
【发布时间】:2013-02-28 02:43:05
【问题描述】:

我正在使用 ASP.NET MVC 4 构建一个 Web 应用程序,数据存储由 T-SQL 数据库通过实体框架提供。我正在集成审计日志,我想提供一个很好的人类可读的操作摘要,以便我可以提供一个友好的日志视图,其中包含清晰的语句,如“用户 Bob 已登录”、“用户 Alice更新文章'Foo'”等

审计记录目前包括:

  • GUID
  • 时间戳
  • 用户 ID
  • 动作类别(控制器名称)
  • action(动作方法名)
  • IsError(布尔值;true 表示这是错误记录,或者此操作未成功完成)
  • 大量序列化详细信息

目前,我的日志记录使用实现IActionFIlter 的自定义属性; OnActionExecuting() 方法记录尝试的操作(将 URL、参数等内容序列化到详细信息 blob),OnActionExecuted() 方法返回并在没有错误时将 IsError 设置为 true,并附加返回的结果或异常错误消息和堆栈跟踪等详细信息。我想为描述字符串添加另一列,但我看不到一个整洁的方法。

我得到的最远的方法是将字符串传递给属性,例如“用户 $user 已登录”,然后让 log 方法扫描字符串中的 $ 字符,并用参数字典中其键的任何内容替换该单词value 匹配那个词(减去 $ 字符)。这有点有限;例如,如果文章是按 ID 号存储的,那么您可以管理的最好是“用户 18 编辑文章 37”。没有真正的方法可以获取用户名或文章标题;您不能将实例数据传递给属性,因为它是在编译时烘焙的,而且我真的不希望我的日志记录方法进行各种数据库调用来获取那种数据,尤其是因为它变得不可能(或至少是真正的痛苦)拥有一个通用的日志记录方法。

所有这一切的替代方法是拥有一个静态审计日志记录类并在所有地方调用类似AuditRecord.WriteLog(foo); 的东西,也许可以使用某种描述符类我可以使用(或继承自)来描述不同类型的操作,存储所有参数并根据需要生成描述字符串,但对我来说似乎不太优雅;我真的很喜欢能够在方法顶部标记 [AuditLog] 并知道它会被记录。

我想避免大量的条件逻辑,比如在一些大的 switch 语句中使用控制器和动作名称来选择正确的字符串模板。如果我可以在日志记录方法中获取诸如文章标题之类的东西,那就没问题了。有没有一种简洁、简单的方法来做到这一点?

【问题讨论】:

    标签: c# entity-framework asp.net-mvc-4 audit-logging


    【解决方案1】:

    我们最近在工作中就记录审计历史和在我们的新 MVC 项目中应用更复杂的安全规则进行了类似的讨论。

    最后,我们想出的最“优雅”的解决方案是在控制器操作中调用方法(您的替代方法)。

    例如:

    [HttpPost]
    public ActionResult CreateItem(Item item)
    {
        //Simplified
        CheckSecurity(SecurityTypes.ItemCreation);
        LogActivity("Created an item");
    
        //Rest of action code
    
    }
    

    这使我们能够灵活地考虑所有可能的用例,并使我们能够将逻辑包装成简单易用的方法以减少代码重复。

    【讨论】:

    • 我可能最终会使用它。实际上,我有一些我很满意的代码。有一个 LogEvent() 方法接受一个描述符对象,但该描述符有一些时髦的动态内容,允许详细的描述字符串而没有太多重复的代码。我只是喜欢提取日志代码的想法;关注点分离等等。我可能会将其拆分为调试日志记录和审核日志记录,并使用ActionFilter 方法进行调试 - 开发人员将阅读这些内容,他们最想要的是详细信息,而不是友好的消息。
    • 我们同样想要提取它,但遇到了与您相同的问题,所以只是退回到在每个操作中使用方法!鉴于我们也处于最后期限... :)
    【解决方案2】:

    现在回答可能已经晚了,但我认为有一个很好的选择来继续使用操作过滤器属性并能够访问每个请求的生命周期对象。

    正如上面的 anaximander 所述,根本问题是属性由 CLR 解析,因此它们的生命周期无法控制,并且它们不能与 IoC 容器很好地混合(使它们瞬态,每个请求实例等)。

    通常,在 .NET 中每次通过反射解析属性时都会创建一个新实例GetCustomAttribute 方法)。

    此外,在 MVC/webapi 的情况下,动作过滤器属性被缓存,因此它们通常只创建一次。

    结论是属性被设计为仅用于注释,换句话说,它们应该只包含元数据(它们是 DTO)。不幸的是,我的理解是 MVC 和 WebApi 框架不是这样设计的。要将操作过滤器属性限制为简单的 DTO,并能够管理围绕它们的逻辑部分的生命周期,必须采取特殊措施。

    我认为您的用例非常适合 Steven van Deursen 的great article 中提供的解决方案。它演示了如何将属性数据与逻辑分离,它基于全局注册的操作过滤器,即所谓的“调度程序”,以 ioc 容器作为依赖项。 容器不是静态解析的。它是在应用程序初始化时注册的全局过滤器的构造函数中提供的。 因此,每次执行时,它都会在正在执行的操作上查找任何属性标记,并解析一个通用接口,其中属性是通用参数。您最终会使用两个类,而不是使用合并数据和行为的操作过滤器属性:一个普通的旧属性 - 标记 - 以及其逻辑对应物的通用接口的相应实现。容器用于解析通用接口。如果您的过滤器依赖于每个请求的组件,您可以使用您需要的服务创建通用接口的实现。如果它不依赖于其他服务,但您需要每个请求的生命周期(例如测量动作开始和结束之间的时间),它也可以完成这项工作,这要归功于使用容器来解决泛型界面。上述文章包含 WebApi、MVC 和 ASP.NET 5 的代码示例。

    此外,马克·西曼 (Mark Seemann) 也以同样的方式制作了 an article

    我认为它不能为所有情况提供一个好的解决方案,比如授权过滤器和异常过滤器,但对我来说,这是许多操作过滤器最优雅的方式。

    【讨论】:

      【解决方案3】:

      更好的方法是在查看这些数据时对其进行格式化,而不是在记录过程中构建这些内容。

      如果操作是“登录”,并且记录的用户可用(您应该这样做),那么您在查看器中构建该消息。

      因此,您记录所有原始事件,然后根据这些更具描述性的数据构建“视图模型”或“读取模型”。如果您想更改其描述,这甚至可以让您重新解析所有原始数据。您可以记录大量尚未使用的数据,以便稍后在描述中实现它。

      IMO,以这种方式在动作中散布方法似乎不是一个好主意,并且控制器或基本控制器上的动作过滤器更干净。如果你想这样做,你可以使用 AOP(面向方面​​的编程)框架来避免横切......

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2016-03-23
        • 2011-03-18
        • 2019-12-01
        • 2010-12-23
        • 1970-01-01
        • 2017-06-26
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多