To make the program easier to maintain, let’s catch all exceptions and write them to a log file.
Step 1: Create file Engine\Services\LoggingService.cs
This LoggingService class and functions are static. We won’t need to instantiate anything to log an exception.
Line 8 has a constant for the sub-directory where we’ll store the log files. In the future, we might move this to a config file setting.
Lines 10-18 are the static “constructor” – the function that runs the first time anything calls a function in this static class. This function checks if a “Logs” sub-directory exists under the directory where the application is currently located. If it doesn’t exist, this function will create it.
Lines 20-36 is where we write exception information to the log file. We write the exception’s Message and StackTrace values. They’re usually the most important information from an exception.
If the exception has an inner exception, we recursively call this “Log” function, passing in a “true” for the “isInnerException” parameter – which defaults to “false” when the initial exception is passed into this function.
Lines 38-44 is a small function to build the log file’s path and file name. Because we are concatenating the current date into the log file name, we’ll have a separate file for each day.
This probably isn’t needed for this program. But, it’s something I usually do in my corporate programs. If something goes wrong with a website, web service, or Windows service, it might produce hundreds or thousands of errors. So, you don’t want to only have one log file you keep writing to forever.
LoggingService.cs
using System;
using System.IO;
namespace Engine.Services
{
public static class LoggingService
{
private const string LOG_FILE_DIRECTORY = "Logs";
static LoggingService()
{
string logDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, LOG_FILE_DIRECTORY);
if(!Directory.Exists(logDirectory))
{
Directory.CreateDirectory(logDirectory);
}
}
public static void Log(Exception exception, bool isInnerException = false)
{
using(StreamWriter sw = new StreamWriter(LogFileName(), true))
{
sw.WriteLine(isInnerException ? "INNER EXCEPTION" : $"EXCEPTION: {DateTime.Now}");
sw.WriteLine(new string(isInnerException ? '-' : '=', 40));
sw.WriteLine($"{exception.Message}");
sw.WriteLine($"{exception.StackTrace}");
sw.WriteLine(); // Blank line, to make the log file easier to read
}
if(exception.InnerException != null)
{
Log(exception.InnerException, true);
}
}
private static string LogFileName()
{
// This will create a separate log file for each day.
// Not that we're hoping to have many days of errors.
return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, LOG_FILE_DIRECTORY,
$"SOSCSRPG_{DateTime.Now:yyyyMMdd}.log");
}
}
}
Step 2: Modify WPFUI\App.xaml and App.xaml.cs
We could add exception-handling in many places. But, it’s usually best to only catch an exception if you can do something about it.
We’re going to put one catch-all exception handler in the program. In WPF programs, you can do that by adding a function to call for any exceptions that aren’t handled anywhere else in the program.
Line 5 of App.xaml is where we define the function to call when there is an unhandled exception. We “dispatch” that exception to “App_OnDispatcherUnhandledException”.
Lines 9-18 of App.xaml.cs are the App_OnDispatcherUnhandledException function. We pass the exception to the new LoggingService, to write its information to the log file. Then, we display the exception to the user in a MessageBox.
In the future, we might come up with a better window to display exceptions. But, it’s probably more important to focus on writing the code so it doesn’t have exceptions.
App.xaml
<Application x:Class="WPFUI.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:WPFUI.CustomConverters"
DispatcherUnhandledException="App_OnDispatcherUnhandledException"
StartupUri="MainWindow.xaml">
<Application.Resources>
<converters:FileToBitmapConverter x:Key="FileToBitmapConverter"/>
</Application.Resources>
</Application>
App.xaml.cs
using System.Windows;
using System.Windows.Threading;
using Engine.Services;
namespace WPFUI
{
public partial class App : Application
{
private void App_OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
string exceptionMessageText =
$"An exception occurred: {e.Exception.Message}\r\n\r\nat: {e.Exception.StackTrace}";
LoggingService.Log(e.Exception);
// TODO: Create a Window to display the exception information.
MessageBox.Show(exceptionMessageText, "Unhandled Exception", MessageBoxButton.OK);
}
}
}
NEXT LESSON: Lesson 15.3: Building a “functional” inventory class
PREVIOUS LESSON: Lesson 15.1: Bug Fixes, Unit Tests, and Tooltips