Archive

Posts Tagged ‘Exception Handling’

Preventing SQL injection attacks in ASP.NET

May 30, 2008 1 comment

Consider a simple web application that requires user input in some fields, lets say some search box. Suppose a user types the following string in that textbox:

'; DROP DATABASE pubs --

On submit our application executes the following dynamic SQL statement

SqlDataAdapter myCommand = new SqlDataAdapter("SELECT OrderId, OrderNumber FROM Orders WHERE OrderNumber = '" + OrderNumberTextBox.Text + "'", myConnection);

Or stored procedure

SqlDataAdapter myCommand = new SqlDataAdapter("uspGetOrderList '" + OrderNumberTextBox.Text + "'", myConnection);

The intention being that the user input would be run as

SELECT OrderId, OrderNumber FROM Orders WHERE OrderNumber = 'PO123'

However, the code inserts the user’s malicious input and generates the following query.

SELECT OrderId, OrderNumber FROM Orders WHERE OrderNumber = ''; DROP DATABASE pubs --'

In this case, the ‘ (single quotation mark) character that starts the rogue input terminates the current string literal in the SQL statement. As a result, the opening single quotation mark character of the rogue input results in the following statement.

SELECT OrderId, OrderNumber FROM Orders WHERE OrderNumber = ''

The; (semicolon) character tells SQL that this is the end of the current statement, which is then followed by the following malicious SQL code.

; DROP DATABASE pubs

Finally, the — (double dash) sequence of characters is a SQL comment that tells SQL to ignore the rest of the text. In this case, SQL ignores the closing ‘ (single quotation mark) character, which would otherwise cause a SQL parser error.

--'

Using stored procedures doesn’t solve the problem either because the generated query would be

uspGetOrderList ''; DROP DATABASE pubs--'

Or perhaps this was your login page and your query being

SELECT UserId FROM Users WHERE LoginId = <inputlogin> AND Password = <inputpwd> AND IsActive = 1

Someone could easily login by typing in the following in your login textbox

' OR 1 = 1; --

Which makes our query

SELECT UserId FROM Users WHERE LoginId = '' OR 1 = 1; --' AND Password = '' AND IsActive = 1

Viola, the attacker has now successfully logged in to your site using SQL injection attack.

