Fault Handling in WCF Services

Robert Green

MCW Technologies

Download

Articles in this series

Introduction

In all applications you write, you need to trap for errors. This includes data entry errors, such as a user entering invalid information in a text box. You also need to trap for exceptions, which are unexpected errors that cause execution to stop. For example, if you have code to read a file and that file does not exist, the .NET Runtime will throw an exception.

If you handle the exception in a Windows or Web application, you can display a user-friendly message explaining what happened and what users can do next. If you don’t handle the exception, the .NET Runtime will handle it and display its own message.

A Windows Communication Foundation service is a .NET application. In your service code, you can handle exceptions using try-catch blocks just as you would in a Windows or Web application. You can also use familiar techniques to log errors.

However, the service cannot return a .NET exception to the client. The WCF service and the client communicate by passing SOAP messages. If an exception occurs, the WCF runtime serializes the exception into XML and passes that to the client.

In this tutorial, you will see how to handle exceptions in WCF services. You will see what information the service returns to the client by default and then explore various techniques you can use to return more detail about exceptions.

Review the Sample Application

In Visual Studio 2008, select File | Open | Project/Solution to display the Open Project dialog box. Navigate to the folder where you downloaded this tutorial’s sample project. Select FaultHandlingDemo.sln and click OK to open the project. The sample application includes three projects. The InventoryServiceLibrary project represents a WCF service that clients can call to query a SQL Server database and manage inventory information for products. The WebHost project uses the ASP.NET Development Server to host the service. The WindowsClient project contains the user interface. This sample is the finished version of the application you built in the first tutorial in this series.

Press F5 to run the application. In the Manage Inventory form, enter 1 in the Product text box and click Get in stock. The form calls the GetInStock method of the WCF service and displays the units in stock on the form (see Figure 1).

Figure 1. You called the WCF service to retrieve the units in stock for a product.

Click Get product. The form calls the GetProduct method of the WCF service. The service retrieves the product’s information and returns it to the client, which then displays the product information on the form (see Figure 2). Close the form. Leave the ASP.NET Development Server running.

Figure 2. You called the WCF service to retrieve additional information for a product.

What happens if an unhandled exception occurs in the service? For example, suppose the service cannot communicate with the SQL Server database. To simulate this situation, select Control Panel | Administrative Tools from the Windows Start menu. If you have not configured the Start menu to display Administrative Tools, open the Control Panel, click Classic View and then click on Administrative Tools. In the Windows Explorer window that appears, double-click the Services shortcut to display the Services applet. Select the entry for SQL Server Express and select the Stop link.

Return to Visual Studio. Press CTRL+F5 to run the application in release mode. In the Manage Inventory form, enter 1 in the Product text box and click Get in stock. The client calls the service, which throws an exception when it cannot connect to SQL Server. The WCF Runtime sends back to the client the message shown in Figure 3.

Figure 3. The WCF service returns this message when an unhandled exception occurs.

By default, the service does not send any information explaining what happened. WCF does not reveal details about what the service does internally. This makes the service more secure. Click Quit to dismiss the dialog box and exit the application.

Provide Additional Information When an Exception Occurs

As you develop and test the service, however, it would be more convenient to see additional details regarding failures. In the Solution Explorer, double-click Web.config. Scroll to the bottom of the file and make the following change in bold:

<serviceBehaviors>

  <behavior name="ServiceBehavior">

    <serviceMetadata httpGetEnabled="true"/>

    <serviceDebug includeExceptionDetailInFaults="true"/>

  </behavior>

</serviceBehaviors>

Save your changes. Press CTRL+F5 to run the application. In the Manage Inventory form, enter 1 in the Product text box and click Get in stock. The WCF Runtime sends back to the client the message shown in Figure 4. Click Quit to dismiss the dialog box and exit the application.

Figure 4. You configured the WCF service to return additional information when an unhandled exception occurs.

Use the FaultException Class to Handle Exceptions

During development of your WCF services, you should include exception details in faults so that you and the testers have some understanding of what happened if the application fails.

When you deploy the service, you should disable this and explicitly handle exceptions. In this tutorial, you will see several techniques for handling faults in your services. First, you will use the FaultException class. When an exception occurs, your code can throw a new instance of this class and pass to the constructor a string that describes the problem.

In the Solution Explorer, double-click the InventoryService file. To handle connection and data reading faults, add the following code in bold to the GetInStock method:

//C#

try

{

  cnn.Open();

}

catch

{

  throw new FaultException(

    "There was a problem connecting to the database.");

}

using (SqlDataReader dataReader =

  cmd.ExecuteReader())

