Why Exceptions Don't Cross Service Boundaries
A regular .NET exception is a CLR construct with a stack trace, an object type, and potentially a reference to internal objects — none of which mean anything to a client that might not even be running on .NET, such as a Java or JavaScript client calling over SOAP or REST. Because of this, WCF never lets a raw exception cross the wire; by default, an unhandled exception thrown from a service operation is caught by the runtime and converted into a generic FaultException with a message like 'The server was unable to process the request due to an internal error,' deliberately stripped of details to avoid leaking implementation internals or sensitive data to callers. You can opt into richer diagnostics during development by setting <serviceDebug includeExceptionDetailInFaults="true"/> in the service behavior configuration, which includes the full exception type and stack trace in the fault — but this should never ship to production, since it hands attackers a roadmap of your internal implementation.
Cricket analogy: It's like a team's internal dressing-room analysis of a batting collapse staying strictly internal — the press conference afterward gives the public a generic 'we didn't execute our plans today' rather than reading out every technical flaw identified in the video review.
FaultContract and FaultException<T>
When you want a client to be able to catch a specific, structured error — say, an OrderValidationFault carrying an error code and a list of invalid fields — you declare it explicitly with [FaultContract(typeof(OrderValidationFault))] on the operation contract, define OrderValidationFault as a DataContract-decorated class, and throw it from the service using throw new FaultException<OrderValidationFault>(fault, reason) rather than throwing OrderValidationFault directly. This is deliberate: FaultContract is part of the service's public interface, documented in the WSDL/metadata just like the operation's parameters and return type, which means clients generated from that metadata get a strongly typed way to catch FaultException<OrderValidationFault> and inspect its Detail property, instead of parsing a generic error string.
Cricket analogy: It's like the ICC publishing a specific, documented code of conduct violation (Level 2, Article 2.1) rather than a vague 'player misconduct' note, so match referees and the public alike know exactly which structured category applies.
[DataContract]
public class OrderValidationFault
{
[DataMember] public string ErrorCode { get; set; }
[DataMember] public List<string> InvalidFields { get; set; }
}
[ServiceContract]
public interface IOrderService
{
[OperationContract]
[FaultContract(typeof(OrderValidationFault))]
void SubmitOrder(Order order);
}
public class OrderService : IOrderService
{
public void SubmitOrder(Order order)
{
if (order.LineItems.Count == 0)
{
var fault = new OrderValidationFault
{
ErrorCode = "EMPTY_ORDER",
InvalidFields = new List<string> { "LineItems" }
};
throw new FaultException<OrderValidationFault>(
fault, new FaultReason("Order has no line items."));
}
// ... process order
}
}IErrorHandler for Centralized Fault Translation
Sprinkling try/catch blocks with FaultException throws inside every operation gets repetitive and inconsistent across a large service. Implementing System.ServiceModel.Dispatcher.IErrorHandler lets you centralize this: HandleError(Exception error) is called for every unhandled exception (a good place to log it, since this runs regardless of whether the client gets details), and ProvideFault(Exception error, MessageVersion version, ref Message fault) lets you inspect the exception type and construct the exact SOAP fault message to send back — for instance, converting any SqlException into a generic 'service temporarily unavailable' fault while converting a custom ValidationException into a properly typed FaultException<OrderValidationFault>. You register the error handler via a custom IServiceBehavior applied through an attribute or through configuration, which keeps operation code focused purely on business logic instead of fault translation plumbing.
Cricket analogy: It's like a team having one designated media manager who reviews every incident (an on-field send-off, an injury, a controversial dismissal) and decides the single official public statement, rather than each individual player giving their own inconsistent quotes to reporters.
IErrorHandler.HandleError should always return false unless you are deliberately suppressing the fault (rare); returning true tells WCF the error handler has fully handled the exception and no fault at all will be sent to the client, which usually just leaves the caller hanging with a communication-level exception instead.
Faults and Channel Faulting
Beyond the message-level fault sent to the client, receiving certain kinds of errors — particularly unhandled exceptions that aren't converted to a well-formed FaultException, or lower-level communication errors like a timeout — can put the WCF channel itself into a Faulted state (ICommunicationObject.State == CommunicationState.Faulted). A faulted channel cannot be reused for further calls; calling Close() on a faulted channel throws a CommunicationObjectFaultedException, so the correct client-side pattern is to check the exception type in a catch block and call Abort() instead of Close() when a fault or communication exception occurs, then create a brand-new channel/proxy for subsequent calls. This is a classic WCF pitfall: developers who wrap every client proxy in a using block get bitten because Dispose() internally calls Close(), which throws a second exception that masks the original one if the channel already faulted.
Cricket analogy: A faulted channel is like a bat that's cracked mid-innings — you can't keep using it for the next ball; the batter must discard it entirely and call for a brand-new bat rather than trying to tape up the crack and continue.
Never wrap a WCF client proxy in a using block without additional handling. If the channel faults during the call, the implicit Dispose()/Close() at the end of the using block throws CommunicationObjectFaultedException, which obscures the original exception. Use a try/catch/finally that calls Abort() on FaultException or CommunicationException, and Close() only on the success path.
- WCF never lets raw .NET exceptions cross the wire; unhandled exceptions become a generic, detail-stripped FaultException by default.
- includeExceptionDetailInFaults="true" is a development-only diagnostic aid and must never ship to production.
- FaultContract plus a DataContract fault type gives clients a strongly typed, WSDL-documented way to catch and inspect specific errors.
- Throw FaultException<T> with your fault detail object rather than throwing the fault type directly.
- IErrorHandler centralizes exception-to-fault translation and logging instead of scattering try/catch blocks across every operation.
- HandleError should return false in almost all cases so WCF still sends an appropriate fault to the caller.
- A faulted channel must be aborted, not closed, and replaced with a new proxy — never reused or wrapped blindly in a using block.
Practice what you learned
1. What does WCF do by default when a service operation throws an unhandled .NET exception?
2. Why should includeExceptionDetailInFaults never be enabled in production?
3. What is the correct way to throw a typed fault detail from a service operation decorated with [FaultContract(typeof(OrderValidationFault))]?
4. What is the purpose of implementing IErrorHandler in a WCF service?
5. What should a WCF client do when a proxy's channel enters the Faulted state?
Was this page helpful?
You May Also Like
WCF Instancing and Concurrency
Understand the InstanceContextMode and ConcurrencyMode settings that control how many service instances WCF creates and how many threads can touch each one at once.
WCF Reliable Messaging
Learn how WCF's WS-ReliableMessaging support guarantees ordered, exactly-once (or at-least-once) message delivery over unreliable transports like HTTP.
WCF Throttling
Learn how ServiceThrottlingBehavior caps concurrent calls, sessions, and instances to protect a WCF service from being overwhelmed under load.
Related Reading
Related Study Notes in Microsoft Technologies
Browse all study notesWindows 10 / UWP Development Study Notes
.NET · 30 topics
Microsoft TechnologiesWindows Batch Scripting Study Notes
Batch · 30 topics
Microsoft TechnologiesMFC (Microsoft Foundation Classes) Study Notes
C++ · 30 topics
Microsoft TechnologiesSilverlight Study Notes
.NET · 30 topics
Microsoft TechnologiesXAML Study Notes
.NET · 30 topics
Microsoft TechnologiesWPF (Windows Presentation Foundation) Study Notes
.NET · 30 topics