Azure VM 2台 を超簡単にLoad Balancer構成してみた

先日、Azureの障害祭りがあったということで・・・というわけではないですが(笑)
2台構成の Azure Virtual Machine(高可用性)を、「超簡単に」組むというのをやってみました。

また、今更どうこう言うまでもなく ResourceManager ベースになってから、Azureの構成は、よりオートメーション化が可能になっています。

Azure QuickStart Template

GithubのAzure QuickStart Templateというところに、かなり使えるテンプレート(json構成ファイル)がたくさん落ちています。

github.com

もう山のようにいろんなテンプレートが落ちていますね。
これらをベースにjsonファイルいじって、自分の必要とする構成をカスタムに構成すれば工数の削減につながりますね。
また、ドキュメント読んだだけじゃ、よくわからなかったなぁ・・というようなものも、実際に動くテンプレートサンプルを使うと非常に理解しやすいですね。

2台構成&LoadBalancerのテンプレート

上記 Azure QuickStart Template の中から今回使うのは「2台構成&LoadBalancerのテンプレート」ということで以下になります。

github.com

上記URLを開くと、親切にも以下の赤枠にあるように「Deploy to Azure」というリンクが用意されています。

f:id:daigo-knowlbo:20170321012755p:plain

リンク先は以下の通りで、Githubに置いてある azuredeploy.json を Azureポータルに食わせるリンクになっています。

https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2Fazure-quickstart-templates%2Fmaster%2F201-2-vms-loadbalancer-lbrules%2Fazuredeploy.json

デプロイ

「Deploy to Azure」リンクをクリックすると、Azure Portalに移動し、構成を組むためのパラメータの入力画面になります。
パラメータを適当に埋めます。

f:id:daigo-knowlbo:20170321013359p:plain

ここではリージョンは西日本にしました。
また、VMサイズは、このテンプレートではデフォルトで Standard_D1 になっています。
「購入」ボタンをクリックしましょう。

「リソース グループ」を確認すると、「Win2VM(先程のパラメータで入力したリソースグループ名)」が作成されています。

f:id:daigo-knowlbo:20170321014136p:plain

VMが作成されるまで数分かかると思うので、作成が完了するまで待ちましょう。

構成を確認する

Win2VMリソースグループは以下のような構成です。

f:id:daigo-knowlbo:20170321014104p:plain

以下のような、2台のVMロードバランサーする最低限の構成がしっかり組まれています。

  • 可用性セット
  • ロードバランサー
  • パブリックIP
  • Virtual Machine + Disk + NIC 2つ
  • 仮想ネットワーク
  • ストレージアカウント

myAvSet(可用性セット)

myAvSet(可用性セット)は、以下のような構成になっています。

f:id:daigo-knowlbo:20170321014307p:plain

障害ドメインは2つ、更新ドメインも2つ、それぞれのドメインに対してVMが割り当てられています。
AzureポータルのGUI上から可用性セットを作成すると「障害ドメイン 2つ、更新ドメイン 5つ」がデフォルトになっています。
この構成JSONをベースにして3台以上の構成をとる場合には適時 障害ドメイン及び更新ドメインを拡張する必要があります。
更新ドメインが2つのまま3台目のVMを追加すると、1つ目のVM(myVM0)と同一更新ドメインに配置されてしまいます。つまり、メンテナンス時に2つのVMが同時に再起動される可能性が発生してしまいます。

ロードバランサーのプローブ

ロードバランサーのプローブは、TCPでポート80、5秒ごとのチェック、以上閾値は2、で構成されています。

f:id:daigo-knowlbo:20170321014856p:plain

Virtual Machine のIISを構成

作成された Virtual MachineにはまだIISが構成されていません。
2つのVMにそれぞれIISを構成しましょう。
myVM0 を選択し、「接続」をクリックするとリモートデスクトップ接続ファイルがダウンロードされます。

f:id:daigo-knowlbo:20170321015116p:plain

通常のWindows Serverへのリモートデスクトップになりますので、IISを構成しましょう。

まあ、例のコレですね・・・(途中端折ります)

f:id:daigo-knowlbo:20170321015434p:plain
f:id:daigo-knowlbo:20170321015544p:plain
f:id:daigo-knowlbo:20170321015552p:plain
f:id:daigo-knowlbo:20170321015602p:plain

※myVM1にも同様にIISを構成しましょう。

LoadBalancerの動きを確認する

これでクライアントからのHTTPリクエストに対して、「2台に負荷分散&1台に障害が発生したらもう1台に振り分け」という高可用性なサーバー構成が完了しました。

本当に障害発生時にもう片方にリクエストが振り分けられるかどうか確認してみます。
myVM0 の c:\inetpub\wwwroot に、以下の index.html を配置します。

Hello. I'm myVm0.

myVM1 の c:\inetpub\wwwroot には、以下の index.html を配置します。

Hello. I'm myVm1.

URL(index.html)にアクセスるする

では、作成した index.html にアクセスしてみましょう。
ブラウザで、URLにアクセスします。
URLのIPアドレスロードバランサー(myLB)の「パブリックIPアドレス」で確認することができます。
ここでは「52.175.152.10」でした。

f:id:daigo-knowlbo:20170321020634p:plain

http://52.175.152.10」にブラウザでアクセスすると・・・私の環境では myVM0 につながりました。

f:id:daigo-knowlbo:20170321020711p:plain

では myVM0 のIISを停止してみます。

f:id:daigo-knowlbo:20170321020901p:plain

再び「http://52.175.152.10」をアクセスすると・・・

f:id:daigo-knowlbo:20170321020957p:plain

myVM1 につながりました!OKです!

まとめ

一昔前はクラスタリング構成のサーバーを組む(もしくは実証試験する)なんて、物理サーバー2台用意して、ケーブルつないで、OSメディア用意して・・・なんて大ごとでしたが、Azureのようなクラウド環境を利用するとサクサクっと社会人1年目でも簡単に組めちゃう素晴らしき時代ですね!
ただ、そのベースにある技術を理解しておくことが重要だと思います!(って人に言える程、私自身が有識者ではないが・・・)

C# 7 の Tuple は戻り値が複数の時に便利

2017/3/11、「Visual Studio 2017 リリース記念勉強会 & まどすた #2」に参加してきました。
1コマ目のセッションとして、岩永 信之(@ufcpp)さんの「C# 7」セッションを聞かせていただきました。
C# 7の新機能について非常に細かく説明していただきましたが、その中で Tuple についてフォーカスしてブログっておきます。

※以下は、セッションでの解説内容ではなく、それを受けて私が調査した内容になります。

Tuple は戻り値が複数の時に便利

ということで、C# 7 で使い勝手がよくなった Tuple ですが、複数の戻り値があるメソッドで使うのが最も美しい使い方だと思いました。
(Tupleは乱用すると見通しの悪い、属人 巧 のコードが生成される可能性があると思っているので)