{

  try

  {

    while (dataReader.Read())

    {

      unitsInStock = dataReader.GetInt16(1);

    }

  }

  catch

  {

    throw new FaultException(

      "There was a problem reading from the database.");

  }

}

'VB

Try

  cnn.Open()

Catch

  Throw New FaultException( _

    "There was a problem connecting to the database.")

End Try

Using dataReader = cmd.ExecuteReader()

  Try

    While (dataReader.Read())

      unitsInStock = dataReader.GetInt16(1)

    End While

  Catch

    Throw New FaultException( _

      "There was a problem reading from the database.")

  End Try

End Using

If the service cannot connect to the database, the service will return a message to that affect. If the service can connect, it sends a query to retrieve the units in stock for the selected product. When the code calls the GetInt16 method of the DataReader, an exception will occur because the query returns only one column, not two. The service will return the message that there is a problem reading from the database.

In the Solution Explorer, right-click the Form1 file and select View Code. To catch exceptions returns by the service, add the following code in bold to the getInStockButton_Click method:

//C#

try

{

  inStockLabel.Text = string.Format("{0} units are in stock",

    proxy.GetInStock(Convert.ToInt32(productIdTextBox.Text)));

}

catch (FaultException faultEx)

{

  MessageBox.Show(faultEx.Message);

}

'VB

Try

  inStockLabel.Text = String.Format("{0} units are in stock", _

    proxy.GetInStock(Convert.ToInt32(productIdTextBox.Text)))

Catch faultEx As FaultException

  MessageBox.Show(faultEx.Message)

End Try

Save your changes and rebuild the solution. Press CTRL+F5 to run the application in release mode. In the Manage Inventory form, enter 1 in the Product text box and click Get in stock. The client displays the message shown in Figure 5. Click OK to dismiss the message.

Figure 5. The WCF service now returns a user-friendly message when it cannot connect to the database.

Return to the Service applet and restart the SQL Server Express service. Return to the form and click Get in stock. The client displays the message shown in Figure 6. Click OK to dismiss the message. Close the form.

Figure 6. The WCF service now returns a user-friendly message when it cannot read from the database.

Use the FaultCode Class so Clients can Distinguish Fault Causes

The users are happy, because the error messages are short and understandable. The client application developers can trap for exceptions, but they have no way of distinguishing between various faults unless they write code to parse the Message property of the FaultException object.

The FaultCode class represents a SOAP fault code. When you throw a FaultException in your service code, you can pass an instance of the FaultCode class to the FaultException constructor. The service will then return the fault code to the client, which can then react differently based on the type of exception.

Return to the InventoryService file. To return a fault code to the client when an exception occurs, make the following changes in bold to the GetInStock method:

//C#

catch

{

  throw new FaultException(

    "There was a problem connecting to the database.",

    new FaultCode("ConnectionFault"));

}

catch

{

  throw new FaultException(

    "There was a problem reading from the database.",

    new FaultCode("DataReaderFault"));

}

'VB

Catch

  Throw New FaultException( _

    "There was a problem connecting to the database.", _

    New FaultCode("ConnectionFault"))

End Try

Catch

  Throw New FaultException( _

    "There was a problem reading from the database.", _

  New FaultCode("DataReaderFault"))

End Try

Return to the Form1 file. To display a different message based on the fault code, make the following changes in bold to the getInStockButton_Click method:

//C#

catch (FaultException faultEx)

{

  switch (faultEx.Code.Name)

  {

    case "ConnectionFault":

      MessageBox.Show(faultEx.Message +

        "\n\n Try again later.", "Connection problem");

      break;

    case "DataReaderFault":

      MessageBox.Show(faultEx.Message +

        "\n\n Contact the administrator.", "Data problem");

      break;

    default:

      MessageBox.Show(faultEx.Message +

        "\n\n Contact the administrator.", "Unknown problem");

      break;

  }

}

'VB

Catch faultEx As FaultException

  Select Case faultEx.Code.Name

    Case "ConnectionFault"

      MessageBox.Show(faultEx.Message & vbCrLf & vbCrLf & _

        "Try again later.", "Connection problem")

    Case "DataReaderFault"

      MessageBox.Show(faultEx.Message & vbCrLf & vbCrLf & _

        "Contact the administrator.", "Data problem")

    Case "ConnectionFault"

      MessageBox.Show(faultEx.Message & vbCrLf & vbCrLf & _

        "Contact the administrator.", "Unknown problem")

  End Select

End Try

Save your changes and rebuild the solution. Press CTRL+F5 to run the application. Return to the Service applet and stop the SQL Server Express service. In the Manage Inventory form, enter 1 in the Product text box and click Get in stock. The client displays the message shown in Figure 7. Click OK to dismiss the message.

