在实际的应用程序中,可能经常会遇到在Service端如何统一处理异常,记Log等的问题。这些问题可能很多AOP框架已经给出了解决方案。其实对于WCF来说简单的做一下扩展就可以解决这个问题了。
由于处理异常,记日志主要针对的是Operation级别,所以我选择的扩展点是IOperationInvoker接口,这个接口提供在Operation调用时的拦截。先看一下它的定义:
public interface IOperationInvoker
{
// Methods
object[] AllocateInputs();
object Invoke(object instance, object[] inputs, out object[] outputs);
IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state);
object InvokeEnd(object instance, out object[] outputs, IAsyncResult result);
// Properties
bool IsSynchronous { get; }
}
{
// Methods
object[] AllocateInputs();
object Invoke(object instance, object[] inputs, out object[] outputs);
IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state);
object InvokeEnd(object instance, out object[] outputs, IAsyncResult result);
// Properties
bool IsSynchronous { get; }
}
它提供了同步和异步调用Operation的方法。调用Operation的过程当然还是由WCF本身来完成,我们要做的就是在调用前和调用后来做日志和处理异常。下面是我的实现类:
public class MyInvoker : IOperationInvoker
{
IOperationInvoker m_OldInvoker;
InterceptionType m_InteType;
public MyInvoker(IOperationInvoker oldInvoker, InterceptionType inteType)
{
Debug.Assert(oldInvoker != null);
m_OldInvoker = oldInvoker;
m_InteType = inteType;
}
public virtual object[] AllocateInputs()
{
return m_OldInvoker.AllocateInputs();
}
protected void PreInvoke(object instance, object[] inputs)
{
if (m_InteType == InterceptionType.None)
{
}
else if (m_InteType == InterceptionType.LogInvoke)
{
}
}
protected void PostInvoke(object instance, object returnedValue, object[] outputs, Exception err)
{
if (m_InteType == InterceptionType.None)
{
}
else if (m_InteType == InterceptionType.LogInvoke)
{
}
else if (m_InteType == InterceptionType.LogException)
{
}
else if (m_InteType == InterceptionType.LogExceptionAndMail)
{
}
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
PreInvoke(instance, inputs);
object returnedValue = null;
object[] outputParams = new object[] { };
Exception exception = null;
try
{
returnedValue = m_OldInvoker.Invoke(instance, inputs, out outputParams);
outputs = outputParams;
return returnedValue;
}
catch (Exception err)
{
outputs = null;
exception = err;
return null;
}
finally
{
PostInvoke(instance, returnedValue, outputParams, exception);
}
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
PreInvoke(instance, inputs);
return m_OldInvoker.InvokeBegin(instance, inputs, callback, state);
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
object returnedValue = null;
object[] outputParams = { };
Exception exception = null;
try
{
returnedValue = m_OldInvoker.InvokeEnd(instance, out outputs, result);
outputs = outputParams;
return returnedValue;
}
catch (Exception err)
{
outputs = null;
exception = err;
return null;
}
finally
{
PostInvoke(instance, returnedValue, outputParams, exception);
}
}
public bool IsSynchronous
{
get
{
return m_OldInvoker.IsSynchronous;
}
}
}
{
IOperationInvoker m_OldInvoker;
InterceptionType m_InteType;
public MyInvoker(IOperationInvoker oldInvoker, InterceptionType inteType)
{
Debug.Assert(oldInvoker != null);
m_OldInvoker = oldInvoker;
m_InteType = inteType;
}
public virtual object[] AllocateInputs()
{
return m_OldInvoker.AllocateInputs();
}
protected void PreInvoke(object instance, object[] inputs)
{
if (m_InteType == InterceptionType.None)
{
}
else if (m_InteType == InterceptionType.LogInvoke)
{
}
}
protected void PostInvoke(object instance, object returnedValue, object[] outputs, Exception err)
{
if (m_InteType == InterceptionType.None)
{
}
else if (m_InteType == InterceptionType.LogInvoke)
{
}
else if (m_InteType == InterceptionType.LogException)
{
}
else if (m_InteType == InterceptionType.LogExceptionAndMail)
{
}
}
public object Invoke(object instance, object[] inputs, out object[] outputs)
{
PreInvoke(instance, inputs);
object returnedValue = null;
object[] outputParams = new object[] { };
Exception exception = null;
try
{
returnedValue = m_OldInvoker.Invoke(instance, inputs, out outputParams);
outputs = outputParams;
return returnedValue;
}
catch (Exception err)
{
outputs = null;
exception = err;
return null;
}
finally
{
PostInvoke(instance, returnedValue, outputParams, exception);
}
}
public IAsyncResult InvokeBegin(object instance, object[] inputs, AsyncCallback callback, object state)
{
PreInvoke(instance, inputs);
return m_OldInvoker.InvokeBegin(instance, inputs, callback, state);
}
public object InvokeEnd(object instance, out object[] outputs, IAsyncResult result)
{
object returnedValue = null;
object[] outputParams = { };
Exception exception = null;
try
{
returnedValue = m_OldInvoker.InvokeEnd(instance, out outputs, result);
outputs = outputParams;
return returnedValue;
}
catch (Exception err)
{
outputs = null;
exception = err;
return null;
}
finally
{
PostInvoke(instance, returnedValue, outputParams, exception);
}
}
public bool IsSynchronous
{
get
{
return m_OldInvoker.IsSynchronous;
}
}
}
在PreInvoke和PostInvoke方法中可以加入我们自己的代码。InterceptionType是一个枚举,里面定义了拦截的类型:
public enum InterceptionType
{
None,
LogInvoke,
LogException,
LogExceptionAndMail
}
这个枚举可以根据需要来进行扩展。如何让WCF使用我们自己的这个OperationInvoker呢,可以通过加入Operation Behavior来完成:
{
None,
LogInvoke,
LogException,
LogExceptionAndMail
}
[AttributeUsage(AttributeTargets.Method)]
public class MyOperationInterceptorAttribute : Attribute, IOperationBehavior
{
private InterceptionType m_InteType = InterceptionType.None;
public MyOperationInterceptorAttribute() { }
public MyOperationInterceptorAttribute(InterceptionType inteType)
{
this.m_InteType = inteType;
}
protected MyInvoker CreateInvoker(IOperationInvoker oldInvoker)
{
return new MyInvoker(oldInvoker, m_InteType);
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{ }
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{ }
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
IOperationInvoker oldInvoker = dispatchOperation.Invoker;
dispatchOperation.Invoker = CreateInvoker(oldInvoker);
}
public void Validate(OperationDescription operationDescription)
{ }
}
在ApplyDispatchBehavior中换掉原来的Invoker就可以了。所以我们只要在Operation上打上这个标签就可以了,并且在打标签的同时可以指定拦截的类型。public class MyOperationInterceptorAttribute : Attribute, IOperationBehavior
{
private InterceptionType m_InteType = InterceptionType.None;
public MyOperationInterceptorAttribute() { }
public MyOperationInterceptorAttribute(InterceptionType inteType)
{
this.m_InteType = inteType;
}
protected MyInvoker CreateInvoker(IOperationInvoker oldInvoker)
{
return new MyInvoker(oldInvoker, m_InteType);
}
public void AddBindingParameters(OperationDescription operationDescription, BindingParameterCollection bindingParameters)
{ }
public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
{ }
public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
{
IOperationInvoker oldInvoker = dispatchOperation.Invoker;
dispatchOperation.Invoker = CreateInvoker(oldInvoker);
}
public void Validate(OperationDescription operationDescription)
{ }
}
这样可能还比较麻烦,因为我要挨个方法都去打上标签,那么我们可以往更高层次的Behavior去扩展。这里可以使用Service Behavior:
[AttributeUsage(AttributeTargets.Class)]
public class MyServiceInterceptorAttribute : Attribute,IServiceBehavior
{
protected MyOperationInterceptorAttribute CreateOperationInterceptor()
{
return new MyOperationInterceptorAttribute();
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,ServiceHostBase host)
{
foreach(ServiceEndpoint endpoint in serviceDescription.Endpoints)
{
foreach(OperationDescription operation in endpoint.Contract.Operations)
{
bool checkresult = false;
foreach (IOperationBehavior behavior in operation.Behaviors)
{
if (behavior is MyOperationInterceptorAttribute)
{
checkresult = true;
break;
}
}
if (!checkresult)
{
operation.Behaviors.Add(CreateOperationInterceptor());
}
}
}
}
public void AddBindingParameters(ServiceDescription serviceDescription,ServiceHostBase serviceHostBase,Collection<ServiceEndpoint> endpoints,BindingParameterCollection bindingParameters)
{}
public void Validate(ServiceDescription serviceDescription,ServiceHostBase serviceHostBase)
{}
}
在ApplyDispatchBehavior这个方法中会去给没有打上标签的Operation都打上标签。所以我们只需要给Service打个标签就可以了。public class MyServiceInterceptorAttribute : Attribute,IServiceBehavior
{
protected MyOperationInterceptorAttribute CreateOperationInterceptor()
{
return new MyOperationInterceptorAttribute();
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription,ServiceHostBase host)
{
foreach(ServiceEndpoint endpoint in serviceDescription.Endpoints)
{
foreach(OperationDescription operation in endpoint.Contract.Operations)
{
bool checkresult = false;
foreach (IOperationBehavior behavior in operation.Behaviors)
{
if (behavior is MyOperationInterceptorAttribute)
{
checkresult = true;
break;
}
}
if (!checkresult)
{
operation.Behaviors.Add(CreateOperationInterceptor());
}
}
}
}
public void AddBindingParameters(ServiceDescription serviceDescription,ServiceHostBase serviceHostBase,Collection<ServiceEndpoint> endpoints,BindingParameterCollection bindingParameters)
{}
public void Validate(ServiceDescription serviceDescription,ServiceHostBase serviceHostBase)
{}
}
通过这个简单的实例,我们就可以对Operation级别的异常和日志进行统一的处理了,代码中的try catch和记录日志的代码就可以通通省去了。