從本章開始對框架的講敘開始進入核心類庫的講解,前面都是對框架外在功能講解,讓人有個整體的概念,知道包含哪些功能與對系統開發有什么幫助。以后多章都是講解核心類庫的,講解的方式基本按照代碼的目錄結構,這樣閱讀代碼的時候也可以針對性看;還有就是為了更好理解文章中的內容把目前還不夠完善的源代碼發布,這個版本Winform部分基本可以直接運行,而Web部分與WCF部分的控制器代碼沒有完成,所以暫時還運行不起來,不過這並不影響學習核心類庫,再就是盡快在下個版本發布一個完整版本給大家;
EnterpriseFrameWork框架源代碼V1.0下載:http://pan.baidu.com/s/1pJsMLlx
本文要點:
1.EntLib配置文件
2.對EntLib進行封裝
3.EntLib實現數據庫訪問
4.EntLib實現錯誤日志記錄
5.EntLib實現緩存管理
6.EntLib實現AOP與對象創建
核心類庫EFWCoreLib目錄結構
EnterpriseFrameWork框架的底層功能是使用微軟企業庫(EntLib)來實現的,包括使用EntLib的The Data Access Application Block實現數據庫訪問,The Caching Application Block進行緩存管理,The Exception Handling Application Block進行異常處理,The Logging Application Block進行日志記錄,Unity Dependency Injection and Interception依賴注入容器進行對象映射;在這些功能之上再進行了一次封裝,讓我們再編寫代碼過程中更簡單的運用,還有就算EntLib以后升級也不需要修改系統中的代碼;
微軟企業庫的一些學習資料:
Enterprise Library 5.0.msi安裝包:http://pan.baidu.com/s/1gdnDz7l
Enterprise Library 5.0說明文檔:http://pan.baidu.com/s/1pJjtvCZ
CHM版說明文檔:http://pan.baidu.com/s/1c0rhtba
學習實例代碼:http://pan.baidu.com/s/1dDthk3Z
Enterprise Library 5.0源代碼:http://pan.baidu.com/s/1mgKDot6
黃聰:Enterprise Library 5.0 系列教程:http://www.cnblogs.com/huangcong/archive/2010/06/08/1753988.html
一、EntLib配置文件
我們先看web項目Web.config中對EntLib的配置,Winform項目中的是App.config
其中標記的紅色部分指定了EntLib的完整配置文件Entlib.config,

