What If There's No Error Handling?
Suppose I write a WCF web service with no try-catch blocks and no error handling. What happens when my web service throws an exception? Since I don't have any error handling, WCF catches the exception for me. It sends the client (the program that called my web service) a FaultException with the following message:
"The server was unable to process the request due to an internal error."
Whenever there's an unhandled exception, that's all the information the client gets. WCF doesn't send the client the exception message or the stack trace or any of the other information that was contained in the exception.
There are a number of reasons why WCF does this. One reason is that the Exception class is specific to .Net. WCF was designed to make web services that can be called by anyone, including clients that are not written in .Net. The client program can be written in Java or PHP or a variety of other languages. So WCF doesn't assume that the clients were written in .Net, and it doesn't send them .Net specific responses.
Another reason WCF doesn't send the client more information is that this might not be safe. It's not safe to provide the stack trace to anyone that may call. Detailed error messages are also risky. For example, it's not safe to inform the caller that the database insert failed because user name "andrew.fenster" is already in use. The safer practice is to write this information to an error log and provide much less detailed error information to the caller.
Providing More Information With FaultExceptions
A bare FaultException may be safe, but it doesn't provide enough information. I may not want to pass the full stack trace, but I want to provide at least basic information about what went wrong. WCF provides multiple ways to do this.
The FaultException class itself includes several ways to provide more information. The FaultException class includes these constructors (among others):
public FaultException(string reason);
public FaultException(string reason, FaultCode code);
public FaultException(FaultReason reason);
public FaultException(FaultReason reason, FaultCode code);
The simplest way to provide information is the first of these:
try
{
// do something
}
catch (Exception ex)
{
myLogger.LogException(ex);
throw new FaultException("Your request timed out. Please try again later.");
}
The client can capture the FaultException and read the message as follows:
try
{
// call the web service
}
catch (FaultException ex)
{
Console.WriteLine(ex.Message);
}
As you can see from the FaultException constructors, you can create a FaultException with a string, a FaultCode, a FaultReason or some combination of these.
The FaultReason class lets you provide the same error message in multiple languages. If you look at the list of FaultException constructors, you will see that you can either provide an error message in a string or a collection of error messages in a FaultReason. You don't need both.
A FaultCode allows you to provide a code (a string) to tell the client what went wrong. For example:
try
{
// do something
}
catch (Exception ex)
{
myLogger.LogException(ex);
FaultCode code = new FaultCode("Invalid Operation");
throw new FaultException("Customer name cannot be null.", code);
}
The client can catch the FaultException and read the FaultCode as follows:
try
{
// call the web service
}
catch (FaultException ex)
{
Console.WriteLine("FaultCode: " + ex.Code);
Console.WriteLine("Message: " + ex.Message);
}
The FaultException class
The FaultException class gives you several ways to inform the client about what went wrong. Sometimes, however, you may want more.
The FaultException class is derived from the FaultException class. As with the FaultException class, you can pass in some combination of string, FaultCode and FaultReason. You can also pass in some additional value T. T can be a value or an entire class object. For example, you could define your own error class:
[DataContract]
public class ErrorMessage
{
private Guid ticketNumber;
[DataMember]
public Guid TicketNumber
{
get { return ticketNumber; }
set { ticketNumber = value; }
}
[DataMember]
public string Message
{
get { return "An error has occurred. For more information,
call us and tell us your ticket number."; }
} public ErrorMessage(Guid newTicket)
{
ticketNumber = newTicket;
}
}
You could use this as follows:
try
{
// Do something
}
catch (Exception ex)
{
Guid ticket = myLogger.LogException(ex);
ErrorMessage message = new ErrorMessage(ticket);
throw new FaultException(message);
}
The client can catch this FaultException as follows:
try
{
// call the web service
}
catch (FaultException ex)
{
Guid ticket = ex.Detail.TicketNumber;
string message = ex.Detail.Message;
}
Of course you could also throw in a FaultCode or a FaultReason, since FaultException is derived from FaultException. FaultException includes these constructors:
public FaultException(T detail);
public FaultException(T detail, string reason);
public FaultException(T detail, FaultReason reason);
public FaultException(T detail, string reason, FaultCode code);
public FaultException(T detail, FaultReason reason, FaultCode code);
When you use the FaultException class, T can be any type, provided in can be serialized. If you are going to use your own custom class, you should define a DataContract for your class, as shown above.
FaultContracts
If you are going to use the FaultException class, you need to create a FaultContract. The FaultContract tells the client program what type of Faults each method can throw. For example:
[ServiceContract]
interface IMyService
{
[OperationContract]
[FaultContract(typeof(ErrorMessage))]
int DoSomething();
}
This FaultContract informs the client that when it calls DoSomething( ) it may receive a fault of type FaultException.
You can specify more than one fault type. For example:
[ServiceContract]
interface IMyService
{
[OperationContract]
[FaultContract(typeof(ErrorMessage))]
[FaultContract(typeof(Guid))]
int DoSomething();
}
Now my method can throw either FaultException or FaultException.
If a web service throws a FaultExeption of a type not declared in the ServiceContract, it will not reach the client. For example, if the ServiceContract says I will throw a FaultException, and my service instead throws a FaultException, WCF will block my fault. Instead, it will send the client a bare FaultException with the generic message "The server was unable to process the request due to an internal error."
Best Practices
There are no shortage of people offering their own advice about error handling. I have only a few points to make.
First and most important, you should be very cautious about providing detailed information to the client about what went wrong. Most error details are either a security risk or simply irrelevant to the client. For example, the Stack Trace contains details about your code which should not be revealed. Even if the client is another division within your own company, they don't need you to send them the Stack Trace. If you sent them the Stack Trace, what would they do with it? Likewise, it's not a good idea to simply catch exceptions and pass the exception Message to the client.
There are only a few types of messages that the client may care about. For example, if the client did not provide a required field, informing the client might be useful. If the system is down temporarily, you could tell the client to try later. Error messages that reveal details about your code, however, don't help the client but do provide security risks.
Using an ErrorMessage class like the one shown above may be sufficient. The client is informed that something went wrong. It anyone needs more information, they can provide you with a ticket number, and you can look up the error in the error log.
One other suggestion: if you are going to provide any significant error information, it would be best to have a FaultContract. Even though the basic FaultException class allows you to pass a message and a FaultCode and a FaultReason (and a few other things not discussed in this article), it makes sense to forego these and use a FaultException, with all your error information inside the detail object T. That way the client knows exactly what to expect and doesn't try reading a value from the FaultReason when there's no FaultReason provided.
In conclusion, WCF provides you with a lot of options for error handling. As long as you think carefully about what information you are providing and the format in which you provide it, you should come out fine.
Suppose I write a WCF web service with no try-catch blocks and no error handling. What happens when my web service throws an exception? Since I don't have any error handling, WCF catches the exception for me. It sends the client (the program that called my web service) a FaultException with the following message:
"The server was unable to process the request due to an internal error."
Whenever there's an unhandled exception, that's all the information the client gets. WCF doesn't send the client the exception message or the stack trace or any of the other information that was contained in the exception.
There are a number of reasons why WCF does this. One reason is that the Exception class is specific to .Net. WCF was designed to make web services that can be called by anyone, including clients that are not written in .Net. The client program can be written in Java or PHP or a variety of other languages. So WCF doesn't assume that the clients were written in .Net, and it doesn't send them .Net specific responses.
Another reason WCF doesn't send the client more information is that this might not be safe. It's not safe to provide the stack trace to anyone that may call. Detailed error messages are also risky. For example, it's not safe to inform the caller that the database insert failed because user name "andrew.fenster" is already in use. The safer practice is to write this information to an error log and provide much less detailed error information to the caller.
Providing More Information With FaultExceptions
A bare FaultException may be safe, but it doesn't provide enough information. I may not want to pass the full stack trace, but I want to provide at least basic information about what went wrong. WCF provides multiple ways to do this.
The FaultException class itself includes several ways to provide more information. The FaultException class includes these constructors (among others):
public FaultException(string reason);
public FaultException(string reason, FaultCode code);
public FaultException(FaultReason reason);
public FaultException(FaultReason reason, FaultCode code);
The simplest way to provide information is the first of these:
try
{
// do something
}
catch (Exception ex)
{
myLogger.LogException(ex);
throw new FaultException("Your request timed out. Please try again later.");
}
The client can capture the FaultException and read the message as follows:
try
{
// call the web service
}
catch (FaultException ex)
{
Console.WriteLine(ex.Message);
}
As you can see from the FaultException constructors, you can create a FaultException with a string, a FaultCode, a FaultReason or some combination of these.
The FaultReason class lets you provide the same error message in multiple languages. If you look at the list of FaultException constructors, you will see that you can either provide an error message in a string or a collection of error messages in a FaultReason. You don't need both.
A FaultCode allows you to provide a code (a string) to tell the client what went wrong. For example:
try
{
// do something
}
catch (Exception ex)
{
myLogger.LogException(ex);
FaultCode code = new FaultCode("Invalid Operation");
throw new FaultException("Customer name cannot be null.", code);
}
The client can catch the FaultException and read the FaultCode as follows:
try
{
// call the web service
}
catch (FaultException ex)
{
Console.WriteLine("FaultCode: " + ex.Code);
Console.WriteLine("Message: " + ex.Message);
}
The FaultException
The FaultException class gives you several ways to inform the client about what went wrong. Sometimes, however, you may want more.
The FaultException
[DataContract]
public class ErrorMessage
{
private Guid ticketNumber;
[DataMember]
public Guid TicketNumber
{
get { return ticketNumber; }
set { ticketNumber = value; }
}
[DataMember]
public string Message
{
get { return "An error has occurred. For more information,
call us and tell us your ticket number."; }
} public ErrorMessage(Guid newTicket)
{
ticketNumber = newTicket;
}
}
You could use this as follows:
try
{
// Do something
}
catch (Exception ex)
{
Guid ticket = myLogger.LogException(ex);
ErrorMessage message = new ErrorMessage(ticket);
throw new FaultException
}
The client can catch this FaultException as follows:
try
{
// call the web service
}
catch (FaultException
{
Guid ticket = ex.Detail.TicketNumber;
string message = ex.Detail.Message;
}
Of course you could also throw in a FaultCode or a FaultReason, since FaultException
public FaultException(T detail);
public FaultException(T detail, string reason);
public FaultException(T detail, FaultReason reason);
public FaultException(T detail, string reason, FaultCode code);
public FaultException(T detail, FaultReason reason, FaultCode code);
When you use the FaultException
FaultContracts
If you are going to use the FaultException
[ServiceContract]
interface IMyService
{
[OperationContract]
[FaultContract(typeof(ErrorMessage))]
int DoSomething();
}
This FaultContract informs the client that when it calls DoSomething( ) it may receive a fault of type FaultException
You can specify more than one fault type. For example:
[ServiceContract]
interface IMyService
{
[OperationContract]
[FaultContract(typeof(ErrorMessage))]
[FaultContract(typeof(Guid))]
int DoSomething();
}
Now my method can throw either FaultException
If a web service throws a FaultExeption
Best Practices
There are no shortage of people offering their own advice about error handling. I have only a few points to make.
First and most important, you should be very cautious about providing detailed information to the client about what went wrong. Most error details are either a security risk or simply irrelevant to the client. For example, the Stack Trace contains details about your code which should not be revealed. Even if the client is another division within your own company, they don't need you to send them the Stack Trace. If you sent them the Stack Trace, what would they do with it? Likewise, it's not a good idea to simply catch exceptions and pass the exception Message to the client.
There are only a few types of messages that the client may care about. For example, if the client did not provide a required field, informing the client might be useful. If the system is down temporarily, you could tell the client to try later. Error messages that reveal details about your code, however, don't help the client but do provide security risks.
Using an ErrorMessage class like the one shown above may be sufficient. The client is informed that something went wrong. It anyone needs more information, they can provide you with a ticket number, and you can look up the error in the error log.
One other suggestion: if you are going to provide any significant error information, it would be best to have a FaultContract. Even though the basic FaultException class allows you to pass a message and a FaultCode and a FaultReason (and a few other things not discussed in this article), it makes sense to forego these and use a FaultException
In conclusion, WCF provides you with a lot of options for error handling. As long as you think carefully about what information you are providing and the format in which you provide it, you should come out fine.
No comments:
Post a Comment