引き続き、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 追記 ここまで
環境
- Windows7
- .NET Framework 4.5
- Microsoft.TeamFoundation.MVVM名前空間を使ったMVVMのWPFアプリ
仕様
- ログイン画面でユーザー名を選択or入力
- ログイン後の画面で「ShowMessage」ボタンを押す
- 権限がある場合、権限があるメッセージを表示
- 権限がない場合、権限がないメッセージを表示するものの、プログラムは終了しない
- ログイン後の画面で「Throw Exception」ボタンを押す
- 例外が発生したメッセージを表示し、プログラムを終了する
画面イメージ
デザインとかはあまり考えていません...
ログイン画面
ログイン後画面
集約例外ハンドラ
WindowsFormの時は、Program.csでApplication.ThreadException
を使って集約例外ハンドラを実装していました。
.NETの例外処理 Part.1 - とあるコンサルタントのつぶやき - Site Home - MSDN Blogs
WPFではどのように書くのかを調べたところ、stackoverflowにまとまった記載があり、 App.xaml
やApp.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プロパティ- SelectedValueなどだと、選択値は取れるけど、入力値はnullになる
- 参考: - ComboBox.IsEditable プロパティ (System.Windows.Controls) - MSDN
- 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まわり