(See WCF error-handling strategies part 1 here)
Deciding on the right error-handling strategy for your WCF services and clients can be tricky. But if you’re not 100% clear on exactly what strategy you are using and how it will behave under different situations at runtime, then you may be in for some nasty surprises later on (especially if your code has already been deployed to a production server). So plan it and implement it from day one. Do not leave it as a ‘TODO’ task on your whiteboard.
I have tried various different approaches and the one that seems to work best is for WCF contracts to explicitly declare the types of FaultException<T> that a service operation may throw. This way, clients are fully aware of what exceptions to expect and can add the necessary exception-handling. This spreads the responsibility somewhat – it is down to the service developer to keep service contracts up to date with any new types of FaultException<T> that are thrown. And it is down to the client developer to put the required exception-handling in place. Of course, good integration test coverage will ensure there are no leaks on either side of the messaging boundary so always update your tests as new exceptions are added.
Declaring the types of FaultException<T> that a service operation may throw is done via the FaultContract attribute as shown below.
In this example, CreateAccount may throw an exception of type FaultException<ValidationFault>, FaultException<GeneralFault> or just a plain non-generic FaultException (which you obviously don’t have to declare in the contract as it is the default WCF exception type). ValidationFault and GeneralFault are just example custom fault types which indicate to the client application what went wrong, but you can replace these with any fault type you like.
Faced with the example service contract above, the client developer should then provide the following try-catch block to cover all possible outcomes. I haven’t included the code to call the service because that’s covered in other posts. But what really matters is that it’s done within the try block and there are matching catch blocks for each expected exception. Depending on the type of client you are developing (web/desktop/mobile app) you may have global exception handling, or you may surround every service call with a try-catch block, or you may have a helper delegate which catches all exceptions. Exactly how you do it is up to you, so long as it’s exception-safe otherwise your client app could start displaying unwelcome error messages or worse still, crash.
As with all client/service messaging, the actual service calls should be made via a proxy (see this post for more information on developing WCF proxies).
Service Fault Types
So you have your service contract and client exception handling worked out. Now you need the actual fault types which will be passed to the FaultException<T> constructor in your service layer. To make this easier, here is an abstract base fault class which will also act as a helper to create the strongly-typed FaultExceptions. Notice that it is decorated with the DataContract attribute. That’s because the derived fault types need to be serialised by the ServiceModel DataContractSerializer, otherwise message serialisation would fail.
The static Throw method instantiates a strongly-typed FaultException based on the type of Fault you give it, and throws the exception.
And here’s an example of a concrete fault class, derived from Fault, which you might throw if input validation fails in your service (e.g. the user enters an invalid email address). This class has been kept very simple because fault classes should provide the minimum amount of information a client needs and no more. A message explaining what went wrong is usually sufficient, which is exactly what the base Fault.ToString method does. All technical details such as exception stack trace should be kept hidden from the client and logged server-side. You don’t want to reveal the intellectual property and technical details about your services to customers or potential hackers.
Throwing a fault from the service
Now raising a specific exception from within your service layer is very straightforward as shown in the following example which assumes email validation has just failed in some previous line of code.
Most importantly, don’t forget that the fault messages you put here could and most likely will end up being displayed on-screen to your users at some point, so always make sure the grammar, punctuation and spelling is correct. Don’t spoil good software with amateurish user messages. This is just a simple example but for globalisation purposes, you would probably want to keep fault messages like this in string table resource files or a database rather than hard-coding them.
One final note – you can add automated WCF parameter validation to your services by referencing a NuGet package such as WCF.Validation.Engine which sets model state validity in a very similar way to how MVC view model validation works, but that will require a whole separate post!