Exception handling is a critical component of every software application. The last thing you want your users to see is errors, or even worse, your application crashing. In this article, we will discuss how to find and catch all exceptions in C# applications. .NET provides several different ways to catch exceptions and view unhandled exceptions.
Topics in this article:
Let’s open the post with a refresher on the basics of exception handling. More specifically, we’ll cover the basics of the try-catch block.
The try-catch block, as its name implies, has two parts. In the first one—the “try” block—we put the code we believe might throw an exception. The second part—the “catch” block—contains the code that handles the exception, should it occur. So, the code inside the try block starts executing normally. If an exception happens, the flow of code is interrupted, and control is handled to the catch block.
Let’s illustrate that with a simple example. That a look at the following excerpt of code, considering it part of a plain-old .NET console application:
try { Console.WriteLine("Displaying first message!"); Console.WriteLine("Displaying second message!"); Console.WriteLine("Displaying third message!"); } catch { Console.WriteLine("Now we're inside the catch block!") }
If you run the code above, you’ll see, unsurprisingly, the console displaying the three messages:
As you can see, the code inside the catch block wasn’t executed. Let’s see how we can change that. First, let’s create a new method with the sole purpose of throwing an exception:
void MethodThatThrows() { throw new NotImplementedException(); }
Then, let’s call this new method from our code between the second and third messages:
try { Console.WriteLine("Displaying first message!"); Console.WriteLine("Displaying second message!"); MethodThatThrows(); Console.WriteLine("Displaying third message!"); } catch { Console.WriteLine("Now we're inside the catch block!"); }
If you now run the code, you’ll the following messages:
Displaying first message! Displaying second message! Now we're inside the catch block!
The first and second messages were executed as expected. Then the code hit the call to the MethodThatThrows() method, which, making justice to its name, did throw an exception. As expected, the control flow was diverted to the catch block, which contains a single line of code that displays a message.
That’s the gist of how the try-catch block works. Of course, things grow from there. For starters, you can specify the type of exception you expect to get as a parameter for the catch block. Additionally, you can have multiple catch blocks, each one dealing with a different type of exception. If none of the blocks match the type of exception that was thrown, the exception doesn’t get caught and bubbles up until either some other code handles it or it reaches the top levels of the application, causing it to crash.
The following excerpt contains an example of multiple catch blocks:
try { Console.WriteLine("Displaying first message!"); Console.WriteLine("Displaying second message!"); MethodThatThrows(); Console.WriteLine("Displaying third message!"); } catch (InvalidOperationException ex) { Console.WriteLine("Now we're inside the catch block for invalid operation!"); } catch (DivisionByZeroException ex) { Console.WriteLine("Now we're inside the catch block for division by zero!"); }
Finally, let’s cover the finally block—pun totally intended. You can use this block for code that should always run, regardless of whether an exception occurred or not.
First, let’s see an example of what happens when no exception occurs:
try { Console.WriteLine("Displaying first message!"); Console.WriteLine("Displaying second message!"); // MethodThatThrows(); Console.WriteLine("Displaying third message!"); } catch { Console.WriteLine("Now we're inside the catch block!"); } finally { Console.WriteLine("We're inside the finally block: this will always run!"); }
If you run the code above, you’ll see four messages being displayed: the three messages from the try block and the one inside the finally block:
Displaying first message! Displaying second message! Displaying third message! We're inside the finally block: this will always run!
Now, let’s simulate the scenario in which the exception occurs by simply uncommenting one line:
try { Console.WriteLine("Displaying first message!"); Console.WriteLine("Displaying second message!"); MethodThatThrows(); Console.WriteLine("Displaying third message!"); } catch { Console.WriteLine("Now we're inside the catch block!"); } finally { Console.WriteLine("We're inside the finally block: this will always run!"); }
The result changes:
Displaying first message! Displaying second message! Now we're inside the catch block! We're inside the finally block: this will always run!
Now, the flow diverts to the catch block—as expected—and then goes to the finally block.
If you want to find and catch every single exception in your application, you need to be aware of “First Chance Exceptions.” Throwing an exception with no catch block to handle it is when this exception occurs.
The .NET Framework provides an easy mechanism to subscribe to every exception thrown in your code. This includes exceptions that are caught deep inside your code that never get surfaced anywhere. Although you can’t technically “catch” all exceptions in C#, you can subscribe to .NET Framework events so you can log these exceptions. Finding these exceptions is a great way to improve performance and eliminate inconvenient application behaviors.
It is also important to note that First Chance Exceptions can include a lot of noise. Some exceptions happen because they are expected to occur, or may only occur, as various warnings when an application starts up. Don’t be alarmed if you see some of this noise all of the sudden if you start logging all these exceptions in C#.
By subscribing to the event, you can potentially log every exception to help identify weird and hidden problems in your application. Doing this long-term on a production application isn’t recommended due to the sheer volume of noise and data it can cause. It is best used during development or to help find pesky production problems.Set this event handler up at the start of your application in Program.cs, Startup.cs or your Global.asax file.
AppDomain.CurrentDomain.FirstChanceException += (sender, eventArgs) => { Debug.WriteLine(eventArgs.Exception.ToString()); };
For ASP.NET web applications, you can’t prevent users from receiving 500 level HTTP internal server error responses when an exception is thrown in an ApiController. How you can handle this depends whether you are using various ASP.NET frameworks or ASP.NET Core. For starters, be sure to enable custom errors within your web.config file so your users never see exception pages.
If your application has a Global.asax, which is essentially a HttpApplication, you should set up events around unhandled exceptions. It is important to know that if you are using MVC, Web API, Nancy, SerivceStack, WCF, etc., not all exceptions may bubble up to your Global.asax error handler. Those specific web frameworks may also have their own error handling mechanisms. We will cover those below as well!
//Global.asax public class MvcApplication : System.Web.HttpApplication { protected void Application_Error(object sender, EventArgs e) { Exception exception = Server.GetLastError(); if (exception != null) { //log the error } } protected void Application_Start() { //may have some MVC registration stuff here or other code } }
With MVC, you can utilize the HandleErrorAttribute to do custom error handling per MVC controller action. It also has an OnException event within the Controller. In general, you should be safe with global error handling via your Global.asax file to catch all exceptions.
Read More: Best Practices for Error Handling in ASP.NET MVC
Web API has more advanced exception handling capabilities that you need to be aware of.
An example of using the unhandled exception logger:
public class UnhandledExceptionLogger : ExceptionLogger { public override void Log(ExceptionLoggerContext context) { var log = context.Exception.ToString(); //Write the exception to your logs } }
You then need to register your exception logger as part of the startup configuration of Web API.
public static class WebApiConfig { public static void Register(HttpConfiguration config) { //Register it here config.Services.Replace(typeof(IExceptionLogger), new UnhandledExceptionLogger()); // Web API routes config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultApi", routeTemplate: "api/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); } }
Like Web API, WCF has a lot of customization options around exception handling. If you are using WCF, it is critical that you set up an IServiceBehavior and IErrorHandler to catch all exceptions properly. Check out this example on CodeProject for the proper way to do it: WCF Global Exception Handling
A lot has changed with ASP.NET Core. An ExceptionFilterAttribute is used to collect unhandled exceptions. You can register it as a global filter, and it will function as a global exception handler. Another option is to use a custom middleware designed to do nothing but catch unhandled exceptions.
public class ErrorHandlingFilter : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { var exception = context.Exception; //log your exception here context.ExceptionHandled = true; //optional } }
You must also register your filter as part of the Startup code.
//in Startup.cs public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddMvc(options => { options.Filters.Add(new ErrorHandlingFilter()); }); }
The .NET Framework provides a couple of events that you can use to catch unhandled exceptions. You only need to register for these events once in your code when your application starts up. For ASP.NET, you would do this in the Startup class or Global.asax. For Windows applications, it could be the first couple lines of code in the Main() method.
static void Main(string[] args) { Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException); AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException); } static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) { // Log the exception, display it, etc Debug.WriteLine(e.Exception.Message); } static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { // Log the exception, display it, etc Debug.WriteLine((e.ExceptionObject as Exception).Message); }
READ MORE: AppDomain.UnhandledException Event (MSDN)
One of the great features of Retrace is its error monitoring capabilities. Retrace can automatically collect all .NET exceptions that are occurring within your application with no code changes. This includes unhandled exceptions but can also include all thrown exceptions or first chance exceptions.
The best thing about this is it works with all types of ASP.NET applications. It works perfectly with MVC, WCF, Web API, .NET Core, etc.
Retrace provides excellent reporting around all of your exceptions. You can even set up alerts for high application exception rates or when a new exception is found.
Retrace offers three modes:
If your application has unhandled exceptions, that may be logged in the Windows Event Viewer under the category of “Application.” This can be helpful if you can’t figure out why your application suddenly crashes.
Windows Event Viewer may log two different entries for the same exception. One with a .NET Runtime error and another more generic Windows Application Error.
From the .NET Runtime:
Application: Log4netTutorial.exe Framework Version: v4.0.30319 Description: The process was terminated due to an unhandled exception. Exception Info: System.IndexOutOfRangeException at Log4netTutorial.Program.Main(System.String[])
Logged under Application Error:
Faulting application name: Log4netTutorial.exe, version: 1.0.0.0, time stamp: 0x58f0ea6b Faulting module name: KERNELBASE.dll, version: 10.0.14393.953, time stamp: 0x58ba586d Exception code: 0xe0434352 Fault offset: 0x000da882 Faulting process id: 0x4c94 Faulting application start time: 0x01d2b533b3d60c50 Faulting application path: C:UsersmattDocumentsVisual Studio 2015ProjectsLog4netTutorialbinDebugLog4netTutorial.exe Faulting module path: C:WINDOWSSystem32KERNELBASE.dll Report Id: 86c5f5b9-9d0f-4fc4-a860-9457b90c2068 Faulting package full name: Faulting package-relative application ID:
Having good best practices around logging and exceptions is critical for every software application. Logging is typically the eyes and ears of the developer. In this guide, we covered several ways to find and catch all exceptions in C#. Be sure to check out our exception handling and logging best practices guides.
Retrace by Netreo gives developers unparalleled visibility into the performance of their applications. With Retrace, find all application exceptions and get a complete transaction traces of what happens when exceptions are thrown.
If you would like to be a guest contributor to the Stackify blog please reach out to [email protected]