我有一个现有的 Web 服务,其中不支持事务流,并在响应的错误集合中返回错误,而不是抛出错误。所以响应消息契约看起来像这样:
[MessageContract()]
public class UpdateResponse
{
[MessageBodyMember()]
public UpdateData Data { get; set; }
[MessageBodyMember()]
public ErrorMessages Errors { get; set; }
}
现在该服务将被更新,因此它的操作可以包含在分布式事务中,该事务将在客户端启动。因为它没有抛出错误,所以我启用了事务流并将 transactionAutoComplete 设置为 false。
所以在操作实现中我只会在错误集合为空的时候手动提交事务:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession,
ConcurrencyMode = ConcurrencyMode.Single,
TransactionIsolationLevel = IsolationLevel.ReadCommitted)]
public partial class MyService: MyServiceContract
{
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
[TransactionFlow(TransactionFlowOption.Allowed)]
public UpdateResponse Update(UpdateRequest request)
{
UpdatePlanResponse response = new UpdateResponse();
... call inner components to perform operation ...
if (errors.Count == 0)
{
OperationContext.Current.SetTransactionComplete();
}
return response;
}
}
在客户端,我还在 wsHttpbinding 配置中启用了事务流。该服务在事务范围内被调用,如下所示:
using (TransactionScope tran = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions{ IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted}))
{
UpdateResponse response = _serviceClient.Update(updateRequest);
... some more work ...
if(response.Errors != null && response.Errors.Count > 0)
{
... handle and raise them ...
}
tran.Complete();
}
事务流向服务。在积极的情况下,一切都按预期工作,服务代码所做的更改仅在客户端提交事务时才会持久化。现在,如果在服务上发现任何问题,服务将中止事务(因为已创建内部事务范围,并且在这种情况下不会完成),并且错误集合中包含许多错误的响应消息将是退还。
但是在客户端,ProtocolException 指出“执行此方法调用的事务被异步中止”。由 WCF 抛出:
服务器堆栈跟踪:在 System.ServiceModel.Channels.ServiceChannel.HandleReply(ProxyOperationRuntime operation, ProxyRpc& rpc) 在 System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter)。 System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) 在 System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation) 在 System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)。 ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage 消息)
在 [0] 处重新抛出异常:在 System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type) 在 MyServiceContract.Update( UpdateRequest 请求)在 MyServiceContractClient.Update(UpdateRequest 请求)
在这种情况下我能做些什么,这样我就可以在服务端中止事务,同时仍然向客户端发送一些关于出了什么问题的信息而不会引发错误?
在这里抛出错误会更有意义(并且避免我启用会话,这是将事务自动完成设置为 false 所必需的),但由于它是现有服务,我想在使用它们之前检查我的选项。
更新
如果服务操作如以下示例那样实现,则包含错误信息的响应将到达客户端。我可以看到事务状态已中止,但 WCF 没有抛出任何 ProtocolException:
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
[TransactionFlow(TransactionFlowOption.Allowed)]
public UpdateResponse Update(UpdateRequest request)
{
UpdatePlanResponse response = new UpdateResponse();
response.errors = new List<Error>() { new Error("dummy"); }
if (response.errors.Count == 0)
{
OperationContext.Current.SetTransactionComplete();
}
return response;
}
但是,如果我在服务实现上启动另一个事务范围并且这被中止,那么错误将被发送回客户端,其中将引发 ProtocolException:
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
[TransactionFlow(TransactionFlowOption.Allowed)]
public UpdateResponse Update(UpdateRequest request)
{
UpdatePlanResponse response = new UpdateResponse();
//start another transaction scope and abort it
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
}
response.errors = new List<Error>() { new Error("dummy"); }
if (response.errors.Count == 0)
{
OperationContext.Current.SetTransactionComplete();
}
return response;
}
似乎在第二种情况下,WCF 尝试中止已经中止的事务,这导致以下错误被发送回客户端......
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:r="http://schemas.xmlsoap.org/ws/2005/02/rm" xmlns:a="http://www.w3.org/2005/08/addressing">
<s:Header>
...
</s:Header>
<s:Body>
<s:Fault>
<s:Code>
<s:Value>s:Sender</s:Value>
<s:Subcode>
<s:Value xmlns:a="http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher">a:TransactionAborted</s:Value>
</s:Subcode>
</s:Code>
<s:Reason>
<s:Text xml:lang="en-GB">The transaction under which this method call was executing was asynchronously aborted.</s:Text>
</s:Reason>
</s:Fault>
</s:Body>
</s:Envelope>
启用跟踪我可以看到在服务端为 System.ServiceModel 源记录了以下异常(由于此异常,上述故障被发送到客户端):
<TraceRecord xmlns="http://schemas.microsoft.com/2004/10/E2ETraceEvent/TraceRecord" Severity="Error">
<TraceIdentifier>http://msdn.microsoft.com/en-GB/library/System.ServiceModel.Diagnostics.TraceHandledException.aspx</TraceIdentifier>
<Description>Handling an exception.</Description>
<AppDomain>a0ef2bea-25-129990861717638246</AppDomain>
<Exception>
<ExceptionType>System.ServiceModel.FaultException, System.ServiceModel, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>
<Message>The transaction under which this method call was executing was asynchronously aborted.</Message>
<StackTrace>
at System.ServiceModel.Diagnostics.ExceptionUtility.TraceHandledException(Exception exception, TraceEventType eventType)
at System.ServiceModel.Dispatcher.TransactionInstanceContextFacet.CheckIfTxCompletedAndUpdateAttached(MessageRpc& rpc, Boolean isConcurrent)
at System.ServiceModel.Dispatcher.TransactionBehavior.ResolveOutcome(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ResolveTransactionOutcome(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage9(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage8(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc)
at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)
at System.ServiceModel.Dispatcher.ChannelHandler.DispatchAndReleasePump(RequestContext request, Boolean cleanThread, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.HandleRequest(RequestContext request, OperationContext currentOperationContext)
at System.ServiceModel.Dispatcher.ChannelHandler.AsyncMessagePump(IAsyncResult result)
at System.ServiceModel.Dispatcher.ChannelHandler.OnAsyncReceiveComplete(IAsyncResult result)
at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
at System.ServiceModel.Channels.ReceiveTimeoutAsyncResult.Callback(IAsyncResult result)
at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
at System.Runtime.InputQueue`1.AsyncQueueReader.Set(Item item)
at System.Runtime.InputQueue`1.Dispatch()
at System.ServiceModel.Channels.ReliableReplySessionChannel.ProcessSequencedMessage(RequestContext context, String action, WsrmSequencedMessageInfo info)
at System.ServiceModel.Channels.ReliableReplySessionChannel.ProcessRequest(RequestContext context, WsrmMessageInfo info)
at System.ServiceModel.Channels.ReliableReplySessionChannel.ProcessDemuxedRequest(RequestContext context, WsrmMessageInfo info)
at System.ServiceModel.Channels.ReliableReplyListenerOverReply.ProcessSequencedItem(ReliableReplySessionChannel reliableChannel, RequestContext context, WsrmMessageInfo info)
at System.ServiceModel.Channels.ReliableListenerOverDatagram`4.HandleReceiveComplete(TItem item, TInnerChannel channel)
at System.ServiceModel.Channels.ReliableListenerOverDatagram`4.OnTryReceiveComplete(IAsyncResult result)
at System.Runtime.Fx.AsyncThunk.UnhandledExceptionFrame(IAsyncResult result)
at System.ServiceModel.Diagnostics.TraceUtility.<>c__DisplayClass4.<CallbackGenerator>b__2(AsyncCallback callback, IAsyncResult result)
at System.Runtime.AsyncResult.Complete(Boolean completedSynchronously)
at System.Runtime.InputQueue`1.AsyncQueueReader.Set(Item item)
at System.Runtime.InputQueue`1.EnqueueAndDispatch(Item item, Boolean canDispatchOnThisThread)
at System.Runtime.InputQueue`1.EnqueueAndDispatch(T item, Action dequeuedCallback, Boolean canDispatchOnThisThread)
at System.ServiceModel.Channels.SingletonChannelAcceptor`3.Enqueue(QueueItemType item, Action dequeuedCallback, Boolean canDispatchOnThisThread)
at System.ServiceModel.Channels.HttpChannelListener.HttpContextReceived(HttpRequestContext context, Action callback)
at System.ServiceModel.Activation.HostedHttpTransportManager.HttpContextReceived(HostedHttpRequestAsyncResult result)
at System.ServiceModel.Activation.HostedHttpRequestAsyncResult.HandleRequest()
at System.ServiceModel.Activation.HostedHttpRequestAsyncResult.BeginRequest()
at System.ServiceModel.Activation.HostedHttpRequestAsyncResult.OnBeginRequest(Object state)
at System.Runtime.IOThreadScheduler.ScheduledOverlapped.IOCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
at System.Runtime.Fx.IOCompletionThunk.UnhandledExceptionFrame(UInt32 error, UInt32 bytesRead, NativeOverlapped* nativeOverlapped)
at System.Threading._IOCompletionCallback.PerformIOCompletionCallback(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* pOVERLAP)
</StackTrace>
<ExceptionString>System.ServiceModel.FaultException: The transaction under which this method call was executing was asynchronously aborted.</ExceptionString>
</Exception>
</TraceRecord>