C#, .NET Exception Handling Best Practices
The Problem Statement
You have just started working on an existing codebase – an n-Tier project – and noticed that the Exception Handling framework wasn’t as well defined as you would expect. Some specific things that bother you include:
- Lack of a Custom Exception Class
- No clearly defined single layer (of the n-Tiers) which was designated to handle all exceptions. All the tiers seemed to be handling one or more exceptions.
- Anytime an exception needed to be re-thrown (e.g. SqlExceptions and SOAPExceptions), it was through ‘throw ex’ – instead of just throw.
The Solution
Each of the issues listed above are discussed in this post. A sample solution that defines a custom exception class – along with a simple client showing its usage – is included. This can be used as a starting point for any Custom Exception handling module inside a .NET project.
Custom Exception Class
- // This is the main piece of your custom exception class – MyAppException(string message, Exception innerException)
- public MyAppException(string message, Exception innerException) : base(message, innerException)
- {
- if (innerException != null)
- {
- message += innerException.ToString();
- }
- }
n-Tier app – Exception handling
Q) Which tier (layer) should you catch the exception in?
A) Always the business tier. Let the Data Access Layer and any other layers (e.g. the persistence/entity layer) rethrow the exception upwards. Then – in the business layer, wrap the caught exception in your custom exception (e.g. MyAppException) using the method shown above (which appends all the inner exceptions to the MyAppException).
Q) How and where should one display the exception message?
A) Certain exceptions have no business being displayed to the end user. Simply log these in an exception log – and do not rethrow these exceptions. However, a good set of exceptions do need to be displayed to the end user. The exception that we previously caught in our business layer – simply needs to be rethrown with a user-friendly message. This is then is caught in the presentation layer (webforms or winforms) – and the friendly exception message is displayed.
Throw versus ThrowEx
Most exception handling code that you see will look the code below:
- try
- {
- // do some operation that can fail
- }
- catch (Exception ex)
- {
- // do some local cleanup
- throw ex;
- }
With the code above (throw ex), the stack trace is truncated – and only offers information starting from the method that failed. The origin of the exception will always appear to be in application code. However, as we all know, this isn’t always true. Exceptions can originate in various external systems – and eventually get thrown as CLR exceptions. Some common examples include
- SqlException – exceptions generated at the Database driver/Data Access Layer .
- SoapException – exceptions generated outside of the process boundary altogether – and passed into the CLR as a general SOAP exception
So – what is the solution?
The solution is to use throw instead of throw ex. Throw retains the entire stack trace.
- try
- {
- // do some operation that can fail
- }
- catch (Exception ex)
- {
- // do some local cleanup
- throw;
- }
On catching exceptions in database code (inside a database stored procedure)
If you have part of your business logic in a stored procedure (there are good reasons why this provides the best data access layer performance), then chances are your stored procedure is doing its own exception handling. It may be throwing and catching its own database layer exceptions. This is a terrible idea – considering the number of possible different exceptions that can occur in a database. Everything from integrity constraints being violated to incorrect SQL syntaxes to what not. To even attempt to identify the root cause of such errors without the database specific debug information – is nightmarish. I was part of a project where developers routinely went through this nightmarish exercise.
The exercise is meaningless since it is trivial to simply rethrow the stored proc exception to the database driver (and hence up to the data access layer). This provides meaningful information to a developer who needs to identify the source of the error. This changes the entire troubleshooting sequence from a ‘trial and error’ investigation – to a ‘simply look at the detailed exception – e.g. integrity constraint violated’.
Once I made these changes to all the stored procedures (simply re-threw all exceptions in the catch blocks on the stored proc exception handling), life became a lot simpler for the development team on the project. There is no reason to let the database stay secretive about its exceptions. Just throw them up to the data access layer – to provide meaningful troubleshooting info.
An example of how to rethrow exceptions in PLSQL code is shown below (use the RAISE keyword)
DECLARE pe_ratio NUMBER(3,1); BEGIN SELECT price / earnings INTO pe_ratio FROM stocks WHERE symbol = 'XYZ'; -- might cause division-by-zero error INSERT INTO stats (symbol, ratio) VALUES ('XYZ', pe_ratio); COMMIT; EXCEPTION -- exception handlers begin WHEN ZERO_DIVIDE THEN -- handles 'division by zero' error INSERT INTO stats (symbol, ratio) VALUES ('XYZ', NULL); COMMIT
WHEN INVALID_CURSOR THEN
dbms_output.put_line('invalid cursor');
RAISE;
WHEN INVALID_NUMBER THEN
dbms_output.put_line('invalid number');
RAISE;
WHEN ROWTYPE_MISMATCH THEN
dbms_output.put_line('rowtype mismatch');
RAISE;
WHEN TOO_MANY_ROWS THEN
dbms_output.put_line('too many rows');
RAISE;
WHEN CURSOR_ALREADY_OPEN THEN
dbms_output.put_line('cursor open');
RAISE;
WHEN DUP_VAL_ON_INDEX THEN
dbms_output.put_line('duplicate value');
RAISE;
WHEN OTHERS THEN
dbms_output.put_line('other exception');
RAISE;
END; -- exception handlers and block end here
Download Sample Solution
Custom Exception Handling download
About the Author
Anuj Varma is a technical architect specializing in Microsoft Platforms. Along with high level design and overall architecture, he provides hands-on development, unit testing and other best practices on all the projects he works on. His troubleshooting extends to n-Tier applications, WCF and Azure based applications to advanced SQL Server performance issues.
His customer base includes DELL, Schlumberger, British Petroleum as well as several small to midsize companies across Texas. Anuj can be contacted via the contact form on his blog – https://www.anujvarma.com/
Awesome explanation.. Cleared all my doubts. Thx a ton.