C# + WPFで、PrincipalPermission属性を使って権限チェックし、集約例外ハンドラで例外を処理する

引き続き、Microsoft.TeamFoundation.MVVM名前空間を使って作る、WPFアプリの話です。

前回GenericPrincipalを使った認証と承認を行いましたが、IsAuthenticatedやIsInRoleの手間を省けないかなと思い、PrincipalPermission属性での権限チェックを考えました。

ただ、PrincipalPermission属性では手軽に権限をチェックできるものの権限がない場合には例外を投げるため、少々扱いに困りそうな感じでした。

そのため、WPFの集約例外ハンドラの勉強がてら、集約例外ハンドラで権限エラーをうまいこと処理することを試してみました。

 
2015/12/10 追記 ここから

公式Blogに以下の記事が掲載されましたので、Microsoft.TeamFoundation.MVVMの使用前に記事を確認してみてください。
Microsoft.TeamFoundation.MVVM 名前空間の利用について - Visual Studio サポート チーム blog - Site Home - MSDN Blogs

2015/12/10 追記 ここまで

 

環境

 

仕様

  • ログイン画面でユーザー名を選択or入力
  • ログイン後の画面で「ShowMessage」ボタンを押す
    • 権限がある場合、権限があるメッセージを表示
    • 権限がない場合、権限がないメッセージを表示するものの、プログラムは終了しない
  • ログイン後の画面で「Throw Exception」ボタンを押す
    • 例外が発生したメッセージを表示し、プログラムを終了する

 

画面イメージ

デザインとかはあまり考えていません...

ログイン画面

f:id:thinkAmi:20140904060934p:plain

 

ログイン後画面

f:id:thinkAmi:20140904060955p:plain

 

集約例外ハンドラ

WindowsFormの時は、Program.csでApplication.ThreadExceptionを使って集約例外ハンドラを実装していました。
.NETの例外処理 Part.1 - とあるコンサルタントのつぶやき - Site Home - MSDN Blogs

WPFではどのように書くのかを調べたところ、stackoverflowにまとまった記載があり、 App.xamlApp.xaml.csで実装すれば良いことが分かりました。
c# - WPF global exception handler - Stack Overflow

ただ、App.xaml.csファイルだけで完結した方が見通しが良いだろうと考え、App.xamlには何も実装しませんでした。

他に、e.Handledをtrueにしないとエラーハンドリングをしなかったと判断されてしまうため、その設定も追加しました。
Unhandled Exception Handler For WPF Applications - CodeProject

public App()
    : base()
{
    //  集約例外ハンドラ
    AppDomain.CurrentDomain.UnhandledException += (s, e) =>
    {
        UnhandledException((Exception)e.ExceptionObject, "AppDomain.CurrentDomain.UnhandledException");
    };

    DispatcherUnhandledException += (s, e) =>
    {
        e.Handled = true;
        UnhandledException(e.Exception, "Application.Current.DispatcherUnhandledException");
    };

    TaskScheduler.UnobservedTaskException += (s, e) =>
    {
        UnhandledException(e.Exception, "TaskScheduler.UnobservedTaskException");
    };
}

 

 
また、それぞれのイベントで呼ばれるUnhandledExceptionメソッドの中ではPrincipalPermission属性による例外かを判断する必要があります。

今回は違反時に飛ぶSystem.Security.SecurityException例外であるかだけで判断しています。

ただ、これだけだと一般的な例外を捕まえそうであまりよくない気もするので、コメントをいただけるとありがたいです。

private void UnhandledException(Exception exception, string eventName, bool handled = false)
{
    //  SecurityExceptionが飛んできたら権限エラーとみなす
    //  権限エラー以外でも発生しないか検討する必要はありそうだけど...
    if (exception is System.Security.SecurityException)
    {
        MessageBox.Show("権限がないため、使用できません");
    }
    else
    {
        MessageBox.Show("例外が発生したため、終了します");

        //  ログ取るとか

        //  続行できないと考えて、終了させる
        this.Shutdown();
    }
}

 

ログイン画面

View

LoginView.xamlに実装します。

 

前回と同じところ
  • Window要素
  • Window.DataContext
  • Window.Resources
  • FocusManager.FocusedElement

 

ComboBoxへのデータバインディング

今回ComboBoxへのデータバインディングでいろいろとやっていますが、

  • 入力or選択が可能なComboBoxとそれに対するデータバインディングを使ってみたかっただけ
  • ComboBoxでEnterを押した時もログイン後の画面へ遷移できますが、これはComboBoxのCommandへとデータバインディングしたかっただけ
  • TextプロパティへデータバインディングしていることからCommandのCommandParameterは不要ですが、これはViewModelでSelectedValueなどの値を見たかっただけ

ということで、深い意味はありません。

 
上記を試してみたところ、ComboBoxについては、

  • 入力可能にするために、 IsEditable="True"
  • 選択するソースを指定するために、 ItemsSourceへデータバインディング
  • IsEditable="True"とした場合、入力値・選択値を両方とも取得できるのはTextプロパティ
  • Enterを押した時というのは、ComboBox.InputBindingsのKeyBindingに設定

ということが分かり、実際のComboBoxに関するXAMLは以下のようになりました。

<ComboBox Name="comboBox" Height="100" Width="200" IsEditable="True"
          Text="{Binding Path=UserName}"
          ItemsSource="{Binding Path=ComboBoxSource}">
    <ComboBox.InputBindings>
        <KeyBinding Command="{Binding Path=LoginCommand}" Key="Enter"
                    CommandParameter="{Binding ElementName=comboBox}"/>
    </ComboBox.InputBindings>
</ComboBox>

 

ViewModel

LoginViewModel.csに実装しますが、前回と同じなので詳細は省略します。

 

ログイン後の画面

View

LoggedinView.xamlに実装します。

ログインしたユーザー名の表示とボタンを2個置くだけなので、省略します。

 

ViewModel

Button向けにICommandを何回も書くことから、T4テキストテンプレートを使うことにして、LoggedinViewModel.ttに実装します。

それ以外はLoggedinViewModel.csに実装します。

 

PrincipalPermission属性による権限チェック

今回はShowMessageボタンを押した時に権限チェックが走るため、RelayCommand経由で呼ばれるExecuteShowCommandメソッドに属性を付けます。

[PrincipalPermission(SecurityAction.Demand, Role = "Role2")]
private void ExecuteShowCommand(object x)
{
    MessageBoxService.ShowInformation("権限があります");
}

 
以上により、権限がない場合にはSecurityException例外が発生しますが、集約例外ハンドラでハンドリングされ、処理は続行となります。

 

ソースコード

GitHubに上げました。
CSharp-Sample/MVVMApp/DeclarativeSecurityCheckMVVM at master · thinkAmi/CSharp-Sample · GitHub

 

参考

集約例外ハンドラ

 

PrincipalPermissionAttributeまわり

 

ComboBoxまわり