<configuration> <configSections> <section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" /> <section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" /> <section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" /> <section name="cachingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Caching.Configuration.CacheManagerSettings, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" requirePermission="true" /> </configSections> <dataConfiguration defaultDatabase="SQL2005" /> <connectionStrings> <add name="SQL2005" connectionString="Data Source=.;Initial Catalog=3yxx_Cssd;User ID=sa;pwd=1;" providerName="System.Data.SqlClient" /> </connectionStrings> <exceptionHandling> <exceptionPolicies> <add name="HISPolicy"> <exceptionTypes> <add name="All Exceptions" type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" postHandlingAction="NotifyRethrow"> <exceptionHandlers> <add name="Logging Exception Handler" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" logCategory="FileLog" eventId="100" severity="Error" title="Enterprise Library Exception Handling" formatterType="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.TextExceptionFormatter, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling" priority="0" /> </exceptionHandlers> </add> </exceptionTypes> </add> </exceptionPolicies> </exceptionHandling> <loggingConfiguration name="" tracingEnabled="true" defaultCategory="ConsoleLog"> <listeners> <add listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.CustomTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" type="EFWCoreLib.CoreFrame.EntLib.Log.TraceListeners.ConsoleTraceListener, EFWCoreLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" traceOutputOptions="None" name="ConsoleTraceListener" /> <add listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.CustomTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" type="EFWCoreLib.CoreFrame.EntLib.Log.TraceListeners.FileTraceListener, EFWCoreLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="FileTraceListener" /> <add listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.CustomTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" type="EFWCoreLib.CoreFrame.EntLib.Log.TraceListeners.DatabaseTraceListener, EFWCoreLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="DatabaseTraceListener" /> <add name="Email Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.EmailTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.EmailTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" toAddress="to@example.com" fromAddress="from@example.com" filter="Off" /> <add name="Event Log Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FormattedEventLogTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FormattedEventLogTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" source="Enterprise Library Logging" formatter="Text Formatter" log="" machineName="." traceOutputOptions="None" filter="Error" /> <add name="Flat File Trace Listener" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" fileName="trace.log" formatter="Text Formatter" /> </listeners> <formatters> <add type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" template="Timestamp: {timestamp}{newline}
Message: {message}{newline}
Category: {category}{newline}
Priority: {priority}{newline}
EventId: {eventid}{newline}
Severity: {severity}{newline}
Title:{title}{newline}
Machine: {localMachine}{newline}
App Domain: {localAppDomain}{newline}
ProcessId: {localProcessId}{newline}
Process Name: {localProcessName}{newline}
Thread Name: {threadName}{newline}
Win32 ThreadId:{win32ThreadId}{newline}
Extended Properties: {dictionary({key} - {value}{newline})}" name="Text Formatter" /> <add type="EFWCoreLib.CoreFrame.EntLib.Log.Formatters.ZhyTextFormatter, EFWCoreLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" name="ZhyTextFormatter" /> </formatters> <categorySources> <add switchValue="All" name="ConsoleLog"> <listeners> <add name="ConsoleTraceListener" /> </listeners> </add> <add switchValue="All" name="FileLog"> <listeners> <add name="Flat File Trace Listener" /> </listeners> </add> <add switchValue="All" name="DatabaseLog"> <listeners> <add name="DatabaseTraceListener" /> </listeners> </add> <add switchValue="All" name="EmailLog"> <listeners> <add name="Email Trace Listener" /> </listeners> </add> </categorySources> <specialSources> <allEvents switchValue="All" name="All Events" /> <notProcessed switchValue="All" name="Unprocessed Category" /> <errors switchValue="All" name="Logging Errors & Warnings"> <listeners> <add name="Event Log Listener" /> </listeners> </errors> </specialSources> </loggingConfiguration> <cachingConfiguration defaultCacheManager="Cache Manager"> <cacheManagers> <add name="Cache Manager" type="Microsoft.Practices.EnterpriseLibrary.Caching.CacheManager, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" expirationPollFrequencyInSeconds="60" maximumElementsInCacheBeforeScavenging="1000" numberToRemoveWhenScavenging="10" backingStoreName="NullBackingStore" /> </cacheManagers> <backingStores> <add type="Microsoft.Practices.EnterpriseLibrary.Caching.BackingStoreImplementations.NullBackingStore, Microsoft.Practices.EnterpriseLibrary.Caching, Version=5.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="NullBackingStore" /> </backingStores> </cachingConfiguration> </configuration>
Entlib.config包含了四個節點exceptionHandling、dataConfiguration、loggingConfiguration和cachingConfiguration,分別是對異常的配置、數據庫的配置、日志的配置和緩存的配置;其實EntLib.Config配置文件的內容可以集成在Web.config和App.Config的配置文件中,就不需要配置filePath路徑,但是分成獨立的文件維護起來肯定更方便;
這里有個問題,就是那個filePath指定路徑,Web項目一定要指定絕對路徑,而Winform項目指定相對路徑又可以,絕對路徑太麻煩了代碼換個地方又要修改此路徑;有人解決過的請告訴我一聲;
除了上面兩處配置,還有一個配置文件EFWUnity.config,涉及到依賴注入的對象需要在此文件中添加配置;
<configuration> <configSections> <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration, Version=2.0.414.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/> </configSections> <unity> <assembly name="Books"/> <namespace name="Books.Dao"/> <container> <register type="IBookDao" mapTo="SqlBookDao"></register> </container> </unity> </configuration>
其中EntLib配置文件除了手工修改,企業庫還提供了可視化工具進行配置,基本上在項目中這些配置都不需要修改;如果是Winform版針對數據庫連接字符串需要加密的話,可能需要工具配置一下,EntLib工具提供了加密的功能;
二、對EntLib進行封裝
為了使調用EntLib的更方便,還有就是以后EntLib升級的話不需要修改太多地方,所以對EntiLib進一步封裝;
所有EntLib對象創建都通過ZhyContainer對象

