公司專案廣泛使用 PIAB,但舊專案基於 EntLib 2.0 無法直接使用 PIAB(3.0 才推出)。為解決此問題,我以方法攔截原理實作一個簡易 AOP 框架,編程模式參考 PIAB。
建立自訂 CallHandler,透過 Attribute 套用至目標方法:
public class UserManager : IUserManager
{
[TransactionScopeCallHandler(Ordinal = 1)]
[ExceptionCallHandler(Ordinal = 2, MessageTemplate = "遇到錯誤:\nMessage:{Message}")]
public void CreateDuplicateUsers(string userID, string userName)
{
UserUtility.CreateUser(userID, userName);
UserUtility.CreateUser(userID, userName); // 故意重複以觸發交易回滾
}
}
執行結果顯示交易生效(重複寫入導致回滾)與異常訊息依模板輸出。
核心類型:
ICallHandler:所有處理器介面CallHandlerBase:抽象基類HandlerAttribute:Attribute 基類CallHandlerPipeline:有序處理器管道InterceptingRealProxy<T>:繼承 RealProxy 實現攔截InvocationContext:方法執行上下文InstanceBuilder:透明 Proxy 建立工廠
public class InvocationContext
{
public IMethodCallMessage Request { get; set; }
public ReturnMessage Reply { get; set; }
public IDictionary<object, object> Properties { get; set; }
}
public interface ICallHandler
{
object PreInvoke(InvocationContext context);
void PostInvoke(InvocationContext context, object correlationState);
int Ordinal { get; set; }
bool ReturnIfError { get; set; }
}
public abstract class CallHandlerBase : ICallHandler
{
public abstract object PreInvoke(InvocationContext context);
public abstract void PostInvoke(InvocationContext context, object correlationState);
public int Ordinal { get; set; }
public bool ReturnIfError { get; set; }
}
public abstract class HandlerAttribute : Attribute
{
public abstract ICallHandler CreateCallHandler();
public int Ordinal { get; set; }
public bool ReturnIfError { get; set; }
}
依序執行 PreInvoke、呼叫目標方法、依序執行 PostInvoke,支援異常中斷與相關狀態傳遞。
public class InterceptingRealProxy<T> : RealProxy
{
private IDictionary<MemberInfo, CallHandlerPipeline> _callHandlerPipelines;
public InterceptingRealProxy(object target, IDictionary<MemberInfo, CallHandlerPipeline> pipelines)
: base(typeof(T))
{
this._callHandlerPipelines = pipelines;
}
public override IMessage Invoke(IMessage msg)
{
InvocationContext context = new InvocationContext { Request = (IMethodCallMessage)msg };
this._callHandlerPipelines[context.Request.MethodBase].Invoke(context);
return context.Reply;
}
}
掃描類別與方法上的 HandlerAttribute,建立對應 Pipeline 字典,產生透明 Proxy。
public class TransactionScopeCallHandler : CallHandlerBase
{
public override object PreInvoke(InvocationContext context) => new TransactionScope();
public override void PostInvoke(InvocationContext context, object correlationState)
{
var scope = (TransactionScope)correlationState;
if (context.Reply.Exception == null) scope.Complete();
scope.Dispose();
}
}
public class ExceptionCallHandler : CallHandlerBase
{
public string MessageTemplate { get; set; } = "{Message}";
public bool Rethrow { get; set; }
public override object PreInvoke(InvocationContext context) => null;
public override void PostInvoke(InvocationContext context, object correlationState)
{
if (context.Reply.Exception != null)
{
string message = this.MessageTemplate
.Replace("{Message}", context.Reply.Exception.InnerException.Message)
.Replace("{Source}", context.Reply.Exception.InnerException.Source)
.Replace("{StackTrace}", context.Reply.Exception.InnerException.StackTrace)
.Replace("{HelpLink}", context.Reply.Exception.InnerException.HelpLink)
.Replace("{TargetSite}", context.Reply.Exception.InnerException.TargetSite.ToString());
Console.WriteLine(message);
if (!this.Rethrow)
context.Reply = new ReturnMessage(null, null, 0, context.Request.LogicalCallContext, context.Request);
}
}
}
完整原始碼可供下載參考。