在前面的Part3中,我介紹Policy Injection模組中內建的Call Handler的使用方法,今天則繼續介紹Call Handler——Custom Call Handler,通過建立Custom Call Handler來實現項目中的使用者動作記錄的記錄,具體的代碼可以在項目中EntLib.Helper項目下找到,如:
本文將從Custom Call Handler兩種方式來介紹:Attribute方式和Configuration方式。
一、核心代碼
建立Custom Call Handler則需要有以下幾個步驟:
1、建立一個類實現介面ICallHandler。
2、根據具體需求建立對應Attribute類或為Custom Call Handler實現特性[ConfigurationElementType(typeof(CustomCallHandlerData))]
首先來介紹下具體的核心代碼,由於我是要實現使用者的動作記錄,則需要對使用者的對資料的增刪改以及一些特殊的操作進行記錄,如:登入,
1、首先需要建立一張表用於存放使用者操作記錄:
CREATE TABLE [dbo].[UserLog]([ID] [int] IDENTITY(1,1) NOT NULL,--主鍵[StudentId] [int] NOT NULL,--對應學生ID[Message] [nvarchar](256) NOT NULL,--操作訊息[LogDate] [datetime] NOT NULL,--記錄時間 CONSTRAINT [PK_UserLog] PRIMARY KEY CLUSTERED ([ID] ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]
2、建立一個名為UserLogCallHandler的類來實現介面ICallHandler,實現其中的方法Invoke(具體的攔截操作方法)和屬性Order,具體代碼如下(關鍵處我都寫好注釋了)
using System;using System.Collections.Generic;using System.Collections.Specialized;using System.Data;using System.Data.Common;using System.Linq;using System.Text;using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;using Microsoft.Practices.EnterpriseLibrary.Data;using Microsoft.Practices.Unity.InterceptionExtension;namespace EntLibStudy.Helper.EntLibExtension.PolicyInjectionExtension{ [ConfigurationElementType(typeof(CustomCallHandlerData))] public class UserLogCallHandler : ICallHandler { /// <summary> /// 建構函式,此處不可省略,否則會導致異常 /// </summary> /// <param name="attributes">設定檔中所配置的參數</param> public UserLogCallHandler(NameValueCollection attributes) { //從設定檔中擷取key,如不存在則指定預設key this.Message = String.IsNullOrEmpty(attributes["Message"]) ? "" : attributes["Message"]; this.ParameterName = String.IsNullOrEmpty(attributes["ParameterName"]) ? "" : attributes["ParameterName"]; } /// <summary> /// 建構函式,此建構函式是用於Attribute調用 /// </summary> /// <param name="message">訊息</param> /// <param name="parameterName">參數名</param> public UserLogCallHandler(string message, string parameterName) { this.Message = message; this.ParameterName = parameterName; } /// <summary> /// 實現ICallHandler.Invoke方法,用於對具體攔截方法做相應的處理 /// </summary> /// <param name="input"></param> /// <param name="getNext"></param> /// <returns></returns> public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { //檢查參數是否存在 if (input == null) throw new ArgumentNullException("input"); if (getNext == null) throw new ArgumentNullException("getNext"); //開始攔截,此處可以根據需求編寫具體商務邏輯代碼 //調用具體方法 var result = getNext()(input, getNext); //判斷所攔截的方法傳回值是否是bool類型, //如果是bool則判斷傳回值是否為false,false:表示調用不成功,則直接返回方法不記錄日誌 if (result.ReturnValue.GetType() == typeof(bool)) { if (Convert.ToBoolean(result.ReturnValue) == false) { return result; } } //如果調用方法沒有出現異常則記錄動作記錄 if (result.Exception == null) { //擷取當前登入的使用者名稱,從cookies中擷取,如果採用的session記錄,則更改為從session中擷取 var uid = Utils.GetCookies("sid"); //如果未登入則拋出異常 if (String.IsNullOrEmpty(uid)) throw new Exception("使用者未登入!"); //操作附加訊息,用於擷取操作的記錄相關標識 var actionMessage = ""; object para = null; //判斷調用方法的主要參數名是否為空白,不為空白則從攔截的方法中擷取參數對象 if (String.IsNullOrEmpty(this.ParameterName) == false) { para = input.Inputs[this.ParameterName]; } //判斷參數對象是否為null,不為null時則擷取參數標識 //此處對應著具體參數的ToString方法,我已經在具體類中override了ToString方法 if (para != null) { actionMessage = " 編號:[" + para.ToString() + "]"; } //插入動作記錄 Database db = DBHelper.CreateDataBase(); StringBuilder sb = new StringBuilder(); sb.Append("insert into UserLog(StudentId,Message,LogDate) values(@StudentId,@Message,@LogDate);"); DbCommand cmd = db.GetSqlStringCommand(sb.ToString()); db.AddInParameter(cmd, "@StudentId", DbType.Int32, uid); db.AddInParameter(cmd, "@Message", DbType.String, this.Message + actionMessage); db.AddInParameter(cmd, "@LogDate", DbType.DateTime, DateTime.Now); db.ExecuteNonQuery(cmd); } //返回方法,攔截結束 return result; } public string Message { get; set; } public string ParameterName { get; set; } private int _order = 0; public int Order { get { return _order; } set { _order = value; } } }}
這段代碼主要部分就是具體的Invoke方法實現,這個方法有2個參數:
input,這個參數中封裝了已攔截的方法、方法的參數等有用的資訊
getNext,一個委託,用於調用攔截的方法,通過這個委託我們可以很好的控制我們需要在攔截了具體方法後如何進行具體的商務邏輯操作。
通過getNext()(input, getNext); 這段代碼即可完成對方法的調用,這樣可以根據具體需求決定在調用方法前還是方法後進行具體操作。
由於我這邊是要實現一個使用者操作記錄,那麼我要知道一些具體的資訊:是誰在什麼時候對什麼資料做了操作,這邊我需要擷取3個參數:具體的操作人、操作的資料及具體描述。
首先來看下第一個參數:
◆具體的操作人,由於這個項目採用的是cookies來記錄當前的登入使用者,所以我可以直接從cookies中擷取當前登入的人,具體可以查看代碼69-71行。
◆操作的資料,這邊我在這個Call Handler中建立了一個ParameterName屬性用來指定記錄所攔截的方法中存放所操作資料的參數名,具體可以查看代碼74-86行。
由於指定了具體的參數名,我們則需要根據參數擷取具體資料值,我們來看下增刪改的方法簽名:
int Add(Student student);
bool Update(Student student);
bool Delete(int id);
可以看到,我們都可以從這3個方法擷取到使用者具體操作的資料標識,如Student.Id和id,這樣我們只需變通一下,在具體的類中,如Student中,重寫ToString方法,返回具體的ID即可,代碼如下:
public override string ToString(){ return this.Id.ToString();}
這樣,我們在Call Handler中我們就可以根據參數名擷取到具體操作的資料了(如果需要詳細描述具體的資料的話則需要更複雜的設計了,這邊就不深入了),代碼如下:
//操作附加訊息,用於擷取操作的記錄相關標識var actionMessage = "";object para = null;//判斷調用方法的主要參數名是否為空白,不為空白則從攔截的方法中擷取參數對象if (String.IsNullOrEmpty(this.ParameterName) == false){ para = input.Inputs[this.ParameterName];}//判斷參數對象是否為null,不為null時則擷取參數標識//此處對應著具體參數的ToString方法,我已經在具體類中override了ToString方法if (para != null){ actionMessage = " 編號:[" + para.ToString() + "]";}
◆具體描述,這個我也是建立一個Message資料,用於存放操作的具體描述。
◆特殊情況,當然動作記錄也不可能就僅僅增刪改3種情況,就比如登入,登出,這種情況則只需指定具體的訊息即可,參數名無需指定,如果還有更加特殊的情況則需要根據具體需求來更改這邊的設計,我這邊只是給出個最基本的。
二、Attribute實現
在完成了核心代碼後,我們則可以根據需求建立Attribute攔截還是Configuration攔截了。
實現Attribute攔截,需要建立一個類,實現HandlerAttribute類,實現其中的CreateHandler方法,用於調用具體的Call Handler方法,具體代碼如下:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Microsoft.Practices.Unity;using Microsoft.Practices.Unity.InterceptionExtension;namespace EntLibStudy.Helper.EntLibExtension.PolicyInjectionExtension{ [AttributeUsage(AttributeTargets.Method)] public class UserLogCallHandlerAttribute : HandlerAttribute { public UserLogCallHandlerAttribute(string message, string ParameterName) { this.Message = message; this.ParameterName = ParameterName; } public string Message { get; set; } public string ParameterName { get; set; } public override ICallHandler CreateHandler(IUnityContainer container) { //建立具體Call Handler,並調用 UserLogCallHandler handler = new UserLogCallHandler(this.Message, this.ParameterName); return handler; } }}
這個屬性類別就比較簡單了,不過還需要在Call Handler中進行處理,增加一個建構函式,接收從Attribute中傳遞過來的參數:
/// <summary>/// 建構函式,此建構函式是用於Attribute調用/// </summary>/// <param name="message">訊息</param>/// <param name="parameterName">參數名</param>public UserLogCallHandler(string message, string parameterName){ this.Message = message; this.ParameterName = parameterName;}
三、Configuration方式
如果要實現可以通過企業庫組態工具進行配置Custom Call Handler的話,則需要對Call Handler增加一個特性:
[ConfigurationElementType(typeof(CustomCallHandlerData))]
然後新增一個建構函式
/// <summary>/// 建構函式,此處不可省略,否則會導致異常/// </summary>/// <param name="attributes">設定檔中所配置的參數</param>public UserLogCallHandler(NameValueCollection attributes){ //從設定檔中擷取key,如不存在則指定預設key this.Message = String.IsNullOrEmpty(attributes["Message"]) ? "" : attributes["Message"]; this.ParameterName = String.IsNullOrEmpty(attributes["ParameterName"]) ? "" : attributes["ParameterName"];}
完成以上2步我們就可以通過企業庫組態工具進行配置了,見:
四、具體使用
在完成了Call Handler的代碼編寫和登入攔截配置後,我們就可以進行使用了,我這邊更改了項目的結構,建立了一個IBLL的介面層,現有的BLL層的類則實現IBLL層中介面,而且由於Policy Injection模組要實現AOP,則具體類必須繼承自MarshalByRefObject或實現一個介面(如果不清楚可以查看Part1),所以為了項目的各模組解耦、方便Policy Injection對具體類的建立和未來Unity介紹做鋪墊則建立了IBLL層。(具體可以參看項目代碼)
由於建立了IBLL層,則展示層的代碼則需要發生變化,所有BLL層建立都需要通過PolicyInjection.Create方法來建立,具體代碼如下:
IStudentManage studentBll = PolicyInjection.Create<StudentManage, IStudentManage>();
這樣,當我們運行代碼後,進入資料庫查看就可以看,動作記錄已經被記錄下來了。
上面說的是通過Configuration方式來進行動作記錄記錄,如果我們想通過Attribute方式來記錄日誌訊息,則需要到具體的BLL層進行操作,代碼如下:
[UserLogCallHandler("更新學生資訊","student")]public bool Update(Student student){ return studentService.Update(student);}
注意:這邊需要為項目引用Microsoft.Practices.Unity.Interception,因為Call Handler的Attribute是繼承自HandlerAttribute,這個HandlerAttribute就是存放於Microsoft.Practices.Unity.Interception,否則自訂的Call Handler Attribute將無法顯示出來。
這樣,更新下學生資訊後,我們可以就可以看到具體的動作記錄了,見:
以上就是本文的所有內容,主要介紹了如何通過Custom Call Handler實現使用者動作記錄記錄,如果有什麼不對,歡迎大家指出,謝謝:)
至此,Policy Injection模組的介紹也結束了,下面將開始介紹企業庫中使用最廣泛的IOC容器——Unity,敬請期待!
微軟企業庫5.0 學習之路系列文章索引:
第一步、基本入門
第二步、使用VS2010+Data Access模組建立多資料庫專案
第三步、為項目加上異常處理(採用自訂擴充方式記錄到資料庫中)
第四步、使用緩衝提高網站的效能(EntLib Caching)
第五步、介紹EntLib.Validation模組資訊、驗證器的實現層級及內建的各種驗證器的使用方法——上篇
第五步、介紹EntLib.Validation模組資訊、驗證器的實現層級及內建的各種驗證器的使用方法——中篇
第五步、介紹EntLib.Validation模組資訊、驗證器的實現層級及內建的各種驗證器的使用方法——下篇
第六步、使用Validation模組進行伺服器端資料驗證
第七步、Cryptographer加密模組簡單分析、自訂加密介面及使用—上篇
第七步、Cryptographer加密模組簡單分析、自訂加密介面及使用—下篇
第八步、使用Configuration Setting模組等多種方式分類管理企業庫配置資訊
第九步、使用PolicyInjection模組進行AOP—PART1——基本使用介紹
第九步、使用PolicyInjection模組進行AOP—PART2——自訂Matching Rule
第九步、使用PolicyInjection模組進行AOP—PART3——內建Call Handler介紹
第九步、使用PolicyInjection模組進行AOP—PART4——建立自訂Call Handler實現使用者動作記錄記錄
第十步、使用Unity解耦你的系統—PART1——為什麼要使用Unity?
第十步、使用Unity解耦你的系統—PART2——瞭解Unity的使用方法(1)
第十步、使用Unity解耦你的系統—PART2——瞭解Unity的使用方法(2)
第十步、使用Unity解耦你的系統—PART2——瞭解Unity的使用方法(3)
第十步、使用Unity解耦你的系統—PART3——依賴注入
第十步、使用Unity解耦你的系統—PART4——Unity&PIAB
擴充學習:
擴充學習篇、庫中的依賴關係注入(重構 Microsoft Enterprise Library)[轉]
原始碼下載:點我下載
注意:
1、MSSQL資料庫在DataBase目錄下(需要自行附加資料庫),SQLite資料庫在Web目錄的App_Data下,由於考慮到項目的大小,所以每個項目的BIN目錄都已經刪除,如出現無法產生項目請自行添加相關企業庫的DLL。
2、由於微軟企業庫5.0 學習之路這個系列我是準備以一個小型項目的形式介紹企業庫的各模組,所以原始碼會根據系列文章的更新而更新,所以原始碼不能保證與文章中所貼代碼相同。
3、項目開發環境為:VS2010+SQL2005。
4、系統管理員帳戶:admin
密碼:admin