What is System.ServiceModel.Diagnostics.CallbackException and why can't I handle it?
Asked Answered
E

2

8

In my WCF client class I'm handling the Faulted() event so that if the remote service throws an exception and faults the channel I can still at least shut it down gracefully. Here's my code:

protected void RemoteDataRetriever_Faulted(object sender, EventArgs e)
{
    (sender as ICommunicationObject).Abort();
    this.Dispose();
    throw new ChannelTerminatedException("The remote service threw an unhandled exception and as a result the channel has been closed.");
}

So what I would expect is that the client can handle the ChannelTerminatedException that I've thrown manually and send a message to the user, etc. Instead my exception is getting wrapped in a System.ServiceModel.Diagnostics.CallbackException. Ok, fine. Except here's the catch: this CallbackException doesn't exist in the ServiceModel library and there appears to be no way for me to handle it except as a generic Exception, which does me no good for my unit tests. What the heck is going on here? Can I disable that somehow and throw the exception that I originally wanted?

Ethben answered 12/2, 2010 at 15:15 Comment(2)
Since you are throwing the exception yourself, can you use another exception that doesn't get wrapped? I'm thinking something that doesn't expose the fact that you are using WCF underneath it all...Gayden
No dice. I changed the ChannelTerminatedException to a StackOverflowException ;-) and it's still getting wrapped in that annoying CallbackException (the one I throw ends up being the inner exception). And then it gives the annoying message: "A user callback threw an exception. Check the exception stack and inner exception to determine the callback that failed." Yes, I know the callback threw an exception! I was the one who threw it! I'm going to do more research and see if there's a WCF flag/parameter somewhere that I can use to disable this behavior.Ethben
E
5

As it turns out, System.ServiceModel.Diagnostics.CallbackException is an internal class stuffed inside a little known assembly called "%SystemRoot%\Microsoft.net\Framework\v3.0\Windows Communication Foundation\SMDiagnostics.dll", which itself contains only internal classes. Well, that stinks because it means that we can never, ever catch that exception. However, I was able to hunt down the class/method that instantiates the above exception (System.ServiceModel.Diagnostics.ExceptionUtility.ThrowHelperCallback(Exception innerException)) and found that it is being called by the virtual method OnFaulted() inside CommunicationObject. So theoretically any class that derives from CommunicationObject (sorry ClientBase<T>) can override that method and tell it not to call ThrowHelperCallback(). Which means that the only viable candidates are classes that derive from ChannelFactoryBase<T>. In theory I could go ahead and implement my own custom channel factory which suppresses the annoying CallbackException but at the moment it's too much work so I guess I'll just have to deal with it.

EDIT: @Jeremy - If I inspect the SOAP envelope coming back over the wire I find that it is giving me a generic fault, as expected, which indicates that the CallbackException is NOT being serialized and thus is NOT being generated on the server.

<s:Body>
    <s:Fault>
        <s:Code>
            <s:Value>s:Receiver</s:Value>
            <s:Subcode>
                <s:Value xmlns:a="http://schemas.microsoft.com/net/2005/12/windowscommunicationfoundation/dispatcher">a:InternalServiceFault</s:Value>
            </s:Subcode>
        </s:Code>
        <s:Reason>
            <s:Text xml:lang="en-US">The server was unable to process the request due to an internal error.  For more information about the error, either turn on IncludeExceptionDetailInFaults (either from ServiceBehaviorAttribute or from the &lt;serviceDebug&gt; configuration behavior) on the server in order to send the exception information back to the client, or turn on tracing as per the Microsoft .NET Framework 3.0 SDK documentation and inspect the server trace logs.</s:Text>
        </s:Reason>
    </s:Fault>
</s:Body>
Ethben answered 16/2, 2010 at 16:27 Comment(3)
I don't think I clarified that CallbackException is generated on the client as a response to an unhandled exception returned by the host. My main point was the generic fault should be handled server side so the client (which could very well be built by someone else in another language) doesn't have to.Mol
OK, +1 to your answer. In reply, I'm using FaultContracts to send over the most common exceptions to be handled gracefully on the client end but more serious and unexpected exceptions would be handled in the Faulted event ideally. That's why I'm a little upset with MS that they steal the exception that I'm trying to throw and wrap it in a class that's internal to them. However, having developed for a handful of MS frameworks I now know that this is merely par for the course.Ethben
How to handle exceptions is one of those things everyone has a different way of doing, but I do agree that it doesn't make sense to throw an inaccessible exception, even if method that throws it is intended to be overridden. At least wrap it in a public type so it can be handled.Mol
M
2

You are seeing the System.ServiceModel.Diagnostics.CallbackException because an exception is occurring on the server. As the exception is occurring outside of the domain of the client application, it is possibly of a type the client doesn't have access to. The callback mechanism handles this by generating the CallbackException you're seeing. This is similar to the System.TypeInitializationException that gets thrown when an unhandled exception occurs when accessing a static member. If you're trying to handle this gracefully, you might want to handle the exception on the server side and close down the socket, which will subsequently trigger an exception on the client that can be handled.

Mol answered 16/2, 2010 at 17:22 Comment(1)
I think the way i phrased it was a bit vague. I've edited the answer to be a little clearer.Mol

© 2022 - 2024 — McMap. All rights reserved.