Figure 7. The client displays this message when the service cannot connect to the database.

Return to the Service applet and restart the SQL Server Express service. Return to the form and click Get in stock. The client displays the message shown in Figure 8. Click OK to dismiss the message. Close the form.

Figure 8. The client displays this message when the service cannot read from the database.

The client application code can now distinguish between different exceptions and execute different code depending on what caused the exception.

Throw Strongly Typed SOAP Faults to Provide More Detail to Clients

Currently, the WCF service throws one type of exception: a FaultException. Essentially, the service throws a generic fault, although the fault code enables the client to distinguish the reason for the fault. You could throw faults that are more specific. For example, you could throw a SqlException object when the service can’t connect to or read from the database.

This, however, violates one of the principles of service-orientation. In a service-orientated application, services and client communicate by passing messages. Neither is aware of how the other is constructed. Throwing a SqlException object from a WCF service assumes clients know about .NET exception types.

A more practical problem is that clients would need to know how to handle a SqlException object. While a .NET client certainly could, a Java application or VBA code in an Excel workbook would not.

Rather than throw .NET exceptions, you can throw strongly typed SOAP faults. To do that, you first create classes that represent faults. The properties of these classes can store detailed information about the faults. You then add these classes to the service’s data contract. Finally, you use the FaultContractAttribute to identify which operations can throw which SOAP fault.

In the Solution Explorer, open the IInventoryService file. Add the following code to define two strongly-typed fault classes, one for connection faults and one for data reader faults:

//C#

[DataContract]

public class ConnectionFault

{

  [DataMember]

  public string Issue { get; set; }

  [DataMember]

  public string Details { get; set; }

}

[DataContract]

public class DataReaderFault

{

  [DataMember]

  public string Issue { get; set; }

  [DataMember]

  public string Details { get; set; }

}

'VB

<DataContract()> _

Public Class ConnectionFault

  Private issueValue As String

  <DataMember()> _

  Public Property Issue() As String

    Get

      Return issueValue

    End Get

    Set(ByVal value As String)

      issueValue = value

    End Set

  End Property

  Private detailsValue As String

  <DataMember()> _

  Public Property Details() As String

    Get

      Return detailsValue

    End Get

    Set(ByVal value As String)

      detailsValue = value

    End Set

  End Property

End Class

<DataContract()> _

Public Class DataReaderFault

  Private issueValue As String

  <DataMember()> _

  Public Property Issue() As String

    Get

      Return issueValue

    End Get

    Set(ByVal value As String)

      issueValue = value

    End Set

  End Property

  Private detailsValue As String

  <DataMember()> _

  Public Property Details() As String

    Get

      Return detailsValue

    End Get

    Set(ByVal value As String)

      detailsValue = value

    End Set

  End Property

End Class

The Issue property of each class will contain the message clients will display to users. The Details property will contain the specifics of the exception.

Make the following changes in bold to the IInventoryService interface to specify that the GetInStock method can throw these strongly-typed SOAP faults:

//C#

[FaultContract(typeof(ConnectionFault))]

[FaultContract(typeof(DataReaderFault))]

[OperationContract]

Int16 GetInStock(int productId);

'VB

<FaultContract(GetType(ConnectionFault))> _

<FaultContract(GetType(DataReaderFault))> _

<OperationContract()> _

Function GetInStock(ByVal productId As Integer) As Short

Return to the InventoryService file. To throw strongly typed connection and data reading faults, make the following changes in bold to the GetInStock method:

//C#

try

{

  cnn.Open();

}

catch (Exception ex)

{

  var connectionFault = new ConnectionFault();

  connectionFault.Issue = "Problem connecting to the database";

  connectionFault.Details = ex.Message;

  throw new FaultException<ConnectionFault>(connectionFault);

}

using (SqlDataReader dataReader =cmd.ExecuteReader())

{

  try

  {

    while (dataReader.Read())

    {

      unitsInStock = dataReader.GetInt16(1);

    }

  }

  catch (Exception ex)

  {

    var dataReaderFault = new DataReaderFault();

    dataReaderFault.Issue = "Problem reading the database";

    dataReaderFault.Details = ex.Message;

    throw new FaultException<DataReaderFault>(dataReaderFault);

  }

}

'VB

Try

  cnn.Open()

Catch ex As Exception

  Dim connFault As New ConnectionFault

  connFault.Issue = "Problem connecting to the database"

  connFault.Details = ex.Message

  Throw New FaultException(Of ConnectionFault)(connFault)

End Try

