MVVM+.NET Standard 環境でのメッセージ表示(MessageBox)実装パターン
背景
WPF アプリケーションを MVVM (Model-View-ViewModel) で構成している場合、UI ロジックとアプリケーションロジックを分離することが重要です。
一方、ビジネスロジック(Model)や ViewModel を .NET Standard プロジェクトで書いておき、UI を .NET Framework(WPF)で動かす構成にすることがあります。このとき問題になりやすいのが UI に強く依存する API(MessageBox.Show
など) をどこでどう呼び出すか、という点です。
もし Model や ViewModel が直接 MessageBox.Show(...)
を呼ぶ実装にしてしまうと、せっかく分割したはずのロジック層が UI フレームワークに依存してしまい、テストや再利用性が下がります。そこで以下のような実装パターンがよく用いられます。
UI 依存コードを分ける考え方
MVVM の原則を簡単にまとめると、Model や ViewModel は「UI フレームワークに依存しないロジックの実装」を担う ことが理想です。そのため、WPF 固有の MessageBox
のような API を直接呼び出すのは望ましくありません。
しかし、アプリケーションとしてユーザーにメッセージを見せたい場面は必ずあります。そこで「UI を抽象化するサービスインターフェースを定義し、Model や ViewModel からはそのインターフェースを呼び出すだけにする」というパターンが定着しています。
実装ステップ
1. .NET Standard 側にインターフェースを定義する
まず、.NET Standard プロジェクト(Model や ViewModel を含む層)に UI 連携用サービスのインターフェース を用意します。例として、メッセージを表示するための IMessageService
を定義しましょう。
// .NET Standard プロジェクトに定義
public interface IMessageService
{
void ShowMessage(string message, string title);
}
これにより、Model や ViewModel 側は「IMessageService
を使えばユーザーに何らかのメッセージを表示できる」ということだけを知っていれば十分になります。
2. UI 側(.NET Framework プロジェクト)に実装を置く
続いて、WPF プロジェクト(.NET Framework 側)にて、上記の IMessageService
を実際に実装します。これは WPF の MessageBox
を呼び出す具体的なクラスになります。例として、以下のような WpfMessageService
クラスを作成します。
// .NET Framework (WPF) プロジェクトに置く
public class WpfMessageService : IMessageService
{
public void ShowMessage(string message, string title)
{
System.Windows.MessageBox.Show(message, title);
}
}
このクラスは WPF 固有の API を使っていますが問題ありません。あくまで「UI に依存する実装」のためのクラスだからです。逆に、このクラスを .NET Standard には配置しないのがポイントです。
3. ViewModel での利用方法: DI(依存性注入)
ViewModel がメッセージを表示したい場合は、IMessageService
経由で呼び出します。以下にサンプルコードを示します。
public class MainViewModel
{
private readonly IMessageService _messageService;
// コンストラクタやプロパティ、あるいは DIコンテナから注入を行う
public MainViewModel(IMessageService messageService)
{
_messageService = messageService;
}
public void SomeCommand()
{
// ユーザーにメッセージを表示したい
_messageService.ShowMessage("Hello world!", "Greetings");
}
}
このように実装しておけば、ViewModel は MessageBox.Show
のような具体的なメソッドを全く知らずに済みます。つまり UI フレームワークへの依存が排除された状態 を保てます。
テストが容易になるメリット
この構成にすると、ユニットテストや自動テストが格段にやりやすくなります。たとえばテストでは実際の MessageBox
を表示したくないので、モックやスタブを注入するようにできます。
// テスト用のダミー実装
public class TestMessageService : IMessageService
{
public void ShowMessage(string message, string title)
{
// テストではログに残すだけとか、呼び出し回数をカウントするだけとか
// UI は一切出さない
Console.WriteLine($"[TestMessageService] {title}: {message}");
}
}
テストコード中では、TestMessageService
を ViewModel に注入して検証できます。UI に依存しないロジック部分がやりやすくなるのは大きなメリットです。
よくある質問
Q1. Model 層でメッセージを表示しても良いか?
厳密には Model も「ドメインロジックを表す層」と考えたとき、UI を全く知らないことが望ましいです。もし Model がメッセージを出す必要があるときも、やはり「メッセージ通知用のイベントやインターフェースを通じて」ViewModel などに通知する設計が自然です。直接 MessageBox.Show()
と結び付けることは避けた方がよいでしょう。
MessageBox
が使えないのはなぜ?
Q2. .NET Standard プロジェクトで .NET Standard
はプラットフォーム中立なライブラリを作るための仕様であり、WPF のような Windows 固有の要素は含まれていないためです。だからこそ .NET Standard
を利用する場合は、UI の実装を別プロジェクト(.NET Framework や .NET Core WPF プロジェクトなど)に分離する必要があります。
まとめ
-
Model や ViewModel(.NET Standard) からは直接
MessageBox.Show
を呼ばず、UI 依存の処理を抽象化したサービスインターフェースを定義する。 -
WPF 側(.NET Framework) で、そのサービスインターフェースを
MessageBox
を使って実装する。 - ViewModel への注入 (DI) によって、UI とロジックを明確に分離でき、テストや保守が容易になる。
MVVM では「UI に依存しないモデル層・ビュー モデル層を保つ」ことが重要です。UI ロジックは WPF プロジェクトに集中させ、.NET Standard にはプラットフォームに依存しない純粋なロジックとそのための抽象化を置く、という構成を押さえておくと、長期的な保守性とテスト容易性を手に入れられます。
Discussion