では、以下のCompanyクラスをベースに説明を進めます。

// 会社クラス
public class Company
{
  ... 正社員数とパートナー社員数を取得するメソッドを追加していくよ!
}

2つの戻り値を持つ同期メソッドの場合

Companyクラスに以下のメソッドを追加します。

[リスト1]
// Companyクラスに追加するメソッド
// outパラメータを使用して2つの戻り値を返す同期メソッド
static public void GetEmployeeCountSync(
  out int properCount, 
  out int partnerCount)
{
  properCount = 100;
  partnerCount = 30;
}
// リスト1の呼び出し側
Company.GetEmployeeCountSync(out int properCount, out int partnerCount);
Console.WriteLine("proper:{0}  partner:{1}", properCount, partnerCount);

// 出力結果:proper:100  partner:30

2つの戻り値があるので、outパラメータを使うというよくある実装です。
※呼び出し側実装における、outパラメータを事前宣言せず、メソッド呼び出し時に宣言する手法はC# 7の新機能です。

次にリスト1を Tuple戻り値 を使って書き換えます。

[リスト2]
// Companyクラスに追加するメソッド
// Tupleを使用して2つの戻り値を返す同期メソッド
static public (int properCount, int partnerCount) GetEmployeeCountSyncTuple()
{
  int properCount = 100;
  int partnerCount = 30;

  return (properCount, partnerCount);
}
// リスト2の呼び出し側
(int properCount, int partnerCount) = Company.GetEmployeeCountSyncTuple();
Console.WriteLine("proper:{0}  partner:{1}", properCount, partnerCount);
// 出力結果:proper:100  partner:30

または、以下のように呼び出すこともできます。

// リスト2の呼び出し側
var result = Company.GetEmployeeCountSyncTuple();
Console.WriteLine("proper:{0}  partner:{1}", result.properCount, result.partnerCount);
// 出力結果:proper:100  partner:30

戻り値の Tupleオブジェクト を分割して各々の変数に受け取るか、まとめて受け取るかの違いになります。

2つの戻り値を持つ非同期メソッドの場合

この非同期メソッドのケースで Tuple戻り値 の威力がより発揮されると思います。
非同期メソッド、つまり asyncキーワードの付いたメソッドでは、「引数に out / ref キーワードをつけることができません」。これは言語仕様としてそのようになっています。

[リスト3]
// これはコンパイルエラー
static public async Task GetEmployeeCountAsync(
  out int properCount,  // asyncなのにoutはNG
  out int partnerCount)
{
  properCount = 100;
  partnerCount = 30;

  return;
}

その為、複数の戻り値を持つ必要がある場合、それらを集約したクラスを定義する必要があります。
例えば以下のような感じ。

[リスト4]
public class CountResult
{
  public int ProperCount { get; set; }
  public int PartnerCount { get; set; }
}
...
static public async Task<CountResult> GetEmployeeCountAsync()
{
  CountResult result = new CountResult() { properCount = 100, partnerCount = 30 };
  // ...
  return result;
}

しかし、Tupleを利用すれば同期メソッドと同様の実装方式をとることができます。

[リスト5]
// Companyクラスに追加するメソッド
// Tupleを使用して2つの戻り値を返す非同期メソッド
static public async Task<(int properCount, int partnerCount)> GetEmployeeCountAsyncTuple()
{
  int properCount = 100;
  int partnerCount = 30;

  await Task.Delay(1000);

  return (properCount, partnerCount);
}
// リスト5の呼び出し側
(int properCount, int partnerCount) = Company.GetEmployeeCountAsyncTuple().Result;
Console.WriteLine("proper:{0}  partner:{1}", properCount, partnerCount);

// 出力結果:proper:100  partner:30

戻り値がオブジェクト型の場合

