Previous posts in this series:
There are four “major” contract types in WCF and at this point we’ve covered three of them; the Service Contract, Data Contract and Message Contract. The last one to cover is the Fault Contract. I began writing a post on how to work with Fault Contracts, and I immediately got a distinct feeling of déjà vu. It was as if I had somehow written this post before. Turns out there was a good reason for this, I had written it before. Twice in fact. Being a developer who values the concept of keeping your code DRY (Don’t Repeat Yourself) I’ve decided to re-publish these original posts (with a few tweaks) here. Obviously this will not be keeping with our “Bagel Factory” analogy, so think of it as having a substitute teacher.
Before That Though…
What is and why do we need a Fault Contract? Well, if the days of ASMX based web services there was no built-in way to report exceptions back to the client. For the most part, the only way a client found out something was wrong was that the call to the web service timed out. There was no other explanation. This was a tremendous source of frustration for client developers, and also prompted many a developer and architect to pull their hair out over the prospect of reporting some sort of exception information back to the client. A lot of developers, myself included, broke out the duct tape, chewing gum and bailing wire and attempted to build some sort of notification mechanism that would not leave the clients hanging (no pun intended). These solutions ranged from complicated to outrageously complicated. All met with limited success.
WCF introduced the concept of a Fault Contract. As you’ll see below, a Fault Contract is simply a Data Contract that contains some information (as much or as little as you decide) about what happened that caused the call to fail. When you specify a Fault Contract for an operation what you’re doing is telling WCF that in addition to whatever your return type for that action is, it may also send back a FaultException, which is really just a wrapper for the instance of the Fault Contract you specified. This allows you to wrap your service calls in a Try/Catch block and catch meaningful exceptions.
Your Substitute Teachers (Be Nice!)
First off, Scott Hanselman wrote a terrific blog post on exceptions. It’s a great place to start and if you haven’t read it you should.
Using Fault Contracts is very easy. In fact, you can do it in four steps...
Step 1: Admit your faults
The first thing we need to do is determine what kind of information we want to return back to the client and create a Data Contract that contains that information. You may have several kinds of fault classes, and you should so that you can return specific types of information for specific faults. Kinda like why there isn't just one Exception class in .NET. Although these are data contracts, you should not use them for anything other than sending faults back to the client. Here's a sample fault class:
[DataContract]
public class MyFault
{
private string _message = string.Empty;
public MyFault(string message)
{
_message = message;
}
[DataMember]
public string Message
{
get
{
return _message;
}
set
{
_message = value;
}
}
}
Like any .NET class, you can create a fault class the inherits from another .NET class, BUT that class also needs to be a data contract and be a known type.
Step 2: Find where you may go wrong
Next, we need to let WCF know where these faults might be coming from. This is done at the operation in the service contract. You simply use a FaultContract attribute which lets WCF know what kind of possible fault class to expect:
[ServiceContract]
public interface IFaultyService
{
[OperationContract]
[FaultContract(typeof(MyFault))]
void ThrowMeAnError();
}
You can specify as many faults for the operation as you like, simply add more FaultContract attributes:
[ServiceContract]
public interface IFaultyService
{
[OperationContract]
[FaultContract(typeof(MyFault))]
[FaultContract(typeof(MyOtherFault))]
[FaultContract(typeof(YetAnotherFault))]
void ThrowMeAnError();
}
Step 3: The throw...
Throwing faults from a service is a little different than throwing them from C# or VB code. We need to enclose our fault class in a FaultException<T>, which represents a SOAP exception. This is just as easy as the other steps:
throw new FaultException<MyFault>(new MyFault("This is a fault"), "Demonstration");
The second argument, in this case "Demonstration" is the reason for the fault. And yes, "reason" is the attribute name of the fault too. This can either be a string or a FaultReason object which allows you to send more detailed information about the exception.
Step 4: ... and the catch
If you've done everything correctly, when you generate a reference to your service, in addition to the normal proxy information, you should have a class representing the data contract you created for your fault class. Catching this exception on the client side is similar to throwing it on the server side. It will come wrapped in a FaultException<T> and you will need to specify that to catch it correctly:
.
.
.
catch(FaultException<MyFault> itsMyFault)
{
Console.WriteLine("MyFault was caught");
Console.WriteLine(string.Format(@"Message: {0}", itsMyFault.Detail.Message));
Console.WriteLine(string.Format(@"Reason: {0}", itsMyFault.Reason));
}
.
.
.
As you can see, getting the reason is pretty straight forward. Our message, and any other field me might have added, will be accessible as a member of the Detail child object of our fault.
That's it. See, nothing to it.
Actually, this rabbit hole does go a little deeper…
When you use WCF on the client side, and your service back-end is not necessarily WCF, then you may not necessarily get a WCF-style Fault Contract. Even WCF, being a close cousin of ASP.NET, can send non-service friendly messages down the pipe (sort of it’s version of the “yellow screen of death”). Lets look a little more closely at how exceptions get received by a WCF client and how the WCF channel reacts to exceptions. We’ll start with what happens if you just throw a normal .NET exception. For this example we’ll be looking at a mock up of a service to return prices for stocks based on a ticker symbol.
The service contact:
1: [ServiceContract]
2: public interface IMyWcfService
3: {
4: [OperationContract]
5: decimal GetStockPrice(string symbol);
6: }
And the implementation:
1: public class MyWcfService : IMyWcfService
2: {
3: public decimal GetStockPrice(string symbol)
4: {
5: switch (symbol)
6: {
7: case "MSFT":
8: return (decimal) 1.41;
9: case "IBM":
10: return (decimal).89;
11: case "JAVA":
12: return (decimal).10;
13: default:
14: throw new ArgumentOutOfRangeException("symbol", "bad symbol");
15: }
16: }
17: }
Since this is a contrived demo, we only handle three symbols and return a static value. This does however provide us with an opportunity to see how .NET exceptions work in WCF. If we receive a symbol that is not in our list, we are throwing an ArgumentOtOfRangeException. If we were using this as just a .NET object we wouldn’t have a problem; either our code would catch the exception and do something with it, or it would bubble up to the user.
Our client is a winform application. The important part is the call to our service (via a proxy of course):
1: public partial class Form1 : Form
2: {
3: private readonly MyWcfServiceClient _proxy;
4:
5: public Form1()
6: {
7: InitializeComponent();
8: btnCallService.Click += CallService;
9: _proxy = new MyWcfServiceClient();
10: }
11:
12: public void CallService(object sender, EventArgs e)
13: {
14: lblResult.Text = _proxy.GetStockPrice(txtSymbol.Text).ToString();
15: }
16: }
So all we’re doing is grabbing a value (the ticker symbol) from a text box and passing it as an argument to the service. The result is used to set the value of a label control. When we run this and feed one of the three symbols that our service uses, the expected happens: a value is returned and set to the text of the label. When we provide an unsupported symbol, we get an error:
The exception text should look familiar to anyone who has worked with ASP.NET…
System.ServiceModel.FaultException: 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 <serviceDebug> 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.
Like ASP.NET, WCF will not return detailed exception information to clients as a security measure. Also like ASP.NET, we have the ability to change the configuration to return more detailed exception information. One line nine below, we have set the includeExceptionDetailInFaults value to “True”:
1: <?xml version="1.0" encoding="utf-8" ?>
2: <configuration>
3: <system.serviceModel>
4: ...
5: <behaviors>
6: <serviceBehaviors>
7: <behavior name="Service1Behavior">
8: <serviceMetadata httpGetEnabled="True"/>
9: <serviceDebug includeExceptionDetailInFaults="True" />
10: </behavior>
11: </serviceBehaviors>
12: </behaviors>
13: </system.serviceModel>
14: </configuration>
This results in the details of the error message being passed back to us:
This can be OK for development, but is not a good idea for production.
Let’s make a few changes to our client application:
1: public void CallService(object sender, EventArgs e)
2: {
3: try
4: {
5: lblResult.Text = _proxy.GetStockPrice(txtSymbol.Text).ToString();
6: }
7: catch
8: {
9: MessageBox.Show("Service call failed");
10: }
11: }
By catching the exception we can let the user know that something happened and handle it gracefully. Running the application and use the symbol “ORCL” causes the exception, which is handled:
So, we can just re-submit a supported symbol right…?
Uh-oh.
Turns out that when a WCF service throws a normal .NET exception, it faults the channel. Any subsequent calls to the channel results in a CommunicationObjectFaultedExcption being thrown immediately. The channel cannot be salvaged; your only option is to throw it out and recreate it. The proxy implements an interface called ICommunicatioObject that has a Faulted event that we can subscribe to.A few more changes to our client…:
1: private MyWcfServiceClient _proxy;
2:
3: public Form1()
4: {
5: InitializeComponent();
6: btnCallService.Click += CallService;
7: _proxy = new MyWcfServiceClient();
8: ((ICommunicationObject)_proxy).Faulted += RecreateProxy;
9: }
10:
11: void RecreateProxy(object sender, EventArgs e)
12: {
13: _proxy = new MyWcfServiceClient();
14: }
… and now our client is able to recreate the proxy if a communication fault occurs.
This way of dealing with exceptions in WCF works well if you are a client of a service that you may not control or have the metadata needed to create a fault contract for. Otherwise, using the FaultException class and/or WCF fault contracts are a better way to go. I’ll be covering them more in detail in the next couple posts.
Code on!