簡體   English   中英

使用ASP.NET MVC 4 ActionFilter進行審核日志記錄

[英]Audit Logging using ASP.NET MVC 4 ActionFilter

我正在使用ASP.NET MVC 4構建Web應用程序,並通過Entity Framework通過T-SQL數據庫提供數據存儲。 我正在整合審核日志記錄,我想提供一個很好的人類可讀的操作摘要,以便我可以顯示友好的日志視圖,其中包含“ User Bob登錄”,“ User Alice”等清晰的語句更新了文章“ Foo””,等等。

審核記錄當前包括:

  • 圖形用戶界面
  • 時間戳
  • 用戶身份
  • 操作類別(控制器名稱)
  • 動作(動作方法名稱)
  • IsError(布爾值; true表示這是一個錯誤記錄,或者此操作未成功完成)
  • 一系列詳細信息

目前,我的日志記錄使用實現IActionFIlter的自定義屬性; OnActionExecuting()方法記錄嘗試的操作(將URL,參數等序列化到詳細信息blob),然后OnActionExecuted()方法返回並在沒有錯誤的情況下將IsError設置為true,然后將返回的結果或異常附加到錯誤消息和堆棧跟蹤等詳細信息。 我想為描述字符串添加另一列,但是看不到一種整潔的方法。

我最想得到的就是將字符串傳遞給屬性,例如“ User $ user login”,然后讓log方法掃描字符串中的$字符,並將該單詞替換為參數字典中鍵值與之匹配的任何內容。字(減去$字符)。 這有點有限; 例如,如果按ID號存儲文章,則最好的管理方法是“用戶18編輯的文章37”。 沒有真正的方法來獲取用戶名或文章標題。 您不能將實例數據傳遞給屬性,因為它是在編譯時生成的,並且我真的不希望我的日志記錄方法進行各種數據庫調用來獲取該數據,尤其是因為那樣就不可能了(或至少是一個真正的痛苦)擁有一個通用的日志記錄方法。

所有這些的替代方法是擁有一個靜態審核日志記錄類,並調用類似AuditRecord.WriteLog(foo); 在整個地方,也許我可以使用某種描述符類來描述(或繼承自)不同類型的動作,存儲所有參數並根據需要生成描述字符串,但對我而言似乎不太優雅; 我非常喜歡能夠在方法頂部標記[AuditLog]並知道它將被記錄。

我想避免使用大量的條件邏輯,例如在一些大型switch語句中使用控制器和動作名稱來選擇正確的字符串模板。 如果我可以在日志記錄方法中掌握諸如文章標題之類的東西,那就很好。 有沒有一種簡潔的方法可以做到這一點?

最近,我們在工作中就記錄審核歷史記錄以及在新的MVC項目中應用更復雜的安全規則進行了類似的討論。

最后,我們想到的最“優雅”的解決方案是在控制器動作(您的替代方法)中使用方法調用。

例如:

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

    //Rest of action code

}

這使我們能夠靈活地處理所有可能的用例,並允許我們將邏輯包裝成易於使用的方法,以減少代碼重復。

答案可能為時已晚,但是我認為有一個很好的選擇,可以繼續使用動作過濾器屬性並能夠訪問每個請求生命周期對象。

正如anaximander在上面指出的那樣,潛在的問題是屬性由CLR解決,因此它們的生存期無法控制,並且它們與IoC容器的混合效果不佳(按請求實例等使它們成為瞬態)。

通常,在.NET中,每次通過反射解析屬性時,都會創建一個新的屬性實例GetCustomAttribute方法)。

此外, 在MVC / webapi的情況下,操作過濾器屬性會被緩存 ,因此通常只創建一次。

結論是,屬性僅設計為注釋,也就是說,它們應僅包含元數據 (它們是DTO)。 不幸的是,我的理解是MVC和WebApi框架不是以這種方式設計的。 為了將動作過濾器屬性限制為簡單的DTO,並能夠管理圍繞它們的邏輯部分的生命周期,必須采取特殊的手段。

我認為您的用例非常適合Steven van Deursen的精彩文章中提供的解決方案。 它演示了如何將屬性數據與邏輯分離,並且它基於全局注冊的動作過濾器(即所謂的“調度程序”),並將ioc容器作為依賴項。 容器不是靜態解析的 在應用程序初始化時注冊全局過濾器的構造函數中會提供它。 因此,每次執行時,它都會在正在執行的動作上查找任何屬性標記,並解析屬性為通用參數的通用接口。 最終,您沒有使用合並數據和行為的動作過濾器屬性,而是使用了兩個類:一個普通的舊屬性-標記-以及對應於其邏輯副本的通用接口的相應實現。 該容器用於解析通用接口。 如果過濾器依賴於每個請求組件,則可以使用所需的服務創建通用接口的實現。 如果它不依賴於其他服務,但是您需要每個請求的生命周期(例如,測量操作開始和結束之間的時間),那么它也可以完成工作,這要感謝使用容器來解決通用問題接口。 前面提到的文章包含WebApi,MVC和ASP.NET 5的代碼示例。

另外,馬克·西曼(Mark Seemann)也就相同的方法發表了一篇文章

我認為它不能為所有情況提供一個好的解決方案,例如授權過濾器,也許是異常過濾器,但是對我來說,這是許多操作過濾器的最優雅的方式。

更好的方法是在查看數據時格式化該數據,而不是在日志記錄過程中構建這些數據。

如果操作是“登錄”,並且已錄制的用戶可用(您應該這樣做),則可以在查看器中構建該消息。

因此,您記錄了所有原始事件,然后根據這些更具描述性的數據構建“視圖模型”或“讀取模型”。 如果您想更改其描述,甚至可以重新解析所有原始數據。 您可能會記錄很多尚未使用的數據,因此可以稍后在說明中實現它。

IMO,以這種方式在動作內部撒灑方法似乎不是一個好主意,並且控制器或基本控制器上的動作過濾器更干凈。 如果您想這樣做,可以使用AOP(面向方面​​的編程)框架來避免交叉切割。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM