Pull to refresh

Окно сообщения об ошибке для WinForms и WPF приложений

Reading time 4 min
Views 19K

Приветствую!

В статье посвященной моему профайлеру для Entity Framework-a, я вкратце описал примененную мной форму для сообщения пользователю об исключительной ошибке в приложении. После оценки количества скачиваний примера кода, было решено выделить этот пример в отдельный проект, а также добавить поддержку WPF приложений.
Исходники библиотеки вместе с примерами опубликованы на CodePlex под свободной лицензией MIT: https://uiexceptionhandler.codeplex.com/

Подробности под катом.

Введение

Всем известно, что приложения периодически падают по самым разным причинам, при этом, крайне желательно показывать пользователю дружественное сообщение об ошибке в приложении, вместо стандартного сообщения Windows.

Что получилось

При подключенной библиотеке, в случае падения приложения будет показано следующие сообщение с просьбой добавить описание шагов которые привели к ошибке и свой email для ответа, при этом текст ошибки сохраняется в лог файл.


При клике по кнопке «Error detail information» выводиться дополнительная информация об ошибке:


Кнопка Debug позволяет подключить отладчик Visual Studio.
Кнопка «Send to Developer» отправляет письмо на почту разработчику. В случае ошибки отправки сообщения, пользователю будет предложено самому отправить лог файл разработчику на почту.
Отправленное разработчику сообщение придет в таком виде:


Использование

1. Забрать последнюю версию кода https://uiexceptionhandler.codeplex.com/SourceControl/latest
2. Собрать в Release mode.
3. Из папки «UIExceptionHandlerLibs\Deploy» подключить в проект библиотеку UIExceptionHandlerWinForms.dll в случае WinForms приложения и UIExceptionHandlerWPF.dll в случае WPF приложения.
4. Инициализировать путем вызова статического метода с рядом параметров:
UIException.Start(
   string serverSmtp, 
   int portSmtp, 
   string passwdSmtp, 
   string userSmtp, 
   string programmerEmail,
   string fromEmail, 
   string subject
)

Как это работает

Статический метод UIException.Start подписывает метод HandleError на событие AppDomain.CurrentDomain.UnhandledException:
AppDomain.CurrentDomain.UnhandledException += (sender, e) => HandleError((Exception)e.ExceptionObject);

Метод HandleError:
private static void HandleError(Exception exception)
{
    try
    {
        // запускаем обработчик формы и передаем ему ссылку на форму наследованную от интерфейса IErrorHandlerForm
        new ErrorHandlerController(exception, new ErrorHandlerForm()).Run();
    }
    catch (Exception e)
    {
        // сохраняем ошибку в лог файл
        LogHelper.Logger.Error(e);
        // в случае ошибки обработки выводим сообщение с просьбой отправить лог файл разработчику на почту
       MessageBox.Show("Error processing exception. Please send log file " + LogHelper.ExceptionLogFileName + " to developer: " + Settings.ProgrammerEmail + " \r\n Exception:" + e);
        // спрашиваем нужно ли подключить отладчик
        if (MessageBox.Show("Attach debugger? \n Only for developer!!!", "Debugging...", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.Yes)
        {
            Debugger.Launch();
            throw;
        }
    }
    finally
    {
        // обязательно завершаем приложение чтобы windows не вывела стандартное сообщение об ошибке
        Environment.Exit(1);
    }
}

Интерфейс IErrorHandlerForm:
public interface IErrorHandlerForm
{
    event Action OnSendButtonClick;
    event Action OnShowErrorLinkClick;
    event Action OnLogFileLinkClick;
    event Action OnDebugButtonClick;

    // меняет высоту формы
    void SetHeight(int height);
    // задает подробное сообщение об ошибке
    string ExceptionInfoText { get; set; }
    // получает текст из поля дополнительной информации введенной пользователем
    string ExceptionDetailText { get; set; }
    // email пользователя для ответа
    string ReplyEmail { get; }
    void ShowExceptionInfoTextBox(bool isShow);
    // выводит информационное сообщение
    void ShowInfoMessageBox( string text, string caption);
    // выводит диалоговое сообщение
    bool ShowQuestionDialog( string text, string caption);
    // показывает окно в режиме диалога! необходимо чтобы приложение дожидалось закрытия окна и завершилось в finaly
    void ShowViewDialog();
    void UpdateContactEmail(string contactEmail);
}

В качестве библиотеки для логгирования используется NLog. Для того чтобы избежать появления лишних xml файлов, вся конфигурация Nlog-а делается в коде:
private static void ConfigureNlog()
{
    var config = new LoggingConfiguration();

    var fileTarget = new FileTarget();
    config.AddTarget("file", fileTarget);

    fileTarget.Layout = @"${longdate} ${message}";
    fileTarget.FileName = "${basedir}/" + ExceptionLogFileName;

    var rule2 = new LoggingRule("*", LogLevel.Trace, fileTarget);
    config.LoggingRules.Add(rule2);

    LogManager.Configuration = config;
}

Чтобы добиться максимальной простой интеграции в проект, я решил все используемые сборки объединить в одну библиотеку. Делается это при помощи приложения ILMerge, путем добавления скрипта в post-build событие:
if $(ConfigurationName) == Release (
"$(SolutionDir)ILMerge\ILMerge.exe" /out:"$(SolutionDir)Deploy\$(TargetFileName)" "$(TargetDir)*.dll" /target:dll /targetplatform:v4,C:\Windows\Microsoft.NET\Framework64\v4.0.30319 /wildcards
)



Послесловие

Данное решение было написано для достаточно крупного проекта, применяется уже более 2-х лет, значительно улучшив процесс исправления ошибок, поскольку о каждом падении приложения узнаешь моментально, без дополнительной нотификации от пользователя.

Надеюсь это все будет кому-то полезно!
Всем спасибо за внимание!
Tags:
Hubs:
+17
Comments 34
Comments Comments 34

Articles