使用ASP.NET MVC 4 ActionFilter进行审核日志记录

时间:2013-03-12 14:16:52

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

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

审计记录目前包括:

  • GUID
  • 时间戳
  • 用户ID
  • 行动类别(控制器名称)
  • 动作(动作方法名称)
  • IsError(boolean; true表示这是错误记录,或者此操作未成功完成)
  • blob of serialized details

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

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

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

我想避免大量的条件逻辑,比如在一些大的switch语句中使用控制器和动作名来选择正确的字符串模板。如果我能在日志记录方法中掌握文章标题等内容,那就没关系了。有一个简洁,简单的方法吗?

3 个答案:

答案 0 :(得分:1)

我们最近在工作中进行了类似的讨论,包括记录审核历史记录以及在我们的新MVC项目中应用更复杂的安全规则。

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

例如:

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

    //Rest of action code

}

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

答案 1 :(得分:0)

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

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

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

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

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

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

此外,马克·西曼(Mark Seemann)对an article采取了相同的方法。

我认为它不能为所有情况提供良好的解决方案,例如授权过滤器和可能的异常过滤器,但对我而言,它是许多动作过滤器最优雅的方式。

答案 2 :(得分:0)

更好的方法是在查看数据时格式化这些数据,而不是在日志记录过程中构建这些数据。

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

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

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