例外集約ハンドラUnhandledException

Silverlightのコントロール内でExceptionが発生した場合、通常のTryCatch文では補足できないことが多いです。そのため、Exceptionが起きないような細心の注意払う必要があります。
しかし、どんなに気をつけてもバグというものは発生してしまいます。Exceptionをキャッチできない場合、通常は次のようなダイアログが表示されます。

リリースしたサービスでこのようなダイアログが出てしまうのはあんまりなので、エラーページを表示するようにカスタマイズしたいと思います。

アジェンダ

  • ApplicationクラスのUnhandledExceptionイベント
  • Exceptionを発生させてみる。
  • Application_UnhandledException変更時の注意点

ApplicationクラスのUnhandledExceptionイベント

スローされたExceptionがキャッチされなかった場合、ApplicationクラスのUnhandledExceptionイベントが発生します。UnhandledExceptionイベントに登録したイベントハンドラでエラーページを表示します。
ここでは、App.xaml.csで定義してあるApplication_UnhandledExceptionメソッドを使用し、下記のように変更します。

private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
    // Silverlightプラグインに例外処理をこれ以上行わなく良いことを通視する。
    e.Handled = true;
            
    MainPage mainPage = (this.RootVisual as MainPage);
    if (mainPage != null)
    {
        // Errorページを表示する。
        mainPage.SetChild(new ErrorPage());
    }
}

ErrorPageを作成します。

<UserControl x:Class="UnHandledExceptionSample.ErrorPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    
    <StackPanel x:Name="LayoutRoot" Background="White">
        <TextBlock Text="システムエラーが発生しました。" FontSize="16" Margin="5,5"/>
        <TextBlock Text="ご迷惑をおかけして申し訳ありません。" Margin="5,0"/>
        <TextBlock Text="システムエラーが発生いたしました。" Margin="5,0"/>
        <TextBlock Text="システム管理者にご連絡ください。" Margin="5,0"/>
    </StackPanel>
</UserControl>

また、MainPageクラスにSetChildメソッドを追加します。

public void SetChild(FrameworkElement errorPage)
{
    // 現在表示している画面をクリアして、エラーページを表示する。
    LayoutRoot.Children.Clear();
    LayoutRoot.Children.Add(errorPage);
}

これで、エラーページの表示の実装は完了です。

Exceptionを発生させてみる。

Exceptionを発生させてエラーページが表示されるか確認してみます。ExceptionをスルーするタイミングによってSilvelightの挙動がどう変わるか調べて見ました。

  • MainPageでボタンを押したとき
  • AppクラスでRootVisualにMainPageをセットする前
  • Appクラスのコンストラクタ
  • Application_UnhandledException内
MainPageでボタンを押したときにExceptionスロー

MainPageに配置したボタンのクリックイベントのイベントハンドラを下記のようにします。

private void BtnThrowException_Click(object sender, RoutedEventArgs e)
{
    // 検証用にエクセプションを投げる。
    throw new Exception();
}

この場合、正常にErrorPageが表示されました。

AppクラスでRootVisualにMainPageをセットする前にExceptionスロー

AppクラスのApplication_Startupメソッドを下記のようにしました。

private void Application_Startup(object sender, StartupEventArgs e)
{
    throw new Exception();

    this.RootVisual = new MainPage();
}

すると、Application_UnhandledExceptionは呼び出されていますが、ブラウザではXAPローディング画面が100%のまま何もおきませんでした。

RootVisualにMainPageがセットされる前に例外が起きてしまったので、RootVisualが空のままだからこのような現象が起きます。
そこで、Application_UnhandledExceptionを下記のように変更すれば、ErrorPageが表示されるようになります。

private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
    // Silverlightプラグインに例外処理をこれ以上行わなく良いことを通視する。
    e.Handled = true;
            
    MainPage mainPage = (this.RootVisual as MainPage);
    if (mainPage != null)
    {
        // Errorページを表示する。
        mainPage.SetChild(new ErrorPage());
    }
    else if (this.RootVisual == null)
    {
        // RootVisualに何もセットされていなかった場合、ErrorPageをセットする。
        this.RootVisual = new ErrorPage();
    }
}

ちなみに、RootVisualにMainPageがセットされている状態で、ErrorPageをRootVisualにセットしてもMainPageが表示されたままとなります。どうやら一度RootVisualにセットされたインスタンスは変更不可のようです。

AppクラスのコンストラクタでExceptionスロー

Appクラスのコンストラクタ内でExceptionをスローしてみます。

public App()
{
    this.Startup += this.Application_Startup;
    this.Exit += this.Application_Exit;
    this.UnhandledException += this.Application_UnhandledException;

    InitializeComponent();

    // 検証用にエクセプションを投げる。
    throw new Exception();
}

この場合、Application_UnhandledExceptionメソッドは呼ばれるもののそこで処理がとまらず、ブラウザまでエラーが通知されてしまいます。未変更時と同じダイアログが表示されます。

Application_UnhandledExceptionメソッド内でExceptionスロー

最後に、もしApplication_UnhandledExceptionメソッド内でExceptionがスローされた場合を見てみます。

private void Application_UnhandledException(object sender, ApplicationUnhandledExceptionEventArgs e)
{
    // Silverlightプラグインに例外処理をこれ以上行わなく良いことを通視する。
    e.Handled = true;

    // 検証用にエクセプションを投げる。
    throw new Exception();
            
    MainPage mainPage = (this.RootVisual as MainPage);
    if (mainPage != null)
    {
        // Errorページを表示する。
        mainPage.SetChild(new ErrorPage());
    }
    else if (this.RootVisual == null)
    {
        // RootVisualに何もセットされていなかった場合、ErrorPageをセットする。
        this.RootVisual = new ErrorPage();
    }
}

この場合も未変更時と同じダイアログが表示されます。

Application_UnhandledException変更時の注意点

Appコンストラクタ内、または、UnhandledExceptionイベント補足後にExceptionを発生させてしまうと手のうち用がありません。よって、Appコンストラクタ内では余計な処理を行わない、UnhandledExceptionイベントハンドラのテストは十分行うなどの対処が必要になります。