前述の例では戻り値の型がリテラル型でした。
オブジェクト型の場合は、非同期メソッドにおいても引数渡しで結果を受け取ることが出来ます(C# 7に関係なく従来の言語仕様的に)。
その部分の振る舞いを見てみたいと思います。

[リスト6]
static public async Task GetEmployeesAsync(
  List<Employee> properEmployees, 
  List<Employee> partnerEmployees)
{
  for (int i = 0; i < 10; i++)
  { // 1秒毎にEmployeeを追加
    await Task.Delay(1000);
    properEmployees.Add(
      new Employee() { Name = "proper" + i.ToString() });
  }

  for (int i = 0; i < 5; i++)
  { // 1秒毎にEmployeeを追加
    await Task.Delay(1000);
    partnerEmployees.Add(
      new Employee() { Name = "partner" + i.ToString() });
  }

  return;
}
// リスト6の呼び出し側
List<Employee> properEmployees = new List<Employee>();
List<Employee> partnerEmployees = new List<Employee>();
var task = Company.GetEmployeesAsync(properEmployees, partnerEmployees);
while (true)
{
  Console.WriteLine("{0} proper:{1}  partner{2}", 
    DateTime.Now.ToString("H:m:s"), 
    properEmployees.Count, 
    partnerEmployees.Count);

  System.Threading.Thread.Sleep(1000);

  if (task.IsCompleted) // Taskの終了を確認
    break;
}

// 出力結果:proper:100  partner:30
17:21:5 proper:0  partner0
17:21:6 proper:1  partner0
17:21:7 proper:2  partner0
17:21:8 proper:3  partner0
17:21:9 proper:4  partner0
17:21:10 proper:5  partner0
17:21:11 proper:6  partner0
17:21:12 proper:7  partner0
17:21:13 proper:8  partner0
17:21:14 proper:9  partner0
17:21:15 proper:10  partner0
17:21:16 proper:10  partner1
17:21:17 proper:10  partner2
17:21:18 proper:10  partner3
17:21:19 proper:10  partner4

非同期メソッド GetEmployeesAsync() を呼出し後、whileループで引き渡したEmployeeオブジェクトを監視&コンソール出力しています。
properEmployees.Count / partnerEmployees.Countが順次カウントアップされているのが確認できます。
GetEmployeesAsync()は1秒毎にEmployeeオブジェクトを追加し、呼び出し元は1秒毎にEmployeeリストオブジェクトの内容を確認しています。
つまり、同一のオブジェクトを別スレッドで互いに参照している状態になっています。これについては、プログラム上のデッドロック等が発生しないように考慮が必要なケースがあるでしょう。

リスト6の実装をTuple戻り値版に書き換えたものが以下になります。

[リスト7]
// リスト6をTuple戻り値版に書き換え
static public async Task<(List<Employee> properEmployees, List<Employee> partnerEmployees)> GetEmployeesAsyncTuple()
{
  List<Employee> properEmployees = new List<Employee>();
  List< Employee > partnerEmployees = new List<Employee>();

  for (int i = 0; i < 10; i++)
  {
    await Task.Delay(1000);
    properEmployees.Add(new Employee() { Name = "proper" + i.ToString() });
  }

  for (int i = 0; i < 5; i++)
  {
    await Task.Delay(1000);
    partnerEmployees.Add(new Employee() { Name = "partner" + i.ToString() });
  }

  return (properEmployees , partnerEmployees);
}
// リスト7の呼び出し側
var (properEmployees, partnerEmployees) = Company.GetEmployeesAsyncTuple().Result;

もしくは

// リスト7の呼び出し側
var task = Company.GetEmployeesAsyncTuple();
(properEmployees, partnerEmployees) = task.Result;

いずれにしても、2つの戻り値(結果)オブジェクトは、GetEmployeesAsyncTuple()非同期メソッド処理が完了してから得られる(処理中には対象オブジェクトへの参照が得られない)為、対象オブジェクトへのスレッドセーフが保証されます。

所感

本投稿の Tuple に限らず、C# 7は「言語仕様としての成熟期に入った」という印象です。
内部関数だったり、「=>」の強化による短縮記述だったり、より洗練された簡潔なコードの記述が可能になっています。
と、同時に思うことは、開発メンバーが非精鋭なチームの場合、洗練された簡潔なコードよりも「冗長だけど昔の技術知識で読み解けるコード」っていうのも重宝されるのかなぁ・・・なんていうネガティブな思いです(笑)。
でも、まあ、LINQなんかもそうだけど、しばらく時が経てば標準的に使われるようになるのかな、とも思います。

参考URL

MS公式 C# 7 の新機能紹介は以下
blogs.msdn.microsoft.com

岩永さんの C# 7 解説記事は以下
ufcpp.net

「2017/3/11 Visual Studio 2017 リリース記念勉強会」岩永さんセッションのスライドは以下
docs.com

VS2017よりもっと先取りしたい! ~ Visual Studio Preview

2017/3/7に Visual Studio 2017 がリリースされました。
多くの機能が盛り込まれ、そして改善されました。

私を含め皆さんも Visual Studio 2017 の新機能を試して、プロジェクトへの導入の検討をしていることでしょう。

しかし、ギーク達は更なる「おもちゃ」を追い求めることでしょう。

ということで「Visual Studio Preview」です!

Visual Studio Previewとは

Visual Studio Previewは、アーリーアダプターのために最新の機能をいち早く取り込んだ Visual Studio です。
つまり、Stableな製品版 Visual Studio 2017 にはまだ取り込まれていない段階の最新機能が Visual Studio Preview として提供されます。

Visual Studio 2017 と Visual Studio Preview は Side-by-Side 可能

これ、結構嬉しい事だと思います。
「VS2017」と「VS Preview」は、同一環境に並行してインストール可能です。
(とはいっても、仮想環境構築が容易な今の時代的には、VM立てちゃったほうが精神衛生的に良いかもしれないけど・・・でも、公式にサイドバイサイドOKとアナウンスされています。)

Visual Studio Previewをインストールする

では、Visual Studio Previewをインストールしてみましょう!
ここでは、Visual Studio 2017をインストール済みの環境に、Visual Studio Previewを追加でインストールしてみます。
(あっ、でも以下は、実は物理PCではなく、Hyper-V上のWindows10 + VS2017の環境にVS Previewを追加インストールしています(汗))

1.ダウンロード

以下のURLをブラウザで開きます。

www.visualstudio.com

「Download Visual Studio」にマウスを乗せるとエディション一覧がプルダウンされるので、ご希望のエディションをクリックしましょう。
私は Enterpriseを選択しました。
セットアップ用exeがダウンロードされます。

2.セットアップ実行

ダウンロードしたセットアップ用exeを実行します。

Visual Studio 2017セットアップで見慣れた画面が表示されます。
続行をクリックします。

f:id:daigo-knowlbo:20170310032357p:plain

インストール対象を選択する画面が表示されますが、「Python 開発」が表示されているのを確認することができます。
これ、StableなVisual Studio 2017にはまだ取り込まれていません(2017/3/10現在)。

f:id:daigo-knowlbo:20170310032442p:plain

インストールボタンをクリックすると、インストールが開始されます(ここでは「ワークロード」の機能をすべて選択しました)。

以下の画面をよく見ると、画面左側に「インストール済み Visual Studio 2017 Enterprise」と表示されています。
そして、その右側に「Visual Studio Enterprise 2017(2)」と表示されています(これが、今インストールしているVisual Studio Preview)。

f:id:daigo-knowlbo:20170310032721p:plain

3.起動

インストールが終わったら「起動」ボタンをクリックします。

f:id:daigo-knowlbo:20170310033058p:plain

バージョン情報を確認してみましょう(メニュー「ヘルプ → Microsoft Visual Studioのバージョン情報」)。
以下のように「Version 15.1(26304.0 Preview)」の文字を確認することが出来ます。

f:id:daigo-knowlbo:20170310033258p:plain

4.Pythonプロジェクトを作成してみる

Pythonサポートは、Preview版で先行リリースされている機能です。
これを使ってみようと思います。

メニュー「ファイル → 新規作成 → プロジェクト」を選択します。
以下のように、Pythonテンプレートが表示されました。Python Applicationを選択してOKをクリックします。

f:id:daigo-knowlbo:20170310033631p:plain

以下のようなプロジェクトが作成されます。

f:id:daigo-knowlbo:20170310034016p:plain

PythonApplication1.pyを適当に以下のように実装します。

print("Hello Python")

CTRL + F5 で実行してみましょう。

f:id:daigo-knowlbo:20170310034207p:plain

はい、実行されました。

まとめ

Visual Studio 2017自体がいろいろ盛りだくさんなのですが、Visual Studio Previewなんかの先行リリースも常に軽く眺めておくと時代についていくのが楽になるのではないかと思います。
まとめっちゅうまとめは特にないのですが、興味がある方は Visual Studio 2017 に加えて Visual Studio Preview も入れても面白いのではないかと思います。

参考URL:

blogs.msdn.microsoft.com

Docker Support で ASP.NET Core アプリを作る - Visual Studio 2017

Visual Studio 2017がいよいよ正式リリースされました。
個人的感想としたは「超目玉!!!」な機能は、感じられていないのですが、個々の技術要素は非常に興味深く思っています。
昔に比べて、各情報は小出しにリリースされるので上記のような感想を持つ形になるのでしょう。昔は大きな区切りで一気にいろんな機能がリリース、という流れだったので・・・

で、今回はVisual Studio 2017を利用し、ASP.NET Coreアプリ を Docker Support で開発実行してみようと思います。

Docker for Windowsのインストール

まずは Docker for Windows をインストールします。
以下のURLにアクセスし Stable channel 版をインストールすればOKです。

Install Docker for Windows - Docker Documentation

f:id:daigo-knowlbo:20170309015615p:plain

うまくインストールされれば、タスクトレイに例のクジラが現れます。

f:id:daigo-knowlbo:20170309015843p:plain

プロジェクトの作成

Visual Studio 2017を起動します。
メニューから「ファイル → 新規作成 → プロジェクト」を選択します。
テンプレートは「Visual C# → Web → ASP.NET Core Webアプリケーション(.NET Core)」を選択し、名前は「HelloAspNetCore」とします。ちなみに場所は「J:\Projects」としました。
OKをクリックします。

f:id:daigo-knowlbo:20170309020131p:plain

次に表示されたウィンドウで「.NET Coreのバージョンは1.1」「Web アプリケーション」を選択します。
そして今回最も重要なのは、左下の「Docker サポートを有効にする」にチェックをつけることです。
で、OKをクリックします。

f:id:daigo-knowlbo:20170309020210p:plain

自動生成されたソリューションは以下の通りです。
Docker構成(docker-compose)が自動生成されたことが確認できます。

f:id:daigo-knowlbo:20170309020424p:plain

Dcokerを設定する

VS上でそのまま実行(CTRL + F5)したいところですが、このままではエラーとなってしまいます。
Docker側の設定が必要です。
タスクトレイのDockerアイコン(クジラ)をマウス右ボタンクリックし「Settings…」を選択します。
Settingsウィンドウが表示されるので、左側のタブから「Shared Drives」を選択し、「システムドライブ(C)」及び「プロジェクトドライブ(J)」を Shared として、Applyボタンをクリックします。

f:id:daigo-knowlbo:20170309020848p:plain

更新&Docker再起動まで少し待ちましょう。
Settingsウィンドウの左下の表示が「Uodating Drives…」から「Docker is running」となったらOKです。

実行!

Visual Studio 2017に戻ってプロジェクトを実行しましょう!
(ビルド)ツールバーに「▶Docker」と表示されているので、これをクリックします(「CTRL + F5」でもOK)。

f:id:daigo-knowlbo:20170309021219p:plain

すばらしい!
Visual Studio 2017で作成した ASP.NET Coreアプリが、Docker上で実行されました!

f:id:daigo-knowlbo:20170309021236p:plain

本当にDockerで実行されたか確認する

あまりに簡単すぎて、これは本当にDokcerで実行されているの?
と、いうことで確認してみましょう。

コマンドプロンプトを開きます。
以下のdockerコマンドを実行してみましょう。

docker images
docker ps -all

結果は以下です。

f:id:daigo-knowlbo:20170309021546p:plain

うん、今作成したプロジェクトがdocker上に作成されています。

まとめ

VSのDockerサポートは.NET Coreのみの機能ですが、非常にシームレスに統合されているように感じました。
.NET Framework / .NET Core / Xamarinというマルチプラットフォームの統合は .NET Standard 2.0 であり、もう少し時間がかかりますが、.NET Core + Dcoker という点については、開発において色々な可能性を秘めているのではないでしょうか。

Portability Analyzerを使ってライブラリの.NET Standard準拠を調べよう

Visual Studio 2017のリリースを迎え(るにあたり)、(技術的には、直接的にそこに関連付いているわけではないけれど)「.NET Standard」というキーワードが強く聞こえてくるようになった気がしています。

そうそう、先日、 Xamarin Studio も .NET Standard Library 対応しましたね。

blog.xamarin.com

ということで、今回は既存資産のライブラリ実装が、.NET Standardに準拠しているかどうか調べることができる「Portability Analyzer」というものを使ってみたいと思います。

Githubリポジトリは↓↓↓

github.com

ちなみにGithubからソース取らなくても、以下のGUI操作だけで使えます。

※以下はVisual Studio 2015を使います。明日か明後日にVS2017版に差し替えるかも・・・

.NET Portability Analyzerをインストール

※2017.3.8追記
2017/3/8現在のVisual Studio 2017では「拡張機能と更新プログラム」からだと「.NET Portable Analyzer」が出てこないみたいです。
以下から.vsix ファイルをダウンロードしてダブルクリックでインストールするとVS2017でも使えるようになります。
でも、VS2017が不安定になる可能性があります、と警告文が出るので自己責任でご利用ください(正式対応じゃないってことですね)。私の環境では、特に何かが変になることなく動いていました。

.NET Portability Analyzer - Visual Studio Marketplace


Visual Studio 2015を起動。
メニュー「ツール → 拡張機能と更新プログラム」を選択。

f:id:daigo-knowlbo:20170307122708p:plain

「オンライン → .NET Portable Analyzer」を選択してインストール

f:id:daigo-knowlbo:20170307122719p:plain

VSの再起動を促されます。

調査対象のライブラリプロジェクトを開く

調査対象のライブラリプロジェクトをVisual Studioで開きます。
今回は以下のような「OmnipotentLibrary」というライブラリプロジェクトを調査対象としました。

f:id:daigo-knowlbo:20170307123000p:plain

プロジェクトに含まれるMyLogger.csは以下のような、Reflectionを使ったキビシイ実装です。

// MyLogger.cs
using System;
using System.Security;
using System.Reflection;
using System.Diagnostics;

namespace OmnipotentLibrary
{
  public class MyLogger
  {
    /// <summary>
    /// メソッドの開始時に呼び出します。
    /// </summary>
    /// <returns></returns>
    [DynamicSecurityMethod]
    public static DateTime Start()
    {
      DateTime now = DateTime.Now;

      const int callerFrameIndex = 1;
      StackFrame callerFrame = new StackFrame(callerFrameIndex);
      MethodBase callerMethod = callerFrame.GetMethod();
      Debug.WriteLine(string.Format("start {0}()", callerMethod.Name));

      return now;
    }

    /// <summary>
    /// メソッドの終了時に呼び出します。
    /// </summary>
    /// <param name="start"></param>
    [DynamicSecurityMethod]
    public static void End(DateTime start)
    {
      DateTime now = DateTime.Now;

      TimeSpan ts = now - start;

      const int callerFrameIndex = 1;
      StackFrame callerFrame = new StackFrame(callerFrameIndex);
      MethodBase callerMethod = callerFrame.GetMethod();
      Debug.WriteLine(string.Format("end {0}(): {1}ms", callerMethod.Name, ts.TotalMilliseconds));
    }
  }
}

namespace System.Security
{
  [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = false)]
  internal sealed class DynamicSecurityMethodAttribute : Attribute
  {
  }
}

