javascriptjavaphprubydotnet

Exception Handling in .NET C#

You’ve seen the term "handling" exceptions throughout this guide, and you’ve seen a couple of examples that show how to throw an exception. When you “handle” an exception, you catch it before it interferes with a user’s workflow. Exception handling is one of the most important parts of software design, because it avoids user frustration associated with buggy software. It’s not the only solution for buggy software, but by handling exceptions you are able to properly guide the user through the next steps without your software crashing. You can also use try/catch blocks to catch an exception, then retry the procedure to salvage the user’s workflow. For instance, if a file is already open and the software can’t write to it, you can handle the exception, give the user the opportunity to close the file, then try again.

Using try, catch, and finally

Most .NET programs have at least one try/catch block, which allows developers to "try" executing some code which may throw an exception, and then "catch" that exception to handle it. One common mistake is surrounding an entire method with one try/catch, but this is bad practice. You need to be able to narrow down where in the method an exception occurred. You do this by surrounding different logical sections with their own try/catch blocks.

Each try/catch block has one try and as many catch sections as you need to handle each exception. The end of the try/catch block is a finally section where you execute what must be done after the exception is handled.

try {
   // statements that must be handled
} catch( ExceptionName ex1 ) {
   // error handling code
} catch( ExceptionName ex2 ) {
   // error handling code
} catch( ExceptionName eN ) {
   // error handling code
} finally {
   // final statements
}

Notice that each catch has its own exception class. Each of these catch blocks check for the exception type, so you can conditionally run code based on the type of exception. Some exceptions will require the program to stop execution, but others retry execution and recover the user’s original workflow. See the "Throwing Exceptions" section for some of the common errors.

public void DivideTwoNumbers(int num1, int num2)
{
	double result = 0;
	try
	{
		result = num1 / num2;
	}
	catch (DivideByZeroException e)
	{
		Console.WriteLine("Exception caught: can't divide by zero");
	}
	finally
	{
		Console.WriteLine("Result: {0}", result);
	}
}

In the method above, DivideTwoNumbers has two parameters passed during execution. The assumption is that both of these numbers (num1 and num2) aren’t zero. However, if this is from user input, it’s possible that num2 could be 0. Since you can’t divide by zero, the program would crash if you don’t have this logic wrapped in a try/catch block. Because the division statement is wrapped in try/catch, you catch the exception and the code execution goes to the first catch statement.

Notice that the catch statement defines a DivideByZeroException exception. Since this exception is what was caught, execution flows to the Console.WriteLine statement that tells the user you can’t divide by zero. Whether an exception is caught or not, the finally statement executes. In this example, the result would be 0. If you suspected more than one type of exception, you would have multiple catch statements to handle all permutations of exceptions.

	WebClient wc = null;
	try
	{
		wc = new WebClient();
		var resultData = wc.DownloadString("http://anysite.com");
	}
	catch (ArgumentNullException ex)
	{
		//code for a ArgumentNullException
	}
	catch (WebException ex)
	{
		//code for a WebException
	}
	catch (Exception ex)
	{
		//code for any other exception
	}
	finally
	{
		//call this if exception occurs or not
		//in this example, dispose the WebClient
		wc.Dispose();
	}

In this example, the try/catch block detects multiple types of exceptions for the WebClient variable. The try/catch block functions similarly to a switch statement. Each exception type is checked, and if the first two don’t match the Exception catch is executed. The finally section disposes of the variable to release it from memory.

When you’re designing the way you handle exceptions, always consider your users and their workflow. Some errors are unrecoverable, forcing you to stop workflow, but you can recover and manage others; at that point workflow continues. Catching errors helps avoid data loss, but it should also let users recover from an unexpected error. For instance, if the user loses internet connection, they should be able to fix it and continue working. If a file is opened and must be closed, the user should be able to close the file and continue.

A good example of a recoverable error is when you install software that has dependencies. The installer detects if the dependency is installed and prompts the user to install it if it’s not found. After the user installs the software, the installation can resume without losing the user’s place in the installation process.

Handling uncaught exceptions

Uncaught exceptions are also known as runtime errors. These errors happen when you don’t catch exceptions. For instance, you could consider a section of code "unbreakable," but during runtime user input or system changes could still cause an exception. Unhandled, uncaught errors are usually critical, because the application crashes unless you catch it.

When you’re writing an MVC app, you could write handlers for Controller errors, but then you handle exceptions only in Controllers and not Views. The best strategy is to write a global handler that catches errors in both Controllers and Views. When you throw an exception or runtime errors occur, this global handler will perform an action such as write to a log file, display message to the user, or send a message to a developer. The functionality you code in a global handler is up to you, but you should have some way to alert developers when it happens. As an example, you could send an email to a developer when an error occurs.

You create a global error handler by overriding the Application_Error handler in the Global.asax file in the MvcApplication class.

//Application-level handler using Application_Error to create a global handler
protected void Application_Error(object sender, EventArgs e)
{
	Exception exception = Server.GetLastError();
	Server.ClearError();
	Response.Redirect("/Home/Error");
}

In the code above, it captures any application error and redirects the user to the Error page. You can customize your global handler’s functionality in any way, but you should do it in a way that makes it easy for a user to navigate through the error without being stuck at a dead end in your application.

You can also configure Rollbar as a global handler to catch and track unhandled exceptions and errors. It will automatically group the occurrences, provide you an overview of how many times it happened, and notify you according to your preferences. As you can see below it also provides a lot of other useful information like the people affected, which deployment is suspected, and more.

Rollbar uncaught exception