SQL injection can occur, as demonstrated above, when an application uses input to construct dynamic SQL statements or when it uses stored procedures to connect to the database. Conventional security measures, such as the use of SSL and IPSec, do not protect your application from SQL injection attacks. Successful SQL injection attacks enable malicious users to execute commands in an application’s database. Common vulnerabilities that make your data access code susceptible to SQL injection attacks include:

  • Weak input validation.
  • Dynamic construction of SQL statements without the use of type-safe parameters.
  • Use of over-privileged database logins.

    So what can we do to help protect our application from such attacks? To counter SQL injection attacks, we need to:

  • Constrain and sanitize input data

    Check for known good data by validating for type, length, format, and range and using a list of acceptable characters to constrain input. Create a list of acceptable characters and use regular expressions to reject any characters that are not on the list. Using the list of unacceptable characters is impractical because it is very difficult to anticipate all possible variations of bad input.

    Start by constraining input in the server-side code for your ASP.NET Web pages. Do not rely on client-side validation because it can be easily bypassed. Use client-side validation only to reduce round trips and to improve the user experience. Check my other blog on Validation Application Block for server-side validation.

    If in the previous code example, the Order Number value is captured by an ASP.NET TextBox control, you can constrain its input by using a RegularExpressionValidator control as shown in the following.

    <%@ language="C#" %> <form id="form1" runat="server"> <asp:TextBox ID="OrderNumberTextBox" runat="server"/> <asp:RegularExpressionValidator ID="regexpPO" runat="server" ErrorMessage="Incorrect Order Number" ControlToValidate="OrderNumberTextBox" ValidationExpression="^PO\d{3}-\d{2}$" /> </form>

    If the Order Number input is from another source, such as an HTML control, a query string parameter, or a cookie, you can constrain it by using the Regex class from the System.Text.RegularExpressions namespace. The following example assumes that the input is obtained from a cookie.

    using System.Text.RegularExpressions; if (Regex.IsMatch(Request.Cookies["OrderNumber"], "^PO\d{3}-\d{2}$")) { // access the database } else { // handle the bad input }

    Performing input validation is essential because almost all application-level attacks contain malicious input. You should validate all input, including form fields, query string parameters, and cookies to protect your application against malicious command injection. Assume all input to your Web application is malicious, and make sure that you use server validation for all sources of input. Use client-side validation to reduce round trips to the server and to improve the user experience, but do not rely on it because it is easily bypassed.

     

  • Apply ASP.NET request validation during development to identify injection attacks

    ASP.NET request validation detects any HTML elements and reserved characters in data posted to the server. This helps prevent users from inserting script into your application. Request validation checks all input data against a hard-coded list of potentially dangerous values. If a match occurs, it throws an exception of type HttpRequestValidationException.

    Request validation is enabled by ASP.NET by default. You can see the following default setting in the Machine.config.comments file.

    <pages validateRequest="true" ... />

    Confirm that you have not disabled request validation by overriding the default settings in your server’s Machine.config file or your application’s Web.config file.

    You can disable request validation in your Web.config application configuration file by adding a <pages> element with validateRequest="false" or on an individual page by setting ValidateRequest="false" on the @ Pages element.

    NOTE: You should disable Request Validation only on the page with a free-format text field that accepts HTML-formatted input.

    You can test the effects of request validation. To do this, create an ASP.NET page that disables request validation by setting ValidateRequest="false", as follows.

    <%@ Language="C#" ValidateRequest="false" %> <html> <script runat="server"> void btnSubmit_Click(Object sender, EventArgs e) { // If ValidateRequest is false, then 'hello' is displayed // If ValidateRequest is true, then ASP.NET returns an exception Response.Write(txtString.Text); } </script> <body> <form id="form1" runat="server"> <asp:TextBox id="txtString" runat="server" Text="<script>alert('hello');</script>" /> <asp:Button id="btnSubmit" runat="server" OnClick="btnSubmit_Click" Text="Submit" /> </form> </body> </html>

    When you run the page, "Hello" is displayed in a message box because the script in txtString is passed through and rendered as client-side script in your browser.

    If you set ValidateRequest="true" or remove the ValidateRequest page attribute, ASP.NET request validation rejects the script input and produces an error similar to the following.

    A potentially dangerous Request.Form value was detected from the client (txtString="<script>alert(‘hello…").
    Note   Do not rely on ASP.NET request validation. Treat it as an extra precautionary measure in addition to your own input validation.

  • Constrain input by using validator controls

    To constrain input, use server-side input validation. Do not rely on client-side validation because it is easily bypassed. Use client-side validation in addition to server-side validation to reduce round trips to the server and to improve the user experience. Validate length, range, format and type. Make sure that any input meets your guidelines for known good input.

    Use the ASP.NET validator controls to constrain form field input received through server controls. For other sources of input data, such as query strings, cookies, and HTTP headers, constrain input by using the Regex class from the System.Text.RegularExpressions namespace. Or you can use the Enterprise Library Validation Application Block to check for input validation. The Validation Application Block can not be used for input validation but it can also validate your business objects. See my other blog on Validation Application Block.

  • Encode unsafe output

    If your application needs to accept a range of HTML elements—for example through a rich text input field such as a comments field—turn off ASP.NET request validation and create a filter that allows only the HTML elements that you want your application to accept. A common practice is to restrict formatting to safe HTML elements such as <b> (bold) and <i> (italic). Before writing the data, HTML-encode it. This makes any malicious script safe by causing it to be handled as text, not as executable code.

    The HtmlEncode method replaces characters that have special meaning in HTML to HTML variables that represent those characters. For example, < is replaced with &lt; and " is replaced with &quot;. Encoded data does not cause the browser to execute code. Instead, the data is rendered as harmless text, and the tags are not interpreted as HTML.

    The following page disables ASP.NET request validation by setting ValidateRequest="false". It HTML-encodes the input and selectively allows the <b> and <i> HTML elements to support simple text formatting.

    <%@ Page Language="C#" ValidateRequest="false"%> <script runat="server"> void submitBtn_Click(object sender, EventArgs e) { // Encode the string input StringBuilder sb = new StringBuilder( HttpUtility.HtmlEncode(htmlInputTxt.Text)); // Selectively allow and <i> sb.Replace("&lt;b&gt;", "<b>"); sb.Replace("&lt;/b&gt;", ""); sb.Replace("&lt;i&gt;", "<i>"); sb.Replace("&lt;/i&gt;", ""); Response.Write(sb.ToString()); } </script> <html> <body> <form id="form1" runat="server"> <asp:TextBox ID="htmlInputTxt" Runat="server" TextMode="MultiLine" Width="318px" Height="168px" /> <asp:Button ID="submitBtn" Runat="server" Text="Submit" OnClick="submitBtn_Click" /> </form> </body> </html>
  • Use type-safe SQL parameters for data access

    Parameter collections such as SqlParameterCollection provide type checking and length validation. If you use a parameters collection, input is treated as a literal value, and SQL Server does not treat it as executable code. An additional benefit of using a parameters collection is that you can enforce type and length checks. Values outside of the range trigger an exception. You can use these parameters with stored procedures or dynamically constructed SQL command strings.

    Using stored procedures does not necessarily prevent SQL injection. The important thing to do is use parameters with stored procedures. If you do not use parameters, your stored procedures can be susceptible to SQL injection if they use unfiltered input.

    The following code shows how to use SqlParameterCollection when calling a stored procedure.

    using System.Data; using System.Data.SqlClient; using (SqlConnection connection = new SqlConnection(connectionString)) { DataSet userDataset = new DataSet(); SqlDataAdapter myCommand = new SqlDataAdapter("uspGetOrderList", connection); myCommand.SelectCommand.CommandType = CommandType.StoredProcedure; myCommand.SelectCommand.Parameters.Add("@OrderNumber", SqlDbType.VarChar, 11); myCommand.SelectCommand.Parameters["@OrderNumber"].Value = OrderNumberTextBox.Text; myCommand.Fill(userDataset); }

    The @OrderNumber parameter is treated as a literal value and not as executable code. Also, the parameter is checked for type and length. In the preceding code example, the input value cannot be longer than 11 characters. If the data does not conform to the type or length defined by the parameter, the SqlParameter class throws an exception.

    You should review your application’s use of stored procedures because simply using stored procedures with parameters does not necessarily prevent SQL injection. For example, the following parameterized stored procedure has several security vulnerabilities.

    CREATE PROCEDURE dbo.uspRunQuery @var ntext AS exec sp_executesql @var GO

    The stored procedure executes whatever statement is passed to it. Consider the @var variable being set to:

    DROP TABLE ORDERS;

    If you cannot use stored procedures, you should still use parameters when constructing dynamic SQL statements. The following code shows how to use SqlParametersCollection with dynamic SQL.

    using System.Data; using System.Data.SqlClient; using (SqlConnection connection = new SqlConnection(connectionString)) { DataSet userDataset = new DataSet(); SqlDataAdapter myDataAdapter = new SqlDataAdapter("SELECT OrderId, OrderNumber FROM Orders WHERE OrderNumber = @OrderNumber", connection); myCommand.SelectCommand.Parameters.Add("@OrderNumber", SqlDbType.VarChar, 11); myCommand.SelectCommand.Parameters["@OrderNumber"].Value = OrderNumberTextBox.Text; myDataAdapter.Fill(userDataset); }

    If you concatenate several SQL statements to send a batch of statements to the server in a single round trip, you can still use parameters if you make sure that parameter names are not repeated i.e. use unique parameter names during SQL text concatenation.

    SELECT OrderId, OrderNumber FROM Orders WHERE OrderNumber = 'PO123' using System.Data; using System.Data.SqlClient; using (SqlConnection oConn = new SqlConnection(connectionString)) { SqlDataAdapter oAdapter = new SqlDataAdapter( "SELECT CustomerID INTO #Temp1 FROM Customers " + "WHERE CustomerID > @custIDParm; " + "SELECT CompanyName FROM Customers " + "WHERE Country = @countryParm and CustomerID IN " + "(SELECT CustomerID FROM #Temp1);", oConn); SqlParameter custIDParm = oAdapter.SelectCommand.Parameters.Add("@custIDParm", SqlDbType.NChar, 5); custIDParm.Value = customerID.Text; SqlParameter countryParm = oAdapter.SelectCommand.Parameters.Add("@countryParm", SqlDbType.NVarChar, 15); countryParm.Value = country.Text; oConn.Open(); DataSet dataSet = new DataSet(); oAdapter.Fill(dataSet); }
  • Use a least privileged account that has restricted permissions in the database

    Ideally, you should only grant execute permissions to selected stored procedures in the database and provide no direct table access. The problem is more severe if your application uses an over-privileged account to connect to the database. For example, if your application’s login has privileges to eliminate a database, then without adequate safeguards, an attacker might be able to perform this operation.

    If you use Windows authentication to connect, the Windows account should be least-privileged from an operating system perspective and should have limited privileges and limited ability to access Windows resources. Additionally, whether or not you use Windows authentication or SQL authentication, the corresponding SQL Server login should be restricted by permissions in the database.

    Consider the example of an ASP.NET application running on Microsoft Windows Server 2003 that accesses a database on a different server in the same domain. By default, the ASP.NET application runs in an application pool that runs under the Network Service account. This account is a least privileged account.

    1. Create a SQL Server login for the Web server’s Network Service account. The Network Service account has network credentials that are presented at the database server as the identity DOMAIN\WEBSERVERNAME$. For example, if your domain is called XYZ and the Web server is called 123, you create a database login for XYZ\123$.
    2. Grant the new login access to the required database by creating a database user and adding the user to a database role.
    3. Establish permissions to let this database role call the required stored procedures or access the required tables in the database. Only grant access to stored procedures the application needs to use, and only grant sufficient access to tables based on the application’s minimum requirements. If the ASP.NET application only performs database lookups and does not update any data, you only need to grant read access to the tables. This limits the damage that an attacker can cause if the attacker succeeds in a SQL injection attack.
  • Use Character Escaping Techniques

    In situations where parameterized SQL cannot be used, consider using character escaping techniques. If you are forced to use dynamic SQL and parameterized SQL cannot be used, you need to safeguard against input characters that have special meaning to SQL Server (such as the single quote character). If not handled, special characters such as the single quote character in the input can be utilized to cause SQL injection.

    Escape routines add an escape character to characters that have special meaning to SQL Server, thereby making them harmless.

    private static string GetStringForSQL(string inputSQL) { return inputSQL.Replace("'", "''"); }

    Special input characters pose a threat only with dynamic SQL and not when using parameterized SQL. Your first line of defense should always be to use parameterized SQL.

  • Avoid disclosing database error information

    In the event of database errors, make sure you do not disclose detailed error messages to the user. Use structured exception handling to catch errors and prevent them from propagating back to the client. Log detailed error information locally, but return limited error details to the client.

    If errors occur while the user is connecting to the database, be sure that you provide only limited information about the nature of the error to the user. If you disclose information related to data access and database errors, you could provide a malicious user with useful information that he or she can use to compromise your database security. Attackers use the information in detailed error messages to help deconstruct a SQL query that they are trying to inject with malicious code. A detailed error message may reveal valuable information such as the connection string, SQL server name, or table and database naming conventions. See my other post on Exception Handling – Do’s and Dont’s.

    You can use the <customErrors> element to configure custom, generic error messages that should be returned to the client in the event of an application exception condition.

    Make sure that the mode attribute is set to "remoteOnly" in the web.config file as shown in the following example.

    <customErrors mode="remoteOnly" />

    After installing an ASP.NET application, you can configure the setting to point to your custom error page as shown in the following example.

    <customErrors mode="On" defaultRedirect="YourErrorPage.htm" />

    Conclusion

    The above list is just some points found on MSDN on how you can make your site more secure by effectively preventing SQL injection attacks. You should always be reviewing your code to find these or other security vulnerabilities; remember all type of attacks start with some input, and your first line of defense should be input validation using both client-side and server-side validation.

    Technorati Tags: ,

  • Advertisements

    Exception Handling – Do’s and Dont’s

    May 6, 2008 6 comments

    Exceptions provide a consistent mechanism for identifying and responding to error conditions. Effective exception handling will make code more robust and easier to debug. Exceptions are a tremendous debugging aid because they help answer:

    • What went wrong
    • Where did it go wrong
    • Why did it go wrong

    What is answered by the type of exception thrown, where is answered by the exception stack trace, and why is answered by the exception message. If your exception is not answering all three questions, chances are they aren’t being used effectively.

    Following are some suggestions on Do’s and Don’ts for exception handling:

    • Don’t throw an exception when a simple if statement can be used to check for errors. Remember an exception is something that is out of the norm. For example, a simple if statement to check whether a connection is closed is much better than throwing an exception for the same.

    Do

    if(conn.State != ConnectionState.Closed) conn.Close();

    Don’t 

    try { conn.Close(); } catch(InvalidOperationException ex) { //do something with the error or ignore it }
    • Do use try/finally blocks around code that can potentially generate an exception and centralize your catch statements in one location. In this way, the try statement generates the exception, the finally statement closes or deallocates resources, and the catch statement handles the exception from a central location. This also has the additional benefit of making your code more readable. Consider a simple example where you want to create a temporary file and delete it afterwards even if an error occurs
    string Foo1(string fileName) { string fileContents; using(StreamReader sr = new StreamReader(fileName)) { fileContents =sr.ReadToEnd(); } File.Delete(fileName); return fileContents; }

    This code deletes the file just before the return statement; but what if an exception occurs in the lines above? it leaves the temporary file on the disk. Some people might try to solve it coding as this:

    string Foo1(string fileName)
    {
        try
        {
            string fileContents;
            using (StreamReader sr = new StreamReader(fileName))
            {
                fileContents = sr.ReadToEnd();
            }
            File.Delete(fileName);
            return fileContents;
        }
        catch (Exception)
        {
            File.Delete(fileName);
            throw;
        }
    }

    The above code does the job but its a bit more complex and the File.Delete is duplicated in the try and catch blocks. Now, see how the try/finally solution is more cleaner and robust:

    string Foo1(string fileName) { try { using (StreamReader sr = new StreamReader(fileName)) { return sr.ReadToEnd(); } } finally { File.Delete(fileName); } }

    Notice that we haven’t used the fileContents local variable now because we can return the contents of the ReadToEnd() method and the cleanup code executes after the return point. This is one of the advantages of having code that can run after the function returns: you can clean resources that may be needed for the return statement.

     

    • Don’t use finally blocks if all you need to do is call Dispose() on your object. Instead use "using" for objects which support the IDisposable interface. "using" will guarantee that the Dispose() method is called even if an exception occurs within the code block.
    • Do postfix your exception class names with the word "Exception". For example:
    public class FileNotFoundException : IOException { }
    • When creating user-defined exceptions, you must ensure that the metadata for the exceptions is available to code executing remotely, including when exceptions occur across application domains. For example, suppose Application Domain A creates Application Domain B, which executes code that throws an exception. For Application Domain A to properly catch and handle the exception, it must be able to find the assembly containing the exception thrown by Application Domain B. If Application Domain B throws an exception that is contained in an assembly under its application base, but not under Application Domain A’s application base, Application Domain A will not be able to find the exception and the common language runtime will throw a FileNotFoundException. To avoid this situation, you can deploy the assembly containing the exception information in two ways:
      • Put the assembly into a common application base shared by both application domains
        – or –
      • If the domains do not share a common application base, sign the assembly containing the exception information with a strong name and deploy the assembly into the global assembly cache.

    In situations where you are using remoting, you must ensure that the metadata for any user-defined exceptions is available at the server (callee) and to the client (the proxy object or caller).

    • Do use at least the three common constructors when creating your own exception classes. Failure to do so means your exception class will not be able to follow some of these Do’s and Don’t’s.
    public class FileNotFoundException : IOException { public FileNotFoundException() { } public FileNotFoundException(string message) : base(message) { } public FileNotFoundException(string message, Exception inner) : base(message, inner) { } }
    • Do use the predefined exceptions types for common cases and define your own custom exception types only for programmatic scenarios. For example, to enable a programmer to take a different action in code based on the exception class.
    • Do remember that exceptions are classes which can have properties for extra information. Include extra information in an exception (in addition to the description string) when there is a programmatic scenario where the additional information is useful. Don’t try and cram all information in the Message property.
    • Design classes so that an exception is never thrown in normal use. For example, a FileStream class exposes another way of determining whether the end of the file has been reached. This avoids the exception that is thrown if you read past the end of the file. The following example shows how to read to the end of the file.
      class FileRead { public void Open(FileStream fileToRead) { if (fileToRead == null) { throw new System.ArgumentNullException(); } int b; fileToRead.Seek(0, SeekOrigin.Begin); for (int i = 0; i < fileToRead.Length; i++) { b = fileToRead.ReadByte(); Console.Write(b.ToString()); } } }
    • Do throw an InvalidOperationException if a property set or method call is not appropriate given the object’s current state. For example, if a OpenSocket() method was unable to allocate the resource and returned null a subsequent Listen() method should throw a InvalidOperationException.
    • Do throw exceptions instead of returning special error codes or HRESULT.
      • Exceptions makes the common case faster, because when you return special values from methods, each method return needs to be checked and this consumes at least one processor register more, leading to slower code.
      • Special values can, and will be ignored. I’ve seen dozens of newsgroup posts asking for if there was some way to check if the method succeeded or not and turns out the method did return an enum of possible return codes.
      • Even if the return code was checked by the programmer its not much use as it does not tell me what, where and why went wrong. Its just a simple code which I may be able to lookup in the documentation but then again it could be a generic error code which simply tells me that the method failed and not why it failed. For all I know it could have failed because of some IOException or some ArgumentException.
      • There may not be a suitable value to return that can be used as an indicator for an error condition. I’ve seen countless methods return -1 to indicate an error condition but its not always suitable. What value would you return from the following function to represent a division by zero?
    • Do return special error code (null) for extremely common situations instead of exception. Wait a minute, wasn’t the point above about not to return error codes? What gives? It turns out most methods which return some resource in the .NET framework follow this rule. For example, Open returns null if the file is not found, but throws an error if the file cannot be opened (file is locked by some other process). But why this alternate rule for null?
      • null would be out of the norm and hence less code will be executed for the common case.
      • null cannot be ignored. Trying to ignore it will most likely throw an NullReferenceException.
    • Do clean up intermediate results and resources when throwing an exception. Callers should be able to assume that there are no side effects when an exception is thrown from a method. For example, if an exception is thrown from Foo1() then it should properly dispose of the objects before leaving the method.
    public void Foo1() { using(SqlConnection oConn = new SqlConnection()) { try { SqlCommand oCmd = oConn.CreateCommand(); //call some proc oConn.Open(); } catch(SqlException ex) { //handle exception throw; } finally { oConn.Close(); } } }
    • Don’t throw Exception(). It is too broad class and catching it could mean swallowing other exceptions. Instead create your own custom exceptions and derive from Exception class.
    [Serializable] class MyCustomException : Exception { }
    • Do mark your custom exceptions with [Serializable]. Your method might very well be called from remoting components or web services and if your exception is not serializable you will end up with a completely different exception hiding your original exception, making any bug extremely hard to debug and fix.
    • Don’t swallow an exception by putting an empty catch block. Never do this. If you don’t need to do anything when an exception is thrown then just don’t catch it and let other code try and handle it. Many times I’ve seen novice programmers will try and put try…catch on all events in an aspx page and catch(Exception ex) without doing anything with that exception. This effectively hides all errors from the user but what if this event was a button click for submitting an online shopping cart? The try…catch will hide the fact that the request failed but will not show it to the user, this will ultimately result in a customer support call by a frustrated user.

    The first class MyClass is on an assembly, and the second class (MyCCValidator) is on another assembly. On the development machine the code ran right, but on the QA machines, the code always returned "Invalid number", even if the entered number was valid.

    public class MyClass { public static string ValidateNumber(string userInput) { try { bool val = MyCCValidator.Validate(userInput); return "Valid number"; } catch (Exception) { return "Invalid number"; } } } public class MyCCValidator { public static bool Validate(string userInput) { return true; } }

    The problem was on our setup, which didn’t include the second assembly (ValidatorsLibrary). Now, we had a FileNotFoundException when the Validate was called, and the code assumed that it was because the number was invalid.

    • Don’t catch Exception. Always use the most specific exception for the code you are writing. Remember good code is not code that doesn’t throw exceptions. Good code throws exceptions as needed and handles only the exceptions it knows how to handle. The more specific the exception, the better your code answers what went wrong. For example, your code may respond to a FileNotFoundException by asking the user for a different file name.
    File prefsFile = new File(prefsFilename); try { readPreferences(prefsFile); } catch (FileNotFoundException e) { // alert the user that the specified file does not exist } catch (EOFException e) { // alert the user that the end of the file was reached } catch (ObjectStreamException e) { // alert the user that the file is corrupted } catch (IOException e) { // alert the user that some other I/O error occurred }

    Assuming IOException is the base class for FileNotFoundException, EOFException and ObjectStreamException. Should an IOException other than those specified by the first three catch blocks be thrown, the last catch block will handle it by presenting the user with a somewhat more generic error message. This way, the program can provide specific information when possible, but still handle the general case should an unanticipated file-related exception slip by.

     

    • Don’t use throw ex. Out of every property in the Exception class, the stack trace is one of the most useful information that an exception carries. If you need to put some exception handling in the catch block and re-throw the exception so it can be logged / handled in the UI block with an appropriate message:

    Don’t

    try { //code that throws an exception } catch(Exception ex) { //handle exception throw ex; }

    Do

    try { //code that throws an exception } catch(Exception ex) { //handle exception throw;
    }

    When you examine the stack trace of the first code sample the point of the exception will be the line "throw ex;" hiding the real error location. "throw;" on the other hand will simply rethrow the exception and will keep your stack trace intact.

    • Don’t log Exception.Message. Always log Exception.ToString() instead of Exception.Message. If you only log Exception.Message, you’ll only have something like "Object reference not set to an instance of an object" in your exception log, all this tells me is that some where an exception occurred and some object was set to null. It answers the what but not the where and why. Exception.ToString() will give you a stack trace, the inner exception and the message.
    • Do use Debug.Assert to validate arguments to your methods during development only (debug build). Assert will throw an exception if the expression evaluates to false. This can be very useful in preventing null (or any invalid argument) being passed as an argument to your method. But don’t forget that Debug.Assert is removed from release code. For release builds you should throw ArgumentException or a class derived from ArgumentException if invalid parameters are passed.

    The above list is just some suggestions from my experiences. Consider them as a starting point for your own exception handling rules. Also, there are lots of good frameworks and libraries which deal with exception handling and logging. One such is the Microsoft Enterprise Library which contains Exception Handling and Logging Application Blocks. These libraries can be great tools in the hands of a programmer who follows strict design guidelines for exception handling and useless if you don’t follow such rules.

    In the end, the hardest part of debugging usually is not fixing the bug, but instead finding where in code the bug hides. By following the above list you can use exceptions to help you track down and eradicate bugs and make your programs more robust and user-friendly.

     

    Technorati Tags: ,