/// <summary> /// 封裝企業庫容器 /// </summary> public class ZhyContainer { public static IUnityContainer container = null; /// <summary> /// 獲取依賴注入容器 /// </summary> /// <returns></returns> public static IUnityContainer CreateUnity() { container = new UnityContainer(); return container; } public static void AddUnity(UnityConfigurationSection section) { if (container == null) CreateUnity(); container.LoadConfiguration(section); } /// <summary> /// 獲取數據庫對象 /// </summary> /// <returns>數據庫對象</returns> public static Database CreateDataBase() { return EnterpriseLibraryContainer.Current.GetInstance<Database>(); } /// <summary> /// 獲取數據庫對象 /// </summary> /// <param name="name">數據庫實例名(默認name為空,調用默認數據庫實例)</param> /// <returns>數據庫對象</returns> public static Database CreateDataBase(string name) { return EnterpriseLibraryContainer.Current.GetInstance<Database>(name); } /// <summary> /// 獲取日志寫入對象 /// </summary> /// <returns></returns> public static LogWriter CreateLog() { return EnterpriseLibraryContainer.Current.GetInstance<LogWriter>(); } public static LogWriter CreateLog(string name) { return EnterpriseLibraryContainer.Current.GetInstance<LogWriter>(name); } /// <summary> /// 獲取日志跟蹤對象 /// </summary> /// <returns></returns> public static TraceManager CreateTrace() { return EnterpriseLibraryContainer.Current.GetInstance<TraceManager>(); } /// <summary> /// 獲取異常處理對象 /// </summary> /// <returns></returns> public static ExceptionManager CreateException() { return EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>(); } public static ICacheManager cacheManager = null; /// <summary> /// 獲取緩存對象 /// </summary> /// <returns></returns> public static ICacheManager CreateCache() { cacheManager = CacheFactory.GetCacheManager(); return cacheManager; } public static ICacheManager CreateCache(string name) { cacheManager = EnterpriseLibraryContainer.Current.GetInstance<ICacheManager>(name); return cacheManager; } }
針對EntLib緩存對象ICacheManager的操作封裝成CacheHelper對象

/// <summary> /// 緩存操作類 /// </summary> public static class CacheHelper { private static ICacheManager cache = ZhyContainer.CreateCache(); /// <summary> /// 添加緩存 /// </summary> /// <param name="key">鍵</param> /// <param name="value">值</param> public static void Add(string key, object value) { cache.Add(key, value); } /// <summary> /// 添加緩存 /// </summary> /// <param name="key">鍵</param> /// <param name="value">值</param> /// <param name="isRefresh">是否刷新</param> public static void Add(string key, object value, bool isRefresh) { if (isRefresh) { //自定義刷新方式,如果過期將自動重新加載,過期時間為5分鍾 cache.Add(key, value, CacheItemPriority.Normal, new MyCacheItemRefreshAction(), new AbsoluteTime(TimeSpan.FromMinutes(5))); } else { cache.Add(key, value); } } /// <summary> /// 緩存是否存在此數據 /// </summary> /// <param name="key">鍵</param> /// <returns></returns> public static bool Contains(string key) { return cache.Contains(key); } /// <summary> /// 獲取緩存對象 /// </summary> /// <param name="key">鍵</param> /// <returns></returns> public static object GetCache(string key) { return cache.GetData(key); } /// <summary> /// 移除緩存對象 /// </summary> /// <param name="key">鍵</param> public static void RemoveCache(string key) { cache.Remove(key); } }
針對EntLib日志跟蹤記錄操作封裝成LogHelper對象

