错误概述
在设计良好的应用程序中,服务应该是被封装的,客户端无法知道有关错误的消息。
设计良好的服务应尽可能是自治的,不能依赖客户端去处理或恢复错误。 任何非空的错误通知都应该是客户端与服务端之间契约交互的一部分。
WCF错误处理的关键3个技术点
- 错误隔离
- 错误屏蔽
- 通道故障
错误隔离
当代表某个客户端的服务调用导致异常时,并不会寄宿在宿主进程,其它客户端仍然可以访问该服务。 这是必须的!
托管在相同进程中的其它服务也不会受到影响。
错误如何隔离
当一个未经处理的异常离开服务范围时,分发器会捕获它,并将它序列化到返回消息中传递给客户端。当返回消息到达代理时,代理会在客户端抛出一个异常。
这一方式为每个WCF服务提供了进程级的隔离。
客户端与服务端能够共享一个进程,但对于错误的处理却完全是独立的。唯一的例外是能够导致.NET崩溃的关键错误,如堆栈溢出,才会影响到宿主进程。
错误屏蔽技术
当客户端试图调用服务时,实际上可能会遭遇三种错误类型。
- 通信错误。如网络故障,地址错误,宿主进程没运行等等。客户端的通信错误表现为CommunicationException异常或其子类异常,如EndpointNotFoundException。
- 代理和通道相关。这种类型存在很多可能的异常。如,试图访问已经关闭的代理,就会导致ObjectDisposedException异常;契约和绑定的安全级别不匹配时,就会导致InvalidOperationException异常等等。
服务调用。服务抛出异常,也可能是服务在调用其它对象或资源通过内部调用抛出的异常。
客户端所关注的就是错误发生了。
出于对封装和解耦的考虑,在默认情况下,所有的服务抛出的异常总是以FaultException类型到达客户端。
通道故障
在传统的.NET编程中,客户端可以捕获异常,并继续调用对象,这是.NET平台的一个重要缺陷。客户端怎么可以知道可能抛出异常,仍然会使用它呢。
开发人员必须在每个对象中委会一个标志:在抛出异常之前或在抛出异常之后设置该标志,然后在公开方法内检查该标志,如果对象是在抛出异常之后被调用,则拒绝使用该对象。
当然,这很繁琐而且乏味。
WCF自动实现了这一最佳实践。如果服务具有传输会话,则任何未经处理的异常(保存在继承子FaultException类中)都会导致通道发生错误(代理的状态就会被修改为CommunicationState.Faulted),这样就避免了客户端使用该代理,而对象也可以隐藏在异常之后。
换句话说,对于如下的服务和代理的定义,最直接的解决方案就是客户端不要在抛出异常之后使用WCF代理。如果具有传输会话,客户端甚至不能关闭代理。
在抛出异常之后,客户端唯一可以安全执行的就是取消代理,或者防止其它的客户端使用代理。
现在的问题是,对于每个方法调用,你都必须重复这些代码。最好能把这些代码封装在代理类中。
处理异常常用调试技术
1 通过管理方式在错误消息中包含异常
2 包含错误契约的回调契约
[ServiceContract(CallbackContract = typeof(IMyContractCallback))]
interface IMyContract
{
[OperationContract]
void DoSomething();
}
interface IMyContractCallback
{
[OperationContract]
[FaultContract(typeof(InvalidOperationException))]
void OnCallBack();
}
3 带外调用中的错误处理
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
class MyService : IMyContract
{
static List<IMyContractCallback> m_Callbacks =
new List<IMyContractCallback>();
public void DoSomething()
{
IMyContractCallback callback =
OperationContext.Current.GetCallbackChannel<IMyContractCallback>();
if (m_Callbacks.Contains(callback) == false)
{
m_Callbacks.Add(callback);
}
}
public static void CallClients()
{
Action<IMyContractCallback> invoke = delegate(
IMyContractCallback callback)
{
try
{
callback.OnCallback();
}
catch(FaultException<InvalidOperationException> exception)
{...}
catch(FaultException exception)
{...}
catch(CommunicationException exception)
{...}
};
m_Callbacks.ForEach(invoke);
}
}