Portability Analyzerの設定を行う

ソリューションエクスプローラでプロジェクトを選択、マウス右ボタンクリック、メニュー「Prtable Analyzer Settings」を選択します。

f:id:daigo-knowlbo:20170307134814p:plain

表示された以下のウィンドウで「Target Platforms」を選択します。
「Target Platforms」とは、アナライズ対象のプラットフォームです。調査対象のプロジェクト(OmnipotentLibrary)が、「対象のプラットフォームに準拠しているか?」調べる対象になります。

f:id:daigo-knowlbo:20170307134827p:plain

ここでは、.NET Core 1.0 / 1.1 / 2.0、.NET Framework 4.5 / 4.5.1 / 4.5.2 / 4.6 / 4.6.1 / 4.6.2、.NET Standard 1.0 / 1.1 / 1.2 / 1.3 / 1.4 / 1.5 / 1.6 / 2.0、Xamarin Android 1.0.0、Xamarin.iOS 1.0.0.0に準拠しているか?を、アナライズする設定としました。

Portability Analyzerを実行する

ソリューションエクスプローラでプロジェクトを選択、マウス右ボタンクリック、メニュー「Analyzer Azzembly Portability」を選択します。

f:id:daigo-knowlbo:20170307135554p:plain

アナライズが完了すると、「Portability Analyze Results」ウィンドウに結果が表示されます。