/// <summary> /// 日志操作類 /// </summary> public static class LogHelper { private static LogWriter logw = ZhyContainer.CreateLog(); private static TraceManager traceMgr = ZhyContainer.CreateTrace(); private static LogEntry loge = new LogEntry(); /// <summary> /// 開始跟蹤 /// </summary> /// <returns></returns> public static Tracer StartTrace() { return traceMgr.StartTrace(Category.FileLog); } /// <summary> /// 結束跟蹤 /// </summary> /// <param name="trace"></param> public static void EndTrace(Tracer trace) { trace.Dispose(); } }
三、EntLib實現數據庫訪問
使用EntLib對數據庫的訪問非常簡單,先創建一個數據庫對象Database
public SqlServerDb(string key) : base() { database = ZhyContainer.CreateDataBase(key); _connString = database.ConnectionString; }
利用Database對象執行SQL語句
public override int DoCommand(string commandtext) { if (isInTransaction) { command = database.GetSqlStringCommand(commandtext); command.Connection = connection; command.Transaction = transaction; command.CommandType = CommandType.Text; return database.ExecuteNonQuery(command,transaction); } else { return database.ExecuteNonQuery(CommandType.Text, commandtext); } }
當初讓我使用EntLib的最重要的原因就是它支持多數據庫訪問與數據庫連接池。數據庫連接是有限資源,它們的妥善管理對可擴展的應用程序來說是非常重要的,傳統的DbHelper都是在使用完后必須關閉和釋放資源,而使用企業庫中的Database對象操作數據庫我們不用自己處理,企業庫會自動處理。
EntLib數據訪問程序塊資料參考:http://www.cnblogs.com/shanyou/archive/2008/05/25/1206898.html
四、EntLib實現錯誤日志記錄
程序中記錄日志是很重要的,好的日志記錄方式可以提供我們足夠多定位問題的依據;如果只是單獨需要日志功能個人推薦Log4net,使用起來更簡單;
先看記錄的日志文件內容,日志文件是放在根目錄的trace.log文件中
五、EntLib實現緩存管理
在構建企業級分布式應用程序時,架構師和開發人員面臨着許多難題。緩存可以幫助您克服其中的一些難題,包括:
性能:通過存儲與數據使用者盡可能接近的相關數據,緩存可以提高應用程序的性能。這樣可以避免重復進行數據創建、處理和傳輸。
可伸縮性:在緩存中存儲信息有助於節省資源,並且可以隨着應用程序需求的增加來提高可伸縮性
可用性:通過將數據存儲在本地緩存中,應用程序可以承受系統的故障,例如網絡等待時間、Web 服務問題以及硬件故障
其實我使用緩存想法很簡單,就是全局訪問的數據我都存入緩存中,不用緩存也能解決那你得定義很多靜態公共變量,但這個代碼實現起來比較麻煩;
1)框架中ORM模塊對實體的自定義標簽配置用到了緩存
cache.Add("entityAttributeList", entityAttributeList);
2)控制器中對配置的內容也用到了緩存
cache.Add("cmdWebController", cmdControllerList);
六、EntLib實現AOP與對象創建
框架中基於EntLib的AOP實現比較簡單,先看如何使用AOP:
上面是數據庫事務處理利用AOP攔截方法實現;
1)定義自定義標簽AOPAttribute

/// <summary> /// AOP標簽 /// </summary> [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method | AttributeTargets.Property |AttributeTargets.Interface)] public class AOPAttribute : HandlerAttribute { private List<Type> _types; /// <summary> /// 創建AOPAttribute實例 /// </summary> /// <param name="types">AOP操作類類型數組</param> public AOPAttribute(params Type[] types) { _types = types.ToList(); } /// <summary> /// 創建AOP管理對象 /// </summary> /// <param name="container">示例容器</param> /// <returns></returns> public override ICallHandler CreateHandler(Microsoft.Practices.Unity.IUnityContainer container) { if (_types.Count > 0) { return new AopCallHandler(_types); } return null; } }
2)AOP操作接口,包括前處理和后處理
/// <summary> /// IAopOperator AOP操作符接口,包括前處理和后處理 /// </summary> public interface IAopOperator { /// <summary> /// 前處理 /// </summary> /// <param name="input"></param> void PreProcess(IMethodInvocation input); /// <summary> /// 后處理 /// </summary> /// <param name="input"></param> /// <param name="result"></param> void PostProcess(IMethodInvocation input, IMethodReturn result); }
3)AOP調用管理類