Using dataReader = cmd.ExecuteReader()

  Try

    While (dataReader.Read())

      unitsInStock = dataReader.GetInt16(1)

    End While

  Catch ex As Exception

    Dim dataFault As New DataReaderFault

    dataFault.Issue = "Problem connecting to the database"

    dataFault.Details = ex.Message

    Throw New FaultException(Of DataReaderFault)(dataFault)

  End Try

End Using

Save your changes and build the solution. You have modified the data and service contracts, so you will need to update the service reference in the client. In the Solution Explorer, expand the Service References node in the WindowsClient project if necessary. Right-click on InventoryService and select Update Service Reference.

Return to the Form1 file. To trap for the strongly-typed faults, make the following changes in bold to the getInStockButton_Click method:

//C#

try

{

  inStockLabel.Text = string.Format("{0} units are in stock",

    proxy.GetInStock(Convert.ToInt32(productIdTextBox.Text)));

}

catch (FaultException<ConnectionFault> connectionFault)

{

  MessageBox.Show(

    string.Format("{0}. Try again later.\n\n{1}",

    connectionFault.Detail.Issue, connectionFault.Detail.Details),

    "Connection problem");

}

catch (FaultException<DataReaderFault> dataReaderFault)

{

  MessageBox.Show(

    string.Format("{0}. Contact the administrator.\n\n{1}",

    dataReaderFault.Detail.Issue, dataReaderFault.Detail.Details),

    "Data problem");

}

catch (Exception ex)

{

  MessageBox.Show(

    string.Format("Contact the administrator.\n\n{0}",

    ex.Message), "Unknown problem");

}

'VB

Try

  inStockLabel.Text = String.Format("{0} units are in stock", _

    proxy.GetInStock(Convert.ToInt32(productIdTextBox.Text)))

Catch connFault As FaultException(Of ConnectionFault)

  MessageBox.Show(String.Format( _

    "{0}. Try again later." & vbCrLf & vbCrLf & "{1}", _

    connFault.Detail.Issue, connFault.Detail.Details), _

    "Connection problem")

Catch dataFault As FaultException(Of DataReaderFault)

  MessageBox.Show(String.Format( _

    "{0}. Contact the administrator." & vbCrLf & vbCrLf & "{1}", _

    dataFault.Detail.Issue, dataFault.Detail.Details), _

    "Data problem")

Catch ex As Exception

  MessageBox.Show(String.Format("Contact the administrator." & _

     vbCrLf & vbCrLf & "{0}", ex.Message), "Data problem")

End Try

Save your changes and rebuild the solution. Press CTRL+F5 to run the application in release mode. Return to the Service applet and stop the SQL Server Express service. In the Manage Inventory form, enter 1 in the Product text box and click Get in stock. The client displays the message shown in Figure 9. Click OK to dismiss the message.

Figure 9. The client displays this message when the service cannot connect to the database.

Return to the Service applet and restart the SQL Server Express service. Return to the form and click Get in stock. The client displays the message shown in Figure 10. Click OK to dismiss the message. Close the form.

Figure 10. The client displays this message when the service cannot read from the database.

As the author of the service, you are now providing to clients both summary and detail information about exceptions. As the author of the client application, you can use this information as you see fit. During development and testing, you might display both the user-friendly message and the actual exception text. Once the application goes into production, you would likely record the exception details and display the user-friendly message.

Conclusion

In this tutorial, you have explored the basics of handling faults in WCF services. You saw that the WCF runtime handles exceptions if you don’t and that, by default, it returns to clients no information on why the exception occurred. You then saw how to configure the services to include details on exceptions. You will typically do this only during development, not after the service goes into production.

You then saw several techniques for handling exceptions. You saw how to use the FaultException class to catch exceptions in your code and return a serialized fault to the client. You also saw how to use the FaultCode class to enable clients to distinguish various types of faults.

Finally, you saw how to use the generic FaultException class to create strongly-typed SOAP faults. This provides clients with a more robust ability to determine what fault occurred.

When a fault occurs, the client application should check to see if the communication channel to the service is still open. If it is in a faulted state, the client will need to recreate the proxy class before the next call. The faults in this tutorial do not fault the channel. In the next tutorial in this series, which covers sessions and instancing, you will see exceptions that do fault the communication channel.

About the Author

Robert Green is a developer, writer, and trainer. He is a senior consultant with MCW Technologies. Robert is a Visual Studio Tools for the Office system MVP and is a co-author of AppDev courseware for Microsoft Visual Basic, Microsoft Visual C#, LINQ and Microsoft Windows Workflow Foundation. Before joining MCW, Robert worked at Microsoft as a Product Manager and also a Program Manager.