f:id:daigo-knowlbo:20170307135652p:plain

「Open Report」をクリックすると結果のExcelファイルが起動されます。

結果を分析する

アナライズ結果のExcelは、2シート構成です。

1シート目は結果のサマリーになっています。
横長なので折り返し加工していますが、以下のような感じです。

f:id:daigo-knowlbo:20170307140356p:plain

表のヘッダ(青い部分)が「ターゲットプラットフォーム」、その下の数値が「分析結果のスコア」です。
スコア100(緑)が準拠OK、スコア100未満(オレンジ)が準拠NG、となります。
このライブラリはReflection系を使用しているので、.NET Core 1.0 / 1.1 や .NET Standard 1.xなどでは結果NGとなりました。

2つ目のシートには、スコアが100未満であった原因の詳細がリストされています。

f:id:daigo-knowlbo:20170307140524p:plain

T:System.Diagnostics.StackFrame とか T:System.Runtime.InteropServices.GuidAttributeを使ってるのが悪いよー、Supported: 2.0+じゃないと使えないよー、っていう結果が分かります。

まとめ

ということで

Let’s head to .NET Standard!

Introducing .NET Standard | .NET Blog

docs.microsoft.com

Visual Studio 2017 で Live Unit Testing(xUnit)した感想

3月7日にVisual Studio 2017の正式リリースを控えた週末に、RC版でチョロチョロと遊んでいました。
その中で「Live Unit Testing」について書いてみたいと思います。
この機能は、結構わかりやすい機能で、すでに多くの方がブログ等で紹介されています。
MsTestベース の説明が多そうなので、大して変わりませんが xUnit を使った Live Unit Testing をここでは紹介します。

テスト対象のプロジェクトを作成

プロジェクトの作成

メニュー「ファイル → 新規作成 → プロジェクト」を選択。
テンプレートは「クラスライブラリ(.NET Framework)」、プロジェクト名は「HogeHogeLibrary」とします。

f:id:daigo-knowlbo:20170306004125p:plain
※余談ですが「.NET Core」とか「.NET Standard」とか「ポータブル」とか、きちんと学んでいないと何が何だかになってしまいそうですね・・・3/7版でもこのラインで行くのかな?(まあ、現在の.NETの状況的にはこれが正しい姿ですね)

テスト対象クラスの追加

「Class1.cs」は削除して、テスト対象のクラスを追加します。
ソリューションエクスプローラで HogeHogeLibrary をマウス右ボタンクリック。メニューの「追加 → クラス」を選択します。

f:id:daigo-knowlbo:20170306004647p:plain

「Programmer.cs」を追加します。

f:id:daigo-knowlbo:20170306005417p:plain

実装は以下の通り。

// HogeHogeLibrary\Programmer.cs
using System;

namespace HogeHogeLibrary
{
  public class Programmer
  {
    public int Stamina { get; set; } = 100;

    public void Work(int hour)
    {
      this.Stamina -= hour * 10;
    }

    public void HaveLunch()
    {
      this.Stamina += 30;
    }

    public void Sleep(int minutes)
    {
      this.Stamina += minutes * 5;
    }
  }
}

彼は
* スタミナがデフォルトで100あります。
* 1時間働くとスタミナが10減ります。
* ランチをとるとスタミナが30増えます。
* 1分居眠りするとスタミナが5増えます。

テストプロジェクトを作成

プロジェクトの作成

では Programerクラス をテストするプロジェクトを作成します。
ソリューションエクスプローラで、ソリューションをマウス右ボタンクリック。メニューの「追加 → 新しいプロジェクト」を選択します。

f:id:daigo-knowlbo:20170306010015p:plain

テンプレートは「クラスライブラリ(.NET Framework)」、プロジェクト名は「HogeHogeLibrary.Test」とします。

f:id:daigo-knowlbo:20170306010134p:plain

xunitをNuGetから追加

ソリューションエクスプローラからHogeHogeLibrary.Testをマウス右ボタンクリック。メニューの「NuGetパッケージの管理」を選択します。

f:id:daigo-knowlbo:20170306010329p:plain

「xunit」をインストールします。

f:id:daigo-knowlbo:20170306010600p:plain

「xunit.runner.visualstudio」をインストールします。

f:id:daigo-knowlbo:20170306010824p:plain

HogeHogeLibraryへの参照を追加

テスト対象プロジェクト「HogeHogeLibrary」への参照を追加します。
ソリューションエクスプローラで「HogeHogeLibrary.Test → 参照」をマウス右ボタンクリック。メニューの「参照の追加」を選択します。

f:id:daigo-knowlbo:20170306011121p:plain

f:id:daigo-knowlbo:20170306011115p:plain

テストクラスを追加

「Class1.cs」は削除して、テスト対象のクラスを追加します。
ソリューションエクスプローラで HogeHogeLibrary.Test をマウス右ボタンクリック。メニューの「追加 → クラス」を選択します。

f:id:daigo-knowlbo:20170306011332p:plain

「ProgrammerTest.cs」を追加します。

f:id:daigo-knowlbo:20170306011511p:plain

実装は以下の通り。

// HogeHogeLibrary.Test\ProgrammerTest.cs
using Xunit;

using HogeHogeLibrary;

namespace HogeHogeLibrary.Test
{
  public class ProgrammerTest
  {
    [Fact]
    public void HardWorkTest()
    {
      var programmer = new Programmer();
      programmer.Work(3);
      programmer.HaveLunch();
      programmer.Work(10);

      Assert.Equal<int>(0, programmer.Stamina);
    }

    [Fact]
    public void NormalWorkTest()
    {
      var programmer = new Programmer();
      programmer.Work(3);
      programmer.HaveLunch();
      programmer.Work(5);

      Assert.Equal<int>(50, programmer.Stamina);
    }
  }
}
  • HardWorkTest()は、プログラマをスタミナ0までめいっぱい働かせます!
  • NormalWorkTest()は、プログラマを健全に働かせます!