/// <summary> /// AOP調用管理類 /// </summary> public class AopCallHandler : ICallHandler { private List<IAopOperator> _list; //AOP操作對象列表 /// <summary> /// 創建AopCallHandler實例 /// </summary> /// <param name="list">AOP操作類類型列表</param> public AopCallHandler(List<Type> list) { _list = new List<IAopOperator>(); for (int i = 0; i < list.Count; i++) { _list.Add((IAopOperator)Activator.CreateInstance(list[i])); } } #region ICallHandler 成員 /// <summary> /// 調用執行 /// </summary> /// <param name="input"></param> /// <param name="getNext"></param> /// <returns></returns> public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) { IMethodReturn result; for (int i = 0; i < _list.Count; i++) { _list[i].PreProcess(input); } //log result = getNext()(input, getNext); //if (result.Exception == null) //{ // Logger.Write("Action Done."); for (int i = _list.Count - 1; i >= 0; i--) { _list[i].PostProcess(input, result); } //} //log return result; } /// <summary> /// 執行順序 /// </summary> public int Order { get; set; } #endregion }
4)最后創建一個AOP事務處理類,繼承接口IAopOperator功能在前處理和后處理兩個方法中實現

public class AopTransaction : IAopOperator { public AopTransaction() { } #region IAopOperator 成員 public void PreProcess(Microsoft.Practices.Unity.InterceptionExtension.IMethodInvocation input) { List<AbstractDatabase> _RdbList = ((IbindDb)input.Target).GetMoreDb(); AbstractDatabase Rdb = ((IbindDb)input.Target).GetDb(); if (_RdbList == null) { Rdb.BeginTransaction(); } else { foreach (AbstractDatabase db in _RdbList) { db.BeginTransaction(); } } } public void PostProcess(Microsoft.Practices.Unity.InterceptionExtension.IMethodInvocation input, Microsoft.Practices.Unity.InterceptionExtension.IMethodReturn result) { List<AbstractDatabase> _RdbList = ((IbindDb)input.Target).GetMoreDb(); AbstractDatabase Rdb = ((IbindDb)input.Target).GetDb(); if (_RdbList == null) { if (result.Exception == null) { Rdb.CommitTransaction(); } else { Rdb.RollbackTransaction(); } } else { List<AbstractDatabase> RdbList = new List<AbstractDatabase>(); foreach (AbstractDatabase db in _RdbList) { RdbList.Add(db); } RdbList.Reverse();//反序 if (result.Exception == null) { foreach (AbstractDatabase db in RdbList) { db.CommitTransaction(); } } else { foreach (AbstractDatabase db in RdbList) { db.RollbackTransaction(); } } } } #endregion }
EntLib實現AOP學習參考:http://www.cnblogs.com/artech/archive/2008/11/27/1342309.html
利用EntLib的Unity創建邏輯層對象,比如Dao對象、ObjectModel對象,這些對象都不能用new關鍵字來創建;
Book book = NewObject<Book>();
public T NewObject<T>() { T t = FactoryModel.GetObject<T>(_oleDb,_container, null); return t; }
public static T GetObject<T>(AbstractDatabase Db,IUnityContainer _container, string unityname) { if (Db == null) { EFWCoreLib.CoreFrame.DbProvider.AbstractDatabase Rdb = EFWCoreLib.CoreFrame.DbProvider.FactoryDatabase.GetDatabase(); //SysLoginRight currLoginUser = (SysLoginRight)EFWCoreLib.CoreFrame.Init.AppGlobal.cache.GetData("RoleUser"); //Rdb.WorkId = currLoginUser.WorkId; Db = Rdb; _container = EFWCoreLib.CoreFrame.Init.AppGlobal.container; } //讀unity配置文件把類注入到接口得到對象實例 IUnityContainer container = _container; T t = default(T); if (unityname == null) t = container.Resolve<T>(); else t = container.Resolve<T>(unityname); IbindDb ibind = (IbindDb)t; ibind.BindDb(Db,container); //給對象加上代理 t = (T)PolicyInjection.Wrap(t.GetType(), t); return t; }
如果覺得此框架對你有所幫助,請關注我的博客,后續文章會持續更新!