Exception Handling and .NET






4.30/5 (27 votes)
Exception Handling and .NET - a practical approach
Before We Start
Before you start reading this article you need to ensure you satisfy all the pre-requisites. You must:
- be familiar with .NET Framework 2.0 or above
- have C# coding skills
- have knowledge of SQL Server 2005 or above
- be familiar with Visual Studio 2005 or above
- be familiar with creating web application in Visual Studio 2005 or above
Overview
Error Handling has always been crucial for an application in a number of ways. It may affect the execution state of the application, or expose sensitive information to a user. If the error handling is not strong, it may aid the attacker, as the errors returned may assist them in constructing correct attack factors.
An important part of secure application development is to prevent leakage of superfluous information to end user. Error messages (if not proper) may give an attacker great insight into the inner workings of an application.
Exception Handling and .NET
Right Approach
This content focusses more on technical feasibility and implementation of the same, we see here how the points discussed above are disguised in the form of code and learn how to implement these points practically in our application.
Consider the Following Live Scenarios
Let's have an aspx page with the following controls, a textbox, a button and a gridview,
When user inputs a student id in the textbox and submits the form by clicking submit button(Populate Grid), the gridview gets populated with the details of the student as per student id.
Code to populate grid
aspx:
.cs :
Following are the points that need to be taken care of,
- The text box value must be an integer value. (Input Validation)
- The student id provided should be greater than 0 and less than 5. (Business Logic Validation)
These are the rules known to a developer, but what if end user uses the application? Let's sneak peek into such scenario:
Scenario 1 : User inputs correct value and presses submit button.
Here we get the desired result
Scenario 2: User inputs a string or junk character and presses submit button,
Results in:
As we can clearly see, whole of the code is exposed to end user which is ethically meaningless.
Instead one should display a meaningfull message to the user, so that next time he inputs correct info.
Therefore we need to wrap our code with a try, catch block.
and following piece of code in aspx page:
<div> <asp:Label runat="server" ID="lblErrorDisplay" ForeColor="Red" FontBold="true" Visible="false" Font-Size="Medium" ></asp:Label> </div>
Now when user inputs value other than integer, he is shown a meaningful message as,
Our application should not scare the end user with displaying yellow pages and complex codes but it should be informative and end-user friendly.
Scenario 3: User inputs an integer but violates business logic by entering 5 or integer greater than 5.
Our case: No message is shown to the user, the page simply refreshes itself,
Ideal case: User should be shown a message that "no user exists with the specified ID, please enter another ID between 1 to 5", after getting this message user will no longer sit idle thinking about what is wrong with the web site, he will take some other fruitfull action.
To overcome such issue we can again decorate our code as:
protected void btnSubmit_Click(object sender, EventArgs e)
{
try
{
string textValue = txtId.Text;
int id;
if (!Int32.TryParse(textValue, out id))
{
throw new ApplicationException("Please provide correct student id.");
}
if (id <= 0 || id >= 5)
{
throw new ApplicationException("No user exists with the specified id, please enter another id between 1 to 5");
}
string connectionString = ConfigurationManager.ConnectionStrings["Connection"].ConnectionString;
SqlConnection sqlConnection = new SqlConnection(connectionString);
SqlCommand sqlCommand = new SqlCommand("Select StudentID,Fnamn,Enamn,Email,Login,Password from egStudent where StudentId=" +
id.ToString(), sqlConnection);
DataSet dataSet = new DataSet();
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(sqlCommand);
sqlConnection.Open();
sqlDataAdapter.Fill(dataSet);
grdStudentView.DataSource = dataSet.Tables[0];
grdStudentView.DataBind();
sqlConnection.Close();
sqlDataAdapter.Dispose();
}
catch (ApplicationException exception)
{
lblErrorDisplay.Visible = true;
lblErrorDisplay.Text = exception.Message;
}
}
Therefore the output:
The Exceptions discussed above were the Level 1 and Level 2 type exceptions.
Next comes Level 3. For level 3 type exceptions a special setup needs to be established, so that the exceptions could be handled as desired, simple playing with try catch blocks sometimes does not work with these kind of exceptions,
Scenario 4: A developer mistakingly writes a wrong query in select statement. Suppose he makes a typo and changes table name to egstudents (correct : egstudent). Let's see what happens.
protected void btnSubmit_Click(object sender, EventArgs e)
{
try
{
string textValue = txtId.Text;
int id;
if (!Int32.TryParse(textValue, out id))
{
throw new ApplicationException("Please provide correct student id.");
}
if (id <= 0 || id >= 5)
{
throw new ApplicationException("No user exists with the specified id, please enter another id between 1 to 5");
}
string connectionString = ConfigurationManager.ConnectionStrings["Connection"].ConnectionString;
SqlConnection sqlConnection = new SqlConnection(connectionString);
SqlCommand sqlCommand = new SqlCommand("Select StudentID,Fnamn,Enamn,Email,Login,Password from egStudents where StudentId=" +
id.ToString(), sqlConnection);
DataSet dataSet = new DataSet();
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter(sqlCommand);
sqlConnection.Open();
sqlDataAdapter.Fill(dataSet);
grdStudentView.DataSource = dataSet.Tables[0];
grdStudentView.DataBind();
sqlConnection.Close();
sqlDataAdapter.Dispose();
}
catch (ApplicationException exception)
{
lblErrorDisplay.Visible = true;
lblErrorDisplay.Text = exception.Message;
}
}
And so, the output:
In this special case, once the site is up, it becomes hard for the developer to trace the code. Moreover the end user is once again exposed to the unwanted yellow page.
Cure: These kind of Level 3 exceptions need to be centralized (as discussed before in the article). We can write the piece of code in Global.asax on Application_Error
event (handles application level errors), one can write the code to log the specific error and show end user a custom error page, to overcome panic situation.
Step1 : Add code to Global.asax,
void Application_Error(object sender, EventArgs e)
{
Exception exception = Server.GetLastError();
LoggError.Write(exception);
}
Step2: Create a Custom Error Page, and define its path in web.config with a key value pair in appsettings
ErrorPage.aspx:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <title></title> </head> <body> <center> <div><img src="ErrorPage1.jpg" alt="Error Page" /></div> </center> </body> </html>
Page Path:
<appSettings> <add key="ErrorLog" value="~/ErrorLog.txt" /> </appSettings>
Step 3: Enable Custom error mode in web.config inside system.web node with path to your error page as default redirect,.
<customErrors mode="On" defaultRedirect="Error/ErrorPage.htm"> </customErrors>
Step 4: Add a class to your appCode folder, that logs the error into a text file.
using System;
using System.Configuration;
using System.IO;
using System.Web;
/// <summary>
/// The Class Writes Exception and Error information into a log file named ErrorLog.txt.
/// </summary>
public class LoggError
{
/// <summary>
/// Writes error occured in log file,if log file does not exist,it creates the file first.
/// </summary>
/// <param name="exception">Exception</param>
public static void Write(Exception exception)
{
string logFile = String.Empty;
StreamWriter logWriter;
try
{
logFile = HttpContext.Current.Server.MapPath(ConfigurationManager.AppSettings["ErrorLog"].ToString());
if (File.Exists(logFile))
logWriter = File.AppendText(logFile);
else
logWriter = File.CreateText(logFile);
logWriter.WriteLine("=>" + DateTime.Now + " " + " An Error occured : " +
exception.StackTrace + " Message : " + exception.Message + "\n\n");
logWriter.Close();
throw exception;
}
catch (Exception e)
{
throw ;
}
finally
{
throw ;
}
}
}
Finally, when such error occurs, the user is displayed an error page as below:
And for the sake of debugging and development, an actual error is logged in a text file (in my case located at root) as:
=>11/21/2011 7:42:02 PM An Error occured : at System.Web.UI.Page.HandleError(Exception e) at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint) at System.Web.UI.Page.ProcessRequest() at System.Web.UI.Page.ProcessRequestWithNoAssert(HttpContext context) at System.Web.UI.Page.ProcessRequest(HttpContext context) at ASP.default_aspx.ProcessRequest(HttpContext context) in c:\Users\akhil.mittal\AppData\Local\Temp\Temporary ASP.NET Files\exceptionhandling\327c9c5b\2858bbb8\App_Web_iexkgkvc.2.cs:line 0 at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) Message : System.Data.SqlClient.SqlException (0x80131904): Invalid object name 'egStudents'. at _Default.btnSubmit_Click(Object sender, EventArgs e) in d:\Akhil Mittal\Blogs\ExceptionHandling\Default.aspx.cs:line 56 at System.Web.UI.WebControls.Button.OnClick(EventArgs e) at System.Web.UI.WebControls.Button.RaisePostBackEvent(String eventArgument) at System.Web.UI.WebControls.Button.System.Web.UI.IPostBackEventHandler.RaisePostBackEvent(String eventArgument) at System.Web.UI.Page.RaisePostBackEvent(IPostBackEventHandler sourceControl, String eventArgument) at System.Web.UI.Page.RaisePostBackEvent(NameValueCollection postData) at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
The log gives full information about the type and source of exception.
There are many other ways to handle Level 3 Exceptions which we'll discuss in next coming articles, but to hit the floor running, this one is the beginning.
And last but not the least, the Finally block.
Finally Block
As the last clause in the try-catch statement a finally block can also be added. This block is used to clean up all the resources allocated in the try block and will always execute whether there is an exception or not. In the above scenarios for example, we can make use of this block to free SQL connection as.
sql connection as , sqlConnection.Close(); sqlDataAdapter.Dispose();
And thus the final code.
protected void btnSubmit_Click(object sender, EventArgs e)
{
string textValue = txtId.Text;
int id;
string connectionString = ConfigurationManager.ConnectionStrings["Connection"].ConnectionString;
SqlConnection sqlConnection = new SqlConnection(connectionString);
SqlDataAdapter sqlDataAdapter = new SqlDataAdapter();
DataSet dataSet = new DataSet();
try
{
if (!Int32.TryParse(textValue, out id))
throw new ApplicationException("Please provide correct student id.");
if (id <= 0 || id >= 5)
throw new ApplicationException("No user exists with the specified id, please enter another id between 1 to 5");
SqlCommand sqlCommand = new SqlCommand("Select StudentID,Fnamn,Enamn,Email,Login,Password from egStudents where StudentId=" +
id.ToString(), sqlConnection);
sqlDataAdapter = new SqlDataAdapter(sqlCommand);
sqlConnection.Open();
sqlDataAdapter.Fill(dataSet);
grdStudentView.DataSource = dataSet.Tables[0];
grdStudentView.DataBind();
}
catch (ApplicationException exception)
{
lblErrorDisplay.Visible = true;
lblErrorDisplay.Text = exception.Message;
}
catch (Exception exception)
{
throw ;
}
finally
{
sqlConnection.Close();
sqlDataAdapter.Dispose();
}
}
Conclusion
Now after successfully implementing the exception handling technique according to above discussed code and logic, we better know how and where to initiate exception handling in our code according to our requirements.