一度ビルドしておきましょう。

テストエクスプローラの表示

メニュー「テスト → ウィンドウ → テストエクスプローラ」を選択します。
「すべて実行」をクリックするとテストが実行され、無事、成功の「緑」を得られます。

f:id:daigo-knowlbo:20170306012436p:plain

Live Unit Testingを開始

メニュー「テスト → Live Unit Testing → 開始」を選択します。
すると、コードエディタ上に緑のチェックマークがつきます。

f:id:daigo-knowlbo:20170306012614p:plain

Liveする!

では、試しにProgrammerクラスのコードをいじってみます。
プログラマ達は、ジムに通いデフォルトスタミナを120に引き上げました。

// HogeHogeLibrary\Programmer.cs
public int Stamina { get; set; } = 120;

すると、ワンテンポの後、以下のように Programmer.cs / ProgrammerTest.cs / テストエクスプローラ の表示が赤くなります。

f:id:daigo-knowlbo:20170306013010p:plain

f:id:daigo-knowlbo:20170306013029p:plain

仕様が変わったProgrammerに合わせてテストコードを書き直してあげると、緑に戻ります。

f:id:daigo-knowlbo:20170306013308p:plain

どのタイミングで動くの?

なんか「カッコいー、未来的ー」って思いますが、どのタイミングで Live Unit Testing は動いているのでしょうか?
見ていると「連続したキー入力が終わって1秒くらい後」には、ビルドが走ってテストが実行されているみたいです。

まとめ(思ったこと)

ということで「Live Unit Testing」を有効にしていると、かなり頻繁に「ビルド→テスト」が繰り返し実行されます。
個人的には「ソースファイルを保存したタイミング」辺りでいいんじゃないかと思ってしまいましたが、製品版でも同様になるのでしょうか(どこかに設定とか有るのでしょうか・・・)。7日に改めて確認してみます。

そしてこのビルドは内部的なもののようで、プロジェクトのbinフォルダのアセンブリは更新されません。

また、Service / Domain / Repositoryなどのように、分離したプロジェクト構成の場合でもきちんと動いてくれます。つまり、Serviceに対するテストコードが、末端のRepositoryソースの変更に対しても反応して Live Unit Testing が行われます。

本投稿のようなシンプルなクラスのテストであれば問題ありませんが、データベースアクセス や 外部WebAPI呼び出し が行われるクラスなんかが絡んでいると、ガンガンアクセスが飛ぶことになると思います。
更新系の処理が予期せず多数走ったり、大き目のチーム開発だとルール化なんかも必要そうかなぁ・・・なんて思いました。

そして、思考が拡散して、そもそも ユニットテストの必要性・TDDの可否・DHH氏のTDD is dead・・・なんかの過去の記事を改めて読み返してしまいました。
個人的には、「細部に至るユニットテスト要らない」「ビジネス的粒度のユニットテスト+自動統合テスト」でおk派です。
(と言いながら、実プロジェクトでテストコードを書かないことが多いのはここだけの話・・・)

Syncfusion SfAutoComplete を使ってみる - Xamarin Forms

前回の投稿に引き続いて Syncfusion の Essential Studio for Xamarin を使ってみたいと思います。

↓↓↓前回↓↓↓

ryuichi111std.hatenablog.com

今回は SfAutoComplete を使ってみます。
SfAutoComplete は、テキストボックスへのユーザーの入力に対して、候補をオートコンプリート表示(&選択)するコントロールです。

f:id:daigo-knowlbo:20170220042709p:plain

ここでは、以下の2つの実装を紹介します。

  • 1.シンプルな実装
     → 最もシンプルに、とりあえずSfAutoCompleteを使ってみます。

  • 2.かな入力 → 漢字で候補表示 & Prism で使ってみる
     → 駅名を「ひらがな」で入力。オートコンプリートでは「漢字の駅名」が候補表示される。オートコンプリートされた「漢字駅名」を選択すると、オートコンプリート内にも「漢字の駅名」が設定される。さらに Prism を使用。というサンプルを実装します。

で、以下で紹介するソースはGithubにあげてあります。

github.com

1. シンプルな実装

まず初めに、シンプルに SfAutoComplete を使ってみます。
ソリューションを新規作成します。
シンプルに「Cross-Platform → Blank Xaml App(Xamarin.Forms.Portable)」とします。
プロジェクト名は「AutoCompleteExample1」としました。

f:id:daigo-knowlbo:20170220003408p:plain

Nugetで SfAutoComplete への参照を追加

※SyncfusionのNugetを追加する方法についてはこちらの「Nugetソースの追加」の項を参照してください。

AutoCompleteExample1(PCLプロジェクト)には以下の参照をNugetで追加します。

  • Syncfusion.Xamarin.SfAutoComplete

AutoCompleteExample1.Droid(androidプロジェクト)には以下の参照をNugetで追加します。

  • Syncfusion.Xamarin.SfAutoComplete
  • Syncfusion.Xamarin.SfAutoComplete.Android

AutoCompleteExample1.iOSiOSプロジェクト)には以下の参照をNugetで追加します。

  • Syncfusion.Xamarin.SfAutoComplete
  • Syncfusion.Xamarin.SfAutoComplete.IOS

iOSプロジェクトに初期化処理を追加

AutoCompleteExample1.iOS プロジェクト→AppDelegate.cs内のFinishedLaunching()メソッドに「SfAutoCompleteRenderer」をインスタンス化する処理を追記します。
この記述がないと、iOSでは実行時に、画面に配置した SfAutoComplete が何の描画も行われません。

// AppDelegate.cs

public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
  // 以下を追記
  new Syncfusion.SfAutoComplete.XForms.iOS.SfAutoCompleteRenderer();

  global::Xamarin.Forms.Forms.Init();
  LoadApplication(new App(new iOSInitializer()));

  return base.FinishedLaunching(app, options);
}

フォームに配置

フォーム(MainForm.xaml)に SfAutoComplete コントロールを配置します。

<!-- MainPage.xaml -->

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
  xmlns="http://xamarin.com/schemas/2014/forms"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:local="clr-namespace:AutoCompleteExample1"
  xmlns:sf="clr-namespace:Syncfusion.SfAutoComplete.XForms;assembly=Syncfusion.SfAutoComplete.XForms"
  x:Class="AutoCompleteExample1.MainPage">
  
  <StackLayout Margin="0,20,0,0">

    <sf:SfAutoComplete x:Name="AutoComplete1" />

  </StackLayout>

</ContentPage>

ポイントは以下の通り。

① 「xmlns:sf」の指定
ルート要素 に、SfAutoCompleteをXaml内で定義するためにXML名前空間を定義します。

xmlns:sf=“clr-namespace:Syncfusion.SfAutoComplete.XForms;assembly=Syncfusion.SfAutoComplete.XForms

