100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
.NET Framework

Faults and Exception Handling

Learn how WCF translates .NET exceptions into SOAP faults, how to design typed FaultContracts, and how to centralize fault handling with IErrorHandler.

Reliability and TransactionsIntermediate9 min readJul 10, 2026
Analogies

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.

csharp
[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

Was this page helpful?

Topics covered

#NETFramework#WCFWindowsCommunicationFoundationStudyNotes#MicrosoftTechnologies#FaultsAndExceptionHandling#Faults#Exception#Handling#Exceptions#ErrorHandling#StudyNotes#SkillVeris