1

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

审计记录目前包括:

  • 图形用户界面
  • 时间戳
  • 用户身份
  • 动作类别(控制器名称)
  • 动作(动作方法名称)
  • IsError(布尔值;true 表示这是错误记录,或者此操作未成功完成)
  • 序列化细节的blob

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

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

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

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

4

3 回答 3

1

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

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

例如:

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

    //Rest of action code

}

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

于 2013-03-12T16:12:02.977 回答
0

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

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

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

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

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

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

此外,Mark Seemann 也发表了一篇关于相同方法的文章。

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

于 2015-10-10T21:29:09.550 回答
0

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

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

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

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

于 2015-10-10T21:51:09.883 回答