上記により、Syncfusion.SfAutoComplete.XFormsアセンブリに実装されたSyncfusion.SfAutoComplete.XForms名前空間を、「sf」として利用可能になります。

② SfAutoCompleteの配置
<sf:SfAutoComplete>要素により SfAutoComplete コントロールをフォームに配置します。

候補文字列を追加

フォームに配置したSfAutoCompleteコントロールに候補文字列を追加します。
ここではコードビハインド上に以下のように実装を追加します。

// MainPage.xaml.cs
using System.Collections.Generic;
using Xamarin.Forms;

namespace AutoCompleteExample1
{
  public partial class MainPage : ContentPage
  {
    public MainPage()
    {
      InitializeComponent();

      // 候補文字列リストを作成
      List<string> Stations = new List<string>();
      Stations.Add("Tokyo");
      Stations.Add("Osaka");
      Stations.Add("Nagoya");
      Stations.Add("Nagatachou");
      Stations.Add("Ebisu");
      Stations.Add("Sinagawa");

      // SfAutoCompleteに候補文字列リストをソースとして設定
      this.AutoComplete1.AutoCompleteSource = Stations;
    }
  }
}

実行

実行すると以下のような画面が表示されます。

f:id:daigo-knowlbo:20170220014345p:plain

「Na」と入力。Nagoya / Nagatachou が候補として表示される。

f:id:daigo-knowlbo:20170220014602p:plain

「O」と入力。Osaka が候補として表示される。

f:id:daigo-knowlbo:20170220014615p:plain

ちょっと実装を追加

引き続き・・・フォームに「ボタン」と「ラベル」を追加します。
ボタンクリック時に SfAutoComplete に入力されている値をラベルに表示してみようと思います。

<!-- MainPage.xaml -->

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
  xmlns="http://xamarin.com/schemas/2014/forms"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:local="clr-namespace:AutoCompleteExample1"
  xmlns:sf="clr-namespace:Syncfusion.SfAutoComplete.XForms;assembly=Syncfusion.SfAutoComplete.XForms"
  x:Class="AutoCompleteExample1.MainPage">
  
  <StackLayout Margin="0,20,0,0">

    <sf:SfAutoComplete x:Name="AutoComplete1" />

    <Label x:Name="Label1" />
    
    <Button Text="入力項目チェック" Clicked="CheckClicked" />

  </StackLayout>

</ContentPage>
// MainPage.xaml.cs

using System.Collections.Generic;
using Xamarin.Forms;

namespace AutoCompleteExample1
{
  public partial class MainPage : ContentPage
  {
    public MainPage()
    {
      InitializeComponent();

      // 候補文字列リストを作成
      List<string> Stations = new List<string>();
      Stations.Add("Tokyo");
      Stations.Add("Osaka");
      Stations.Add("Nagoya");
      Stations.Add("Nagatachou");
      Stations.Add("Ebisu");
      Stations.Add("Sinagawa");

      // SfAutoCompleteに候補文字列リストをソースとして設定
      this.AutoComplete1.AutoCompleteSource = Stations;
    }

    // ボタンクリックイベントハンドラ
    private void CheckClicked(object sender, System.EventArgs e)
    {
      // ラベルにSfAutoCompleteに入力された値を設定
      this.Label1.Text = this.AutoComplete1.Text;
    }
  }
}

さあ、実行!

「To」と入力して「Tokyo」が候補に挙がった。

f:id:daigo-knowlbo:20170220015734p:plain

「Tokyo」を選択。

f:id:daigo-knowlbo:20170220015807p:plain

「入力項目チェック」ボタンクリック。

f:id:daigo-knowlbo:20170220015901p:plain

2.かな入力 → 漢字で候補表示 & Prism で使ってみる

次に、もう少し面白みのあるサンプルを実装してみます。
要件は以下の通りです。

  • 電車の駅名を入力するUIを想定する
  • ユーザーは、駅名を「ひらがな」で入力する
  • 「ひらがな」の入力に対して「漢字の駅名」を候補としてオートコンプリートする
  • オートコンプリート候補の中から「漢字の駅名」を選択したら、SfAutoComplete内の表示も「漢字の駅名」となる

実行画面は以下のようになります。

f:id:daigo-knowlbo:20170220033030p:plain

ひらがな で「え」と入力すると、漢字の駅名が候補で表示される。

f:id:daigo-knowlbo:20170220033049p:plain

候補から「恵比寿」を選択。

f:id:daigo-knowlbo:20170220033142p:plain

「検索」ボタンをクリックすると、SfAutoCmopleteの入力内容が、下のラベルに表示される。

f:id:daigo-knowlbo:20170220033223p:plain

では、以下に実装を・・・

ソリューションを新規作成

「Prism Xamarin Forms → Prism Unity App(Xamarin.Forms)」とします。
プロジェクト名は「AutpCompleteWithPrism1」としました。

f:id:daigo-knowlbo:20170220023747p:plain

Nugetからの SfAutoComplete 参照の追加、iOSプロジェクト AppDelegate.cs へのコード追加は先程と同様。

Event to Command の下準備

本サンプルでは、Prism(MVVM)を使用します。
そして、SfAutoCompleteの「SelectionChangedイベント」を、ViewModelで捕捉したいです。
さらに、SelectionChangedはCommandとして提供されていません。
Viewのコードビハインドにコードを書きたくないし、MVVM的には「すべきではない」から。
という事で、「Event To Command」な実装が必要になります。

nuits.jpさんが以下の投稿で、いい感じの実装を提供してくれているので、これを拝借させていただきました。

www.nuits.jp

以下の2ソースをPCLプロジェクトに追加しました。

Stationモデルクラスを作成

先程のシンプルな実装では List をソースとしましたが、今回は Stationモデルクラス をソースとします。
漢字名・ひらがな名を属性として持った駅クラスを用意するためです。

// Models/Station.cs

using System;

namespace AutpCompleteWithPrism1.Models
{
  // 駅モデルクラス
  public class Station
  {
    /// <summary>
    /// 漢字駅名を取得または設定します。
    /// </summary>
    public string Name { get; set; }

    /// <summary>
    /// ひらがな駅名を取得または設定します。
    /// </summary>
    public string Kana { get; set; }
  }
}

View(MainPage.xaml)を作成

ビュークラスは以下のように実装します。ポイントは後述。

