| name | wpf-clean-architecture-mvvm |
| description | Design guidelines for WPF applications using .NET 10 C#, MVVM Toolkit, Clean Architecture, Modular Monolith, and .NET Aspire. Use this skill when creating, modifying, or reviewing any part of this application's architecture, project structure, or code organization. |
WPF Clean Architecture + Modular Monolith + Aspire Design Guidelines
.NET 10 C# + WPF で MVVM Toolkit・クリーンアーキテクチャ・モジュラーモノリス・.NET Aspire を組み合わせて構築します。
参照ドキュメント
ソリューション構成
src/
AspireAppHost/ # Aspire オーケストレーター(バックエンドインフラ管理)
ServiceDefaults/ # OpenTelemetry・サービスディスカバリの共通設定
WpfApp/ # WPF シェル(最小限のグルーコードのみ)
Shared/
Shared.Kernel/ # ドメインプリミティブ(WPF 非依存)
Shared.UI/ # UI プリミティブ(IModule・IShellService・共通スタイル)
Modules/
{ModuleName}/
{ModuleName}.Domain/
{ModuleName}.Application/
{ModuleName}.Infrastructure/
{ModuleName}.Presentation/ # Views・ViewModels・IModule 実装
tests/
{ModuleName}.{Layer}.Tests/
モジュラーモノリスのルール
- モジュール間の直接参照は禁止。モジュール間通信は
Shared.Kernel 経由(イベント・インターフェース・共通 DTO)のみ。
- UI もモジュールが所有する。Views・ViewModels・ナビゲーション登録はすべて
Presentation 層が管理する。
WpfApp は薄いシェルのみ。IModule の列挙・初期化とシェル UI(ウィンドウ枠)だけを持ち、ビジネス知識を持たない。
- 各モジュールは DI 登録拡張メソッドを
Infrastructure 層(AddXxxModule)と Presentation 層(AddXxxPresentation)にそれぞれ実装する。
クリーンアーキテクチャ 各層の責務
| 層 | 依存先 | 主な内容 |
|---|
| Domain | Shared.Kernel のみ | エンティティ・値オブジェクト・集約ルート・リポジトリ IF・ドメイン例外 |
| Application | Domain・Shared.Kernel | コマンド/クエリ・ハンドラー・DTO・バリデーション・ドメインイベントハンドラー |
| Infrastructure | Application・Domain・Shared.Kernel | リポジトリ実装・EF Core・外部クライアント・DI 登録 |
| Presentation | Application・Shared.Kernel・Shared.UI | Views (XAML)・ViewModels (MVVM Toolkit)・IModule 実装・DI 登録 |
MVVM Toolkit 使用規則
- ViewModel は
ObservableObject を継承した partial class で定義する。
- プロパティは
[ObservableProperty](フィールド名 _camelCase)、コマンドは [RelayCommand] で生成する。
- プロパティ変更時の副作用は
partial void OnXxxChanged(T value) で対応する。
CanExecute は [RelayCommand(CanExecute = nameof(CanXxx))] で指定する。
- ViewModel と View は Presentation 層の
AddXxxPresentation() で DI 登録する。
public partial class OrderListViewModel(IOrderQueryService queryService) : ObservableObject
{
[ObservableProperty] private ObservableCollection<OrderDto> _orders = [];
[ObservableProperty] private bool _isLoading;
[RelayCommand]
private async Task LoadOrdersAsync(CancellationToken ct)
{
IsLoading = true;
try { Orders = new(await queryService.GetAllAsync(ct)); }
finally { IsLoading = false; }
}
}
Shared.Kernel の内容(WPF 非依存)
Entity<TId>・ValueObject・AggregateRoot<TId>・IDomainEvent・Result<T>・共通例外基底・IEventBus
Shared.UI の内容(WPF 依存)
IModule・IShellService・NavigationItem・共通 ResourceDictionary(テーマ・スタイル)・共通コンバーター基底
インターフェース定義と実装例 → shell-pattern.md
WpfApp(シェル)の責務
Generic Host (Host.CreateApplicationBuilder()) で起動し、AddServiceDefaults() を組み込む。
ShellService / MainWindowViewModel / MainWindow を DI 登録
- 各モジュールの
AddXxxModule() + AddXxxPresentation() を呼び出す
- ビルド後、全
IModule を取得して module.Initialize(shell) を実行
MainWindow を表示する
完全なコード → shell-pattern.md
.NET Aspire 統合
AspireAppHost はバックエンドインフラ(DB・API・キュー等)をオーケストレートする。WPF アプリは登録しない(手動起動)。
ServiceDefaults を WpfApp から参照し、AddServiceDefaults() でテレメトリ・サービスディスカバリを有効化する。
- バックエンド API への接続は
https+http://{サービス名} で行う(Aspire のサービスディスカバリ)。
詳細・コード例 → aspire-integration.md
依存方向のルール(厳守)
Presentation → Application → Domain
Presentation → Shared.UI
Infrastructure → Application → Domain
WpfApp → {ModuleName}.Presentation(IModule 列挙のため)
WpfApp → Shared.UI
Shared.Kernel ← すべての層から参照可(UI 依存なし)
Shared.UI ← Presentation・WpfApp のみ参照可
AspireAppHost → バックエンドサービスプロジェクトのみ
モジュール間の直接参照 → 禁止
プロジェクトファイルと参照の設定例 → project-settings.md
コード生成時のチェックリスト
- 新しいモジュール: Domain → Application → Infrastructure → Presentation の順に4プロジェクトを作成し、Presentation から
Shared.UI を参照する。
- ViewModel:
ObservableObject + partial class + [ObservableProperty]/[RelayCommand]。
- ナビゲーション画面の追加:
IModule.Initialize() に NavigationItem を登録し、View と ViewModel を DI 登録する。
- モジュール間通信:
Shared.Kernel の IEventBus や共通 DTO を経由する。モジュール直接参照は禁止。
- 新しいユースケース: Application 層にコマンド/クエリ+ハンドラーを追加し、ドメインロジックは Domain 層に保持する。
- バックエンドサービス追加:
AspireAppHost に登録し、Infrastructure 層で https+http://{サービス名} でアクセスする。
- 新規プロジェクト作成:
ServiceDefaults を参照し、ホストビルダーで AddServiceDefaults() を呼び出す。