1、記住這個單詞的意思:Interception(攔截)
2、首先說一下原理和背景
原理:所謂的AOP就是面向切面編程,這里不多說,百度搜索。
目的:個人認為是為了解耦,部分代碼跟業務代碼分離,業務代碼里面不摻雜其它功能,比如:記錄異常、記錄操作日志。
背景:項目基本功能已完成,產品要求記錄用戶的操作日志,新增的時候記錄某人在某時做了某事(包括詳細的信息,比如新增了哪些字段或者修改了哪些字段)。於是着手在業務代碼里寫了大量的關於記錄操作日志的代碼,怎么看怎么別扭,像是被XX了的感覺。
3、解決辦法
針對上述背景,於是想到了在業務邏輯方法上面加個特性,用以記錄操作日志,這樣代碼就變的非常干凈。而剛好項目里用到了DI,是微軟的Microsoft.Practices.Unity組件。於是在網上開始找資料,最終通過不斷的閱讀別人的代碼和反復試驗,總算實現了。因為Microsoft.Practices.Unity組件本身就自帶攔截功能。這就是為什么一開始就說攔截單詞(Interception)的原因。
其實我是想實現這樣的代碼:
namespace Business { public interface IUserBusiness { string Speak(); string Run(); [OperationLog("UserName,Password,Id", "UserRepository")] User Create(User user, Authority authority); [OperationLog("", "UserRepository")] User Get(long id); } }
通過在方法上面添加特性,實現記錄用戶操作日志的功能。
OperationLog特性介紹:
第一個參數表示我想記錄哪些字段
第二個參數表示我將采用哪個Repository來根據ID獲取原始值。
4、具體代碼
准備好需要的Dll(2.1.505.0版本):
Microsoft.Practices.Unity.Configuration.dll
Microsoft.Practices.Unity.dll
Microsoft.Practices.Unity.Interception.Configuration.dll
Microsoft.Practices.Unity.Interception.dll
DI我采用的是在配置文件里面做的,如下:
unity.di.infrastructure.config
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection,Microsoft.Practices.Unity.Configuration"/> </configSections> <unity xmlns="http://schemas.microsoft.com/practices/2010/unity"> <!--引用程序集--> <assembly name="Business" /> <assembly name="Repository" /> <!--引用命名空間--> <namespace name="Business" /> <namespace name="Repository" /> <container> <register type="IUserBusiness" mapTo="UserBusiness" /> <register type="IUserRepository" mapTo="UserRepository" /> </container> </unity> </configuration>
ioc幫助類:
namespace AopDemo { /// <summary> /// 依賴注入幫助類 /// 創建人:君爺 /// 創建時間:2015-10-23 /// </summary> public class IocHelper { /// <summary> /// 讀取接口和實現類的XML配置文件,並向MVC控制器注入自定義的ControllerFactory /// </summary> public void Init() { try { IUnityContainer container = new UnityContainer(); //加載Ioc配置文件,讀取所有Ioc接口和實現 var fileMap = new ExeConfigurationFileMap { ExeConfigFilename = HttpContext.Current.Server.MapPath("~/app_data/unity.di.infrastructure.config") }; Configuration configuration = ConfigurationManager.OpenMappedExeConfiguration(fileMap, ConfigurationUserLevel.None); UnityConfigurationSection section = (UnityConfigurationSection)configuration.GetSection("unity"); container.LoadConfiguration(section); //AOP container.AddNewExtension<Interception>(); container.RegisterType<IUserBusiness, UserBusiness>().Configure<Interception>().SetInterceptorFor<IUserBusiness>(new InterfaceInterceptor()); //向Mvc請求的上下文注入 Unity控制器工廠 IControllerFactory controllerFactory = new UnityControllerFactory(container); ControllerBuilder.Current.SetControllerFactory(controllerFactory); } catch (Exception ex) { throw ex; } } } /// <summary> /// 依賴工廠 /// </summary> public class UnityControllerFactory : DefaultControllerFactory { private readonly IUnityContainer container; /// <summary> /// 構造方法 /// </summary> /// <param name="container"></param> public UnityControllerFactory(IUnityContainer container) { //要做異常處理 this.container = container; } /// <summary> /// 根據請求的上下文實例化控制器 /// </summary> /// <param name="requestContext">請求上下文</param> /// <param name="controllerType">控制器類型</param> /// <returns></returns> protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { //這里把Controller實例注冊到了unity容器 try { IController icontroller = container.Resolve(controllerType) as IController; return icontroller; } catch (Exception ex) { return null; } } } }
Global.asax的Application_Start方法添加依賴注入容器
public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); //IOC var iocFactory = new IocHelper(); iocFactory.Init(); } }
然后添加接口記錄操作日志的Handler和Attribute
public class OperationLogHandler : ICallHandler { private string _fields; private string _repository; public OperationLogHandler(string fields, string repository) { this._fields = fields; this._repository = repository; } public int Order { get; set; }//這是ICallHandler的成員,表示執行順序 public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { //定義存放原始值和新值的變量 BaseModel oldValue; BaseModel newValue; //獲取類型為BaseModel的參數 object model = null; object authority = null; foreach (var argument in input.Arguments) { if (argument.GetType().BaseType == typeof(BaseModel)) { model = argument; } else if (argument.GetType() == typeof(Authority)) { authority = argument; } } if (model == null) { throw new Exception("沒有找到BaseModel類型,攔截導彈失敗。"); } //獲取實體的Id屬性的值 var properties = model.GetType().GetProperties(); var idProperty = properties.Where(m => m.Name == "Id").FirstOrDefault(); long id = (long)idProperty.GetValue(model, null); //根據ID從數據庫獲取原先的值。其中Assembly.Load("Repository")中的參數是程序集的dll文件名稱,.CreateInstance()中的參數是程序集中的命名空間和類名。 var repository = Assembly.Load("Repository").CreateInstance("Repository." + this._repository) as IRepository; oldValue = repository.Get(id); //獲取新增后返回的實體的值 var returnValue = getNext()(input, getNext); newValue = returnValue.ReturnValue as BaseModel; //這樣就可以獲取到插入前和插入后的數據了;比較兩個實體,如果不同就記錄下來,插入日志表 var arr = this._fields.Split(','); Dictionary<string, object> dic = new Dictionary<string, object>(); foreach (var field in arr) { var property = properties.Where(m => m.Name == field).FirstOrDefault(); var oldFieldValue = property.GetValue(oldValue, null); var newFieldValue = property.GetValue(newValue, null); var oldHashCode = oldFieldValue.GetHashCode(); var newHashCode = newFieldValue.GetHashCode(); if (oldHashCode != newHashCode) { dic.Add(field, newFieldValue); } } return returnValue; } }
public class OperationLogAttribute : HandlerAttribute { private string _fields; private string _repository; public OperationLogAttribute(string fields, string repository) { this._fields = fields; this._repository = repository; } public override ICallHandler CreateHandler(IUnityContainer container) { return new OperationLogHandler(this._fields, this._repository);//返回MyHandler } }
這樣通過配置文件和在代碼里設置AOP映射關系,我們就可以輕松的在方法上添加特性。
[OperationLog("UserName,Password,Id", "UserRepository")] User Create(User user, Authority authority);
當然中間有些其它的都省略了,關鍵是這些代碼。
代碼之后補全