<!-- Views/MainPage.xaml -->

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 
  xmlns="http://xamarin.com/schemas/2014/forms"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
  xmlns:common="clr-namespace:AutpCompleteWithPrism1.Common;assembly=AutpCompleteWithPrism1"
  xmlns:sf="clr-namespace:Syncfusion.SfAutoComplete.XForms;assembly=Syncfusion.SfAutoComplete.XForms"
  prism:ViewModelLocator.AutowireViewModel="True"
  x:Class="AutpCompleteWithPrism1.Views.MainPage"
  Title="MainPage">
  
  <StackLayout HorizontalOptions="Center" VerticalOptions="Center">

  <sf:SfAutoComplete 
    DataSource="{Binding Stations}" 
    DisplayMemberPath="Kana" 
    Text="{Binding InputText}">
    <sf:SfAutoComplete.ItemTemplate>
    <DataTemplate>
      <Label Text="{Binding Name}" />
    </DataTemplate>
    </sf:SfAutoComplete.ItemTemplate>
    <sf:SfAutoComplete.Behaviors>
    <common:EventToCommandBehavior 
      EventName="SelectionChanged" 
      Command="{Binding SelectionChangedCommand}"  />
    </sf:SfAutoComplete.Behaviors>
  </sf:SfAutoComplete>

  <Button Text="検索" Command="{Binding SearchCommand}" />
  <Label Text="{Binding Message}" />
  </StackLayout>
</ContentPage>

ポイントは以下の通り。

①SfAutoComlete / Button / Label の配置
SfAutoCompleteコントロールを配置します。
検索ボタン・ラベルを配置します。
検索ボタンクリック時には、ラベルに「SfAutoCompleteに入力された値」を表示します。

②SfAutoComplete.DataSourceの指定
カスタムクラスのコレクションを、オートコンプリート表示候補としてバインドする際には、DataSourceプロパティにデータソースを設定します。

③DisplayMemberPathの指定
オートコンプリート候補となるオブジェクト「Station」の、「どのプロパティ」を「表示項目(候補検索項目)」とするのかを指定します。

④ItemTemplateの指定
「ひらがなで入力、漢字名で候補を表示」の要件を満たすために、先のDisplayMemberPathには候補検索用としてKana(Station.Kane)を、漢字名表示用にカスタムなItemTemplate(Station.Nameを表示するように設定)を指定しています。 このItemTemplate の指定が無いと、候補の駅名が「ひらがな」で表示されてしまいます(DisplayMemberPathがKanaの為)。

⑤Event To Command Behavior の使用
以下のXML名前空間を定義。

xmlns:common=“clr-namespace:AutpCompleteWithPrism1.Common;assembly=AutpCompleteWithPrism1”

そして、SfAutoComplete の SelectionChangedイベント に Behavior を追加。

ViewModelを作成

ビューモデルクラスの実装は以下になります。

// ViewModels/MainPageViewModel.cs

using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;

using Xamarin.Forms;

using AutpCompleteWithPrism1.Models;

namespace AutpCompleteWithPrism1.ViewModels
{
  public class MainPageViewModel : BindableBase, INavigationAware
  {
    /// <summary>
    /// SfAutoCompleteにデータバインドする駅情報データソース
    /// </summary>
    public List<Station> Stations { get; set; } = new List<Station>();

    /// <summary>
    /// 検索ボタンコマンド
    /// </summary>
    public ICommand SearchCommand { get; }

    /// <summary>
    /// SfAutoComplete選択項目変更コマンド
    /// </summary>
    public ICommand SelectionChangedCommand { get; }

    /// <summary>
    /// SfAutoComplete入力テキスト
    /// </summary>
    private string inputText = "";
    public string InputText
    {
      get
      {
        return this.inputText;
      }
      set
      {
        this.SetProperty(ref this.inputText, value);
      }
    }

    /// <summary>
    /// メッセージテキスト
    /// </summary>
    private string message = "";
    public string Message
    {
      get
      {
        return this.message;
      }
      set
      {
        this.SetProperty(ref this.message, value);
      }
    }

    public MainPageViewModel()
    {
      // データソース作成
      this.Stations.Add(new Station() { Name = "東京", Kana = "とうきょう" });
      this.Stations.Add(new Station() { Name = "恵比寿", Kana = "えびす" });
      this.Stations.Add(new Station() { Name = "江戸橋", Kana = "えどばし" });
      this.Stations.Add(new Station() { Name = "品川", Kana = "しながわ" });
      this.Stations.Add(new Station() { Name = "新宿", Kana = "しんじゅく" });

      // SfAutoComplete.SelectionChangedイベントに対応したCommand
      SelectionChangedCommand = new Command((param) =>
      {
        this.InputText = this.Stations.First(s => s.Kana == ((Syncfusion.SfAutoComplete.XForms.SelectionChangedEventArgs)param).Value).Name;
      });

      // 検索ボタンクリック時のCommand
      SearchCommand = new Command(() => { this.Message = this.InputText; });
    }

    public void OnNavigatedFrom(NavigationParameters parameters)
    {

    }

    public void OnNavigatedTo(NavigationParameters parameters)
    {
    }
  }
}

ポイントは以下の通り。

①SfAutoComleteのデータソースを用意
List型プロパティ「Stations」をクラスプロパティとして用意し、コンストラクタ内で初期化しています。

②SelectionChangedCommand の実装
SfAutoCompleteの「候補」が選択されたときに発生するイベントをBehavior経由でSelectionChangedCommandとして取得します。
その際には、パラメータ「Syncfusion.SfAutoComplete.XForms.SelectionChangedEventArgs型 Param 」から、選択された値「ひらがな」名を取得し、該当するStationモデルクラスのName(漢字駅名)をInputTextプロパティに設定しています。
InputTextプロパティは、ViewにおいてShAutoComplete.Textにバインドされています。
つまり、以下のような動作が行われます。

SfAutoCompleteにユーザーが「ひらがな」入力 ↓↓↓
候補として「ひらがな」に該当する Kanaプロパティ を持つ Stationオブジェクト の漢字駅名が候補一覧される ↓↓↓
ユーザーが漢字駅名を選択
↓↓↓
SelectionChangedCommandが発生
↓↓↓
ViewModelのInputTextプロパティ値に、選択されたStationモデルクラスに該当する「漢字駅名」を設定
↓↓↓
SfAutoComplete.Textに、ViewModel.InputText値が反映される(つまり漢字駅名)

③SearchCommand の実装
検索ボタンコマンドの実装です。
InputTextの値(つまりSfAutoCompleteの入力値)を、Messageプロパティに設定します。
Messageプロパティはビューのラベルにバインドされています。

まとめ

ということで「Syncfusionを使ってみる シリーズ(?)」第2回でした。
SfAutoCompleteは、すごくシンプルで僅かな英語力であっても、以下のヘルプを読めばすぐに理解できました。

help.syncfusion.com

が、「ひらがな入力→漢字表示」みたいな、非英語圏(日本)のちょっとした要件を満たす部分には、若干の調査が必要でした・・・
ちょっとイレギュラーな利用方法をしているような気がするので、その使い方だと「こんな時におかしくなるよー」とかありましたらご指摘お願いいたします。
ということで、「Syncfusionを使ってみる」は引き続き、本ブログでシリーズ化していきたいな、と思っています!