◆ 目次 ◆
- はじめに
- ソリューション・プロジェクトの構成
- [UI フレームワークとデザインについて](#UI フレームワークとデザインについて)
- 利用したライブラリ
- .NetCoreの利用について
はじめに
この記事ではWPFで開発している工数管理アプリについて技術的な側面から説明します。
WPFでアプリ開発するときの一つの事例として参考にしていただければ。
# アプリ自体の説明はこちらをどうぞ ↓
WPF on .NetCore で工数管理アプリを作りました
ソリューション・プロジェクトの構成
ドメイン駆動設計のオニオンアーキテクチャを採用してます。
#オニオンアーキテクチャ自体の説明はこの記事がわかりやすいです
以前はクリーンアーキテクチャっていいなーと考えていた時期があったんですが、
(WinFormでの実装サンプル)
実践してみるとやりたいことに対してアーキテクチャがFATすぎる(要はメンドクサイ)感覚がありました。
いろいろDDDに関する記事を漁ってると、クリーンアーキテクチャはFATすぎるからオニオンのほうがオススメですよ~って書いてるのを見かけたので今回試してみた感じです。
ドメインプロジェクトは極力プレーンなC#で実装して、インプット(プレゼンテーション層、カレンダーからの予定取込、CSVファイル取込etc...)とアウトプット(永続化層、CSVファイル出力...)のインフラ依存部分がその周りを取り囲むイメージ。
#オニオンアーキテクチャでの実装にあたり、こちらの書籍が非常に参考になりました。
DDDの学習を始める際はまずはこれを読み込むのをおすすめします。「エリック・エヴァンスのドメイン駆動設計」「実践ドメイン駆動設計」よりも先にこちらを読むほうが理解しやすいかと。
https://booth.pm/ja/items/1835632
名前空間の切り方
機能ごと(関心ごと)に名前空間を切ってます。
View/ViewModel/Modelで切るのもいいんですが、機能追加するときにView/ViewModelをまぜこぜにして一箇所にファイルがまとまってるほうが便利かなって。最近は「技術」ではなく「関心」で名前空間を切るように心がけてます。
UI フレームワークとデザインについて
UI フレームワーク
UIフレームワークにはWPFを採用しました。
下記3点が主な採用理由です。
- 仕事で使っているので勝手がわかっている
- (WinFormに比べて)自由にレイアウトが組める
- UWPに比べて制約が少ない(らしい)
WinFormも仕事でよく使うんですが、オシャレな画面つくろうとするとすぐ「こんにちはOwnerDraw!!」(=描画処理をカスタマイズしてそれっぽく見せるしかない)になるのがツライ...ので僕的にはWPF一択でした。
UWPも使ってみたい気持ちはあるんですが、ここで新しいものに手を出すとアプリ完成いつになるんだ... という不安があったので今回は採用を見送りました。
デザインについて
デザインの基本原則については常に気をつけてます。
参考:https://webdesign-trends.net/entry/7810
ガイドラインとしては、MaterialDesignを採用しました。
素人が小綺麗にアプリをつくるコツは、プロがつくったデザイン規約にしっかり従うことだと思ってます。
今回のアプリについては、極力下記の規約に従って作成したつもりです。
https://material.io/
利用したライブラリ
プレゼンテーション層
MVVMアーキテクチャを採用しました。プレーンなC#ではキツイのでMVVMインフラのライブラリを利用してます。
Livet
リンク:GitHub
▽ 採用理由
・利用経験がある
・必要十分な機能が揃っている
WPFを始めたときからLivetにはお世話になってます。途中、メンテナンスが一時停止してた時期もありましたが今はokazukiさんが管理されているので安心してます。
Livetを使い始めてからそれなりに時間がたっているので、他のライブラリの情報もちょろっと集めていたのですが乗り換えるほどのメリットが感じられなかったのでそのまま採用となりました。
今回は、ViewとのMessaging機構とDisposeが必要なオブジェクトの管理が主な利用目的です。
ReactiveProperty
リンク:GitHub
▽ 採用理由
・前から使ってみたかった!
・ドメイン層 - プレゼンテーション層の変換コスト削減のため
今回初利用です。
#注意)Rxの技術自体、実際に利用するのが初めてなので、変なコード書いてるところがあるかも。。です。
いや~カッコイイですねぇ~
変更通知プロパティを定義するときの記述量が大幅に減るので、それだけでも採用の価値がある気がします。
ドメインオブジェクトのプロパティをViewModelと同期とりつつ、プレゼンテーション層のバリデーション定義をFluentにコーディングできるのがとても気持ちいいです。
Title = DomainModel.ToReactivePropertyWithIgnoreInitialValidationError(x => x.Title)
.SetValidateNotifyError(x => string.IsNullOrWhiteSpace(x) ? "タイトルは入力必須です" : null)
.AddTo(CompositeDisposable);
TaskCategory = DomainModel.ToReactivePropertyAsSynchronized(x => x.TaskCategory)
.AddTo(CompositeDisposable);
Product = DomainModel.ToReactivePropertyAsSynchronized(
x => x.ProductId,
m => _Products.FirstOrDefault(p => p.Id == m),
vm => vm?.Id ?? Identity<Product>.Empty)
.AddTo(CompositeDisposable);
あと、複数の選択肢に依存して変更したい読み込み専用プロパティを1発で書けるのも好きです!
↑ 選択年、選択月のどちらかが変わるとファイル出力するときのファイル名の初期値が自動で更新される、の図
もっとちゃんと勉強して使いこなせるようになりたいなーと思いました。
デザインToolKit
- MaterialDesignInXamlToolkit リンク:GitHub
- MahApps.Metro リンク:GitHub
#MahApps.MetroとMaterialDesignInXamlToolkitの統合はここの情報を参考にしました。
https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/wiki/MahApps.Metro-integration
▽ MaterialDesignInXamlToolkitの日本語対応について
ダイアログをいい感じに表示する機能(DialogHost.Show)があるんですが、ダイアログ内のテキストボックスで日本語入力するとIMEの推測候補画面がデスクトップの右下に飛んでいってしまうという不具合があるようで...
がんばって調査してたんですが、自分の力ではどうにもできなさそうだったので諦めて普通のWindowダイアログをそれっぽく見せることにしました。
こういうときに日本語扱うアプリは不利ですね..ネット上の情報も少ないし...
永続化層
SQLite
リンク:公式サイト
DBサーバー立てるのは メンドクサイ のとトランザクション気にする必要はほとんどないのでローカルファイルでやってしまうことに。
ローカルファイルでRDB扱うときは、SQLiteがほぼデファクトになってるんじゃないでしょうか。
Dapper
リンク:GitHub
(マイクロ)ORMとしてDapperを採用しました。
クエリは絶対自分で書きたいんですが、生ADO.NET触るのも今の時代どうなの、と思い。
アプリでちゃんと利用するのは今回が初めてでした。
オブジェクトのマッピングは自動で行いつつ、パフォーマンスは生ADO.NETと大差ないということで、パフォーマンスに優れているようです。
その他
▽ DIコンテナ:MicroResolver
リンク:GitHub
C#界で最速らしいので(初利用)。
▽ CSV取込・出力:CsvHelper
リンク:GitHub
C#でCSVといえばこれ、的な存在になりつつあるんじゃないでしょうか。
▽ 単体テスト:NUnit
特にこだわりはないです。VSTestでもなんでもいいですが、なんとなく。
.NetCoreの利用について
大きくつまづくことはなかったです。
.Net Coreを採用したのは、技術的な優位性が~というよりは
- 自分自身のスキルアップのため
- HelloWorldの記事はよくみるけど、実際のところちゃんと動くの??っていう疑問を解消したい
という観点からです。
#ちゃんと動くの??ってところに関してはなんの問題もなかったです!各種ライブラリも.NetCore対応してくれてました。
何点か軽くつまづくところがあったのでまとめておきます。
▽ バージョン情報の定義
Assemblyinfo.csが廃止されてました。バージョン情報はプロジェクトファイルに移行してます。
参考:https://rimever.hatenablog.com/entry/2019/03/12/083000
▽ SJIS対応
デフォルトではSJIS扱えない(!)ので、nugetでパッケージ入れる必要ありです。
▽ JSONの日本語がエンコードされない問題
オプションが必要になるよう。
参考:https://blog.beachside.dev/entry/2020/02/04/190000
▽ build時に言語リソースを含めない
普通にビルドすると、言語リソースファイルが大量に生成されます...
日本語圏のみで利用するなら下記のように指定すればOK。
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<SatelliteResourceLanguages>ja</SatelliteResourceLanguages>
</PropertyGroup>
参考:https://stackoverflow.com/questions/20342061/disable-dll-culture-folders-on-compile