首先声明,Enterprise Library v5.0已发布了,此处的DEMO是基于Enterprise Library v4.1这个版本. 差别并不太多了.Enterprise Library 提供了图形的化配置工具,让我们免去写XML的配置文件.我们可以用policy injection application block和Unity搭建一个简单的AOP框架,而你不用写一句代码. AOP框架解决是实现Logger,Transaction,同步处理,权限控制,验证等功能的复用,让我们把更多的精力关注在业务上.
使用配置工具分别增加Exception Handing Application Block,Logging Application Block, Policy Injection Application Block如下图:
PIAB的Matching Rules是很灵活的,可以匹配一个Type,Namespace,Assembly等.这里配置的是Tag Attribute,当然还可以配置自定义规则.为PIAB的Handlers增加Exception Handling,Logging,TransactionScope handler,注意这个TransactionScope是社区贡献的,去Enterprise Library Contrib project下载. 这里你可能还注意到上面的Exception Handing Application Block也有一个Logging Handler, 不是同一个,但它们可以共享Logging Application Block的Category.配置时是非常灵活的.最终的XML是:
<configSections>
<section name="policyInjection" type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.Configuration.PolicyInjectionSettings, Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<section name="loggingConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.LoggingSettings, Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<section name="exceptionHandling" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Configuration.ExceptionHandlingSettings, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</configSections>
<policyInjection>
<policies>
<add name="MyPIPolicy">
<matchingRules>
<add match="MyTagName" ignoreCase="false" type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.MatchingRules.TagAttributeMatchingRule, Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Tag Attribute Matching Rule" />
</matchingRules>
<handlers>
<add exceptionPolicyName="MyPolicy" order="2" type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers.ExceptionCallHandler, Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Exception Handling Handler" />
<add logBehavior="BeforeAndAfter" beforeMessage="MyDefault Before"
afterMessage="MyDefault After
" eventId="0" includeParameterValues="true"
includeCallStack="true" includeCallTime="true" priority="-1"
severity="Information" order="0" type="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers.LogCallHandler, Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Logging Handler">
<categories>
<add name="General" />
</categories>
</add>
<add transactionScopeOption="Required" timeout="00:01:00" isolationLevel="Serializable"
interopOption="None" complete="true" type="EntLibContrib.PolicyInjection.CallHandlers.TransactionScopeCallHandler, EntLibContrib.PolicyInjection.CallHandlers, Version=4.1.0.0, Culture=neutral, PublicKeyToken=null"
name="TransactionScope Handler" />
</handlers>
</add>
</policies>
</policyInjection>
<loggingConfiguration name="Logging Application Block" tracingEnabled="true"
defaultCategory="General" logWarningsWhenNoCategoriesMatch="true">
<listeners>
<add fileName="Generaltrace.log" header="----------------------------------------"
footer="----------------------------------------" formatter="Text Formatter"
listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.FlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
traceOutputOptions="None" filter="All" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.FlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="FlatFile TraceListener" />
<add fileName="ErrorRolling.log" footer="----------------------------------------"
formatter="" header="----------------------------------------"
rollFileExistsBehavior="Overwrite" rollInterval="None" rollSizeKB="0"
timeStampPattern="yyyy-MM-dd" listenerDataType="Microsoft.Practices.EnterpriseLibrary.Logging.Configuration.RollingFlatFileTraceListenerData, Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
traceOutputOptions="None" filter="All" type="Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners.RollingFlatFileTraceListener, Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Rolling Flat File Trace Listener" />
</listeners>
<formatters>
<add template="Timestamp: {timestamp}
Message: {message}
Category: {category}
Priority: {priority}
EventId: {eventid}
Severity: {severity}
Title:{title}
Machine: {machine}
Application Domain: {appDomain}
Process Id: {processId}
Process Name: {processName}
Win32 Thread Id: {win32ThreadId}
Thread Name: {threadName}
Extended Properties: {dictionary({key} - {value}
)}"
type="Microsoft.Practices.EnterpriseLibrary.Logging.Formatters.TextFormatter, Microsoft.Practices.EnterpriseLibrary.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Text Formatter" />
</formatters>
<categorySources>
<add switchValue="All" name="ExceptionLogger">
<listeners>
<add name="Rolling Flat File Trace Listener" />
</listeners>
</add>
<add switchValue="All" name="General">
<listeners>
<add name="FlatFile TraceListener" />
</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="Rolling Flat File Trace Listener" />
</listeners>
</errors>
</specialSources>
</loggingConfiguration>
<exceptionHandling>
<exceptionPolicies>
<add name="MyPolicy">
<exceptionTypes>
<add type="System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
postHandlingAction="NotifyRethrow" name="Exception">
<exceptionHandlers>
<add logCategory="ExceptionLogger" eventId="100" severity="Error"
title="Enterprise Library Exception Handling" formatterType="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.TextExceptionFormatter, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
priority="0" useDefaultLogger="false" type="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging.LoggingExceptionHandler, Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"
name="Logging Handler" />
</exceptionHandlers>
</add>
</exceptionTypes>
</add>
</exceptionPolicies>
</exceptionHandling>
需要引用以下程序集:
1: <Reference Include="Microsoft.Practices.EnterpriseLibrary.ExceptionHandling.Logging, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
2: <Reference Include="Microsoft.Practices.EnterpriseLibrary.PolicyInjection, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
3: <Reference Include="Microsoft.Practices.EnterpriseLibrary.PolicyInjection.CallHandlers, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
4: <Reference Include="Microsoft.Practices.Unity, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
5: <Reference Include="Microsoft.Practices.Unity.Interception, Version=1.2.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" />
6: <Reference Include="EntLibContrib.PolicyInjection.CallHandlers, Version=4.1.0.0, Culture=neutral, processorArchitecture=MSIL">
好了,一切就绪了. 让我们来测试下:
1: public interface IProdutDAO
2: {
3: bool AddProduct(string productname);
4: bool DeleteProduct(int pkid);
5: bool UpdateProduct(int pkid);
6: }
7:
8: /// <summary>
9: /// ProductDAO
10: /// </summary>
11: /// <remarks>author: Petter Liu http://wintersun.cnblogs.com </remarks>
12: public class ProductDAO : IProdutDAO
13: {
14: [ExceptionCallHandler("MyPolicy")]
15: public bool AddProduct(string productname)
16: {
17: throw new Exception("You can not create duplicate user in system");
18: }
19:
20: [LogCallHandler]
21: public bool DeleteProduct(int pkid)
22: {
23: return true;
24: }
25:
26: [TransactionScopeCallHandler]
27: [ExceptionCallHandler("MyPolicy")]
28: public bool UpdateProduct(int pkid)
29: {
30: string sql = @"delete Employees where EmployeeID=(select max(EmployeeID) from Employees where LastName='Petter')
31: Insert into Employees(LastName,FirstName,Title) values('testname','asddddddddddddddddddddddasdfasdffsd','fsfs');";
32:
33: ConnectionStringSettings cfg = ConfigurationManager.ConnectionStrings["maindb"];
34: DbProviderFactory factory = DbProviderFactories.GetFactory(cfg.ProviderName);
35:
36: using (DbConnection cnx = factory.CreateConnection())
37: {
38: using (DbCommand cmd = factory.CreateCommand())
39: {
40: cnx.ConnectionString = cfg.ConnectionString;
41: cmd.Connection = cnx;
42: cmd.CommandText = sql;
43:
44: cnx.Open();
45: int roweffect = cmd.ExecuteNonQuery();
46: cnx.Close();
47: cmd.Dispose();
48: }
49: }
50: return false;
51: }
52: }
这里使用是Attribute, 看下使用Tag attribute的:
1: /// <summary>
2: /// All method will be blocked
3: /// </summary>
4: [Tag("MyTagName")]
5: public class ProductLogDAO : IProdutDAO
6: {
7: public bool AddProduct(string productname)
8: {
9: throw new Exception("You can not create duplicate user in system");
10: }
11:
12: #region IProdutDAO Members
13:
14: public bool DeleteProduct(int pkid)
15: {
16: return true;
17: }
18:
19: #endregion
20:
21: #region IProdutDAO Members
22:
23: public bool UpdateProduct(int pkid)
24: {
25: return false;
26: }
27:
28: #endregion
29: }
我们的UnitTest Code如下:
1: /// <summary>
2: /// TestPIAB
3: /// </summary>
4: /// <remarks>Author Petter Liu http://www.cnblogs.com/wintersun </remarks>
5: [TestFixture]
6: public class TestPIAB
7: {
8: [Test]
9: [ExpectedException(typeof(Exception))]
10: public void TestThrowException()
11: {
12: IProdutDAO productdao = PolicyInjection.Create<ProductDAO, IProdutDAO>();
13: productdao
14: .AddProduct("new product");
15:
16: }
17:
18: [Test]
19: public void TestAutoLogger()
20: {
21: IProdutDAO productdao = PolicyInjection.Create<ProductDAO, IProdutDAO>();
22: productdao.DeleteProduct(1);
23:
24: }
25:
26: [Test]
27: public void TestAutoTransaction()
28: {
29: IProdutDAO productdao = PolicyInjection.Create<ProductDAO, IProdutDAO>();
30: productdao.UpdateProduct(1);
31: }
32:
33: [Test]
34: [ExpectedException(typeof(Exception))]
35: public void TestTagAttribute()
36: {
37: IProdutDAO productdao = PolicyInjection.Create<ProductLogDAO, IProdutDAO>();
38: productdao
39: .AddProduct("new product");
40:
41: }
42:
43: [Test]
44: [ExpectedException(typeof(Exception))]
45: public void TestWorkingWithUntiy()
46: {
47: IUnityContainer container = new UnityContainer();
48: container.RegisterType<IProdutDAO, ProductDAO>();
49: container.AddNewExtension<Interception>();
50: container.Configure<Interception>().SetDefaultInterceptorFor(typeof(IProdutDAO)
51: , new TransparentProxyInterceptor());
52: var productdao = container.Resolve<IProdutDAO>();
53: productdao
54: .AddProduct("new product");
55: }
56: }
当我们执行第一个AddProduct时, 将会产生这样的日志ErrorRolling.log, 这是自动Exception Logging:
---------------------------------------- ExceptionLogger Error: 100 : Timestamp: 2010-06-07 14:14:54 Message: HandlingInstanceID: d684dfc6-0e5a-476d-861f-9c685b818ad3 An exception of type 'System.Exception' occurred and was caught. ---------------------------------------------------------------- 06/07/2010 22:14:54 Type : System.Exception, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Message : You can not create duplicate user in system Source : WindowsFormsApplication1 Help link : Data : System.Collections.ListDictionaryInternal TargetSite : Boolean AddProduct(System.String) Stack Trace : at WindowsFormsApplication1.ProductDAO.AddProduct(String productname) in H:\My Project\DotNet30\WindowsFormsTDD2008\TestPIAB.cs:line 113 Additional Info: MachineName : USER TimeStamp : 2010-06-07 14:14:54 FullName : Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 AppDomainName : domain-nunit.addin.dll ThreadIdentity : WindowsIdentity : USER\Petter Category: ExceptionLogger Priority: 0 EventId: 100 Severity: Error Title:Enterprise Library Exception Handling Machine: USER App Domain: domain-nunit.addin.dll ProcessId: 6076 Process Name: D:\Program Files\TestDriven.NET 2.0\ProcessInvocation.exe Thread Name: TestRunnerThread Win32 ThreadId:2176 Extended Properties: ----------------------------------------
当我们执行DeleteProduct, 测试的是自动方法Logging, 此处将生成日志文件:Generaltrace.log,有BeforeMessage,AfterMessage,所以有两段
借这个思路,我们可以实现前方法,后方法,以后有时间讨论.内容如下:
---------------------------------------- Timestamp: 2010-06-07 14:22:10 Message: Category: General Priority: -1 EventId: 0 Severity: Information Title:Call Logging Machine: USER Application Domain: domain-nunit.addin.dll Process Id: 6076 Process Name: D:\Program Files\TestDriven.NET 2.0\ProcessInvocation.exe Win32 Thread Id: 6412 Thread Name: TestRunnerThread Extended Properties: pkid - 1 ---------------------------------------- ---------------------------------------- Timestamp: 2010-06-07 14:22:10 Message: Category: General Priority: -1 EventId: 0 Severity: Information Title:Call Logging Machine: USER Application Domain: domain-nunit.addin.dll Process Id: 6076 Process Name: D:\Program Files\TestDriven.NET 2.0\ProcessInvocation.exe Win32 Thread Id: 6412 Thread Name: TestRunnerThread Extended Properties: pkid - 1 ----------------------------------------
当我们执行测试UpdateProduct时,这时测试的是自动Transaction,我们故意执行SQL时,先删除一条记录,再插入一条记录,
并造成字段截段的Exception,看以下日志的结果:
ExceptionLogger Error: 100 : Timestamp: 2010-06-07 14:22:03 Message: HandlingInstanceID: 59690aa2-8e07-4bdb-bcfc-28379961e667 An exception of type 'System.Data.SqlClient.SqlException' occurred and was caught. ---------------------------------------------------------------------------------- 06/07/2010 22:22:03 Type : System.Data.SqlClient.SqlException, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 Message : 将截断字符串或二进制数据。 语句已终止。 Source : .Net SqlClient Data Provider Help link : Errors : System.Data.SqlClient.SqlErrorCollection Class : 16 LineNumber : 2 Number : 8152 Procedure : Server : .\sqlexpress2008 State : 4 ErrorCode : -2146232060 Data : System.Collections.ListDictionaryInternal TargetSite : Void OnError(System.Data.SqlClient.SqlException, Boolean) Stack Trace : at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection) at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection) at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj) at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) at System.Data.SqlClient.SqlCommand.RunExecuteNonQueryTds(String methodName, Boolean async) at System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe) at System.Data.SqlClient.SqlCommand.ExecuteNonQuery() at WindowsFormsApplication1.ProductDAO.UpdateProduct(Int32 pkid) in H:\My Project\DotNet30\WindowsFormsTDD2008\TestPIAB.cs:line 141 Additional Info: MachineName : USER TimeStamp : 2010-06-07 14:22:03 FullName : Microsoft.Practices.EnterpriseLibrary.ExceptionHandling, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 AppDomainName : domain-nunit.addin.dll ThreadIdentity : WindowsIdentity : USER\Petter Category: ExceptionLogger Priority: 0 EventId: 100 Severity: Error Title:Enterprise Library Exception Handling Machine: USER App Domain: domain-nunit.addin.dll ProcessId: 6076 Process Name: D:\Program Files\TestDriven.NET 2.0\ProcessInvocation.exe Thread Name: TestRunnerThread Win32 ThreadId:7532 Extended Properties: HelpLink.ProdName - Microsoft SQL Server HelpLink.ProdVer - 10.00.1600 HelpLink.EvtSrc - MSSQLServer HelpLink.EvtID - 8152 HelpLink.BaseHelpUrl - http://go.microsoft.com/fwlink HelpLink.LinkId - 20476 ----------------------------------------
是不是相当详细的,记录的内容模板你是可以修改的,这里全部用的是默认的配置值.
TestTagAttribute 这里使用是Tag Attribute,那被标记的对像将按配置的Order值执行所有Injection Handler.
以最后一个UnitTest方法是PIAB与Unity组合使用.
总结,现在是组件开发的时代了,上面所有组件你都可以使用其它的,或是你自己写的,或是第三方的.使用Enterprise Libary各个组件,我们可以轻易搭建一个AOP框架.
作者:Petter Liu
出处:http://www.cnblogs.com/wintersun/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
该文章也同时发布在我的独立博客中-Petter Liu Blog。