やる気駆動型エンジニアの備忘録

WPF(XAML+C#)の話題を中心に.NET/Android/CI やたまに趣味に関するブログです

WPF on .NET Core 3.0アプリをAzure PipelinesでビルドしてAppCenterにリリースする

タイトルまんまです。

2ヶ月ほど前に WPF / WindowsForms に対応した App Center について色々遊んでいましたが、Distribute については .NET Framework だとちょっと面倒な部分があったので .NET Core 3.0 が GA されるまで放置してました。 9月に無事 .NET Core 3.0 が GA されたので色々試した結果、Azure Pipelines を使用して App Center Distribute へリリースするところまでできたので備忘録として残したいと思います。

前回までの記事

iyemon018.hatenablog.com

iyemon018.hatenablog.com

やりたいこと

  1. GitHub 上のコードにプッシュ
  2. Azure Pipelines が上記をトリガーにビルド&publish
  3. 成果物を App Center Distribute へリリース
  4. メールで通知してアプリを配布する

今回はこういった流れでコードのプッシュから配布までをやってみます。

これができると例えば"release ブランチにコードをプッシュしたらステークホルダー グループに登録したユーザーにメールで通知する"みたいなことが可能になります。

開発環境

やってみる

ということで早速やっていきます。 GitHub と Azure Pipelines との連携はググるレベルでもないので省略します。

App Center の設定

App Center の設定はプロダクトによって異なるので必要な情報のみ記載します。 Release Group の登録やバージョン番号の管理など予め設定されているものとします。

Azure Pipelines から App Center Distribute を使用するためには以下のページが参考になります。

docs.microsoft.com

Azure Pipelines との連携に API Token が必要になるので発行しておきましょう。

次に、App Center タスクに必要な情報を取得しておきます。 まずはApp slugです。こちらは上記 URL にも記載されていますが、リリース先の App Center ページの URL から取得します。 https://appcenter.ms/users/{username}/apps/{app_identifier}このような URL 形式になっていると思うので、{username}{app_identifier}を結合して{username}/{app_identifier}としたものがApp slugとなります。

そしてDestination IDsです。こちらはリリースグループを識別するための ID です。 取得方法は上記 URL に記載がありますので参照してください。

とりあえず App Center で必要なのはここまで。

Azure Pipelines の設定

Azure Pipelines と App Center との連携には[Project Settings] - [Service connections] から設定します。 App Center の API Token が必要になるのでご注意を。

今回設定したタスクは以下のとおりです。(タスク名を列挙しています)

  1. NuGet tool installer
  2. NuGet
  3. Use .NET Core
  4. .NET Core
  5. Archive files
  6. App Center distribute

NuGet tool installer, NuGet, Use .NET Core はいつも通りのやつなので省略します。 1つ注意点として、Use .NET Coreに指定するバージョン番号は .NET Core 3.0 の場合は、3.0.100などになります。 一度ローカル環境で dotnet コマンドを実行してバージョン番号を確かめておきましょう。(私はこれにちょいハマりました)

.NET Coreタスクはビルド用と publish 用に2つ使用します。 ビルドは特別なことはしていないので省略します。

publish はタスクのArgumentsに以下を設定しました。

-c Release -r win10-x64 /p:PublishSingleFile=true --output $(Build.ArtifactStagingDirectory)

f:id:iyemon018:20191104143607p:plain

シングルファイルでパッケージして、出力先を$(Build.ArtifactStagingDirectory)としました。 なぜこうしているかはハマリポイントを参照してください。

次にArchive filesです。 こちらも普通に publish で生成したファイルを zip にするだけです。 WPF アプリを App Center Distribute に追加するには .zip もしくは .msi でなければならないため、このタスクを追加しています。

ここはもうそのまままですね。

f:id:iyemon018:20191104143900p:plain

最後はApp Center distributeです。 このタスクの各種設定地については以下を参照して下さい。

docs.microsoft.com

  • App Center service connection
    接続先の App Center を選択します。選択式なので迷うことはないでしょう。
  • App slug
    アプリケーション名とユーザー名を組み合わせたものです。App Center の設定を参照してください。
  • Binary file path
    発行対象のファイルパスを指定します。ここには先程Archive filesで指定した出力先を設定します。
  • Build version
    アプリのバージョン番号を指定します。ここは過去のバージョンよりも大きい数値を設定する必要があります。
  • Create release notes
    リリースノートをどうするか選択します。Enter Release Notesを選択するとリリースノートを入力できるようになります。Select Release Notes Fileを選択すると、リポジトリ内にあるファイルをリリースノートとして使用します。こちらはプロダクトに合わせて設定してください。
  • Destination IDs
    配布先の ID です。ステークホルダーやテスターなど、アプリを配布したいグループの ID を設定します。ID の取得方法はApp Center の設定を参照してください。

今回は以下のようなりました。

f:id:iyemon018:20191104150204p:plain

これを実行すれば、App Center へ配布して登録されているユーザーへメールで通知されます。

今回のYAML

今回作成したタスクをYAMLで出力するとこうなります。

pool:
  name: Azure Pipelines
steps:
- task: NuGetToolInstaller@1
  displayName: 'Use NuGet 4.4.1'
  inputs:
    versionSpec: 4.4.1

- task: NuGetCommand@2
  displayName: 'NuGet restore'
  inputs:
    restoreSolution: Samples/AppCenter.Wpf/AppCenter.Wpf.sln

- task: UseDotNet@2
  displayName: 'Use .Net Core sdk 3.0.100'
  inputs:
    version: 3.0.100

- task: DotNetCoreCLI@2
  displayName: 'dotnet build'
  inputs:
    projects: 'Samples/AppCenter.Wpf/AppCenter.Wpf/**/*.csproj'

- task: DotNetCoreCLI@2
  displayName: 'dotnet publish'
  inputs:
    command: publish
    publishWebProjects: false
    projects: 'Samples/AppCenter.Wpf/AppCenter.Wpf/**/*.csproj'
    arguments: '-c Release -r win10-x64 /p:PublishSingleFile=true --output $(Build.ArtifactStagingDirectory)'
    zipAfterPublish: false
    modifyOutputPath: false

- task: ArchiveFiles@2
  displayName: 'Archive $(Build.ArtifactStagingDirectory)'
  inputs:
    rootFolderOrFile: '$(Build.ArtifactStagingDirectory)'

- task: AppCenterDistribute@3
  displayName: 'Deploy $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip to Visual Studio App Center'
  inputs:
    serverEndpoint: '<連携した App Center を選択する>'
    appSlug: '<slug を設定する>'
    appFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip'
    buildVersion: 1.2.0
    symbolsOption: UWP
    releaseNotesOption: file
    releaseNotesFile: Samples/AppCenter.Wpf/AppCenter.Wpf/ReleaseNote.txt
    distributionGroupId: '<Group ID を設定する>'
    isSilent: false

ハマリポイント

publishする際に出力先を指定しないとFile not foundになる

そのまんまですが、publish で--outputを指定していないと以下のようなエラーが出力されます。 なぜこのような現象が発生するのかは不明です。 --outputを指定すれば回避できるので素直に指定しましょう。

##[error]Error: form-data: EISDIR: illegal operation on a directory, read

ビルド番号が指定されていないとApp Centerからレスポンスコード422が返ってくる

こちらもそのまま。 App Center distributeタスクのBuild versionが未指定だと以下のように422が返ってきます。 バージョン番号を設定することで回避できるのでこちらも素直に設定しましょう。

##[error]"{\"status\":\"error\",\"message\":\"Version could not be created from build.\"}"

わかっていない・試してないところ

.msi での配布

Azure Pipelines の Hosted を使用して .msi ファイルを作るには Wix が必要になります。 この .msi ファイルであれば .NET Framework でも配布は可能です。 私は Wix は使ったことがないため、 .msi ファイルの配布は試したことがありません。

Build versionを動的に設定する

今回の方法ではApp Center distributeタスクのBuild versionは固定値になっています。 できればここは動的に設定できるようにしたいところ。 策としては以下の2つでしょうか。

  1. ブランチにバージョン番号のタグを設定し、PowerShell を使ってブランチのタグからバージョン番号を取得する
  2. .msi を作成して製品バージョンを手動で設定してプッシュする

.msi の方は説明を読む限り App Center 側で製品バージョンを読み込んでくれるようです。(未検証) .NET Core の場合はもうちょいうまいやり方ってないんでしょうかね? この辺は知識不足なのでもっといい方法があるかもしれません。

最後に

今回でプッシュ→リリースのパイプラインが完成しました。 プロダクトに利用すれば面倒な作業はかなり自動化されそうです。

通知手段がメールしかないのでチャットツールとの連携手段もほしいところ。ここは App Center のアップデート待ちですね。

にしても App Center は日本ではあまり知名度がないのかほとんどブログなどで見かけることがありません。 Analytics や Crashes , Distribute といった仕組みがフルマネージドで提供されている上に組み込みもかんたんなので、使い所は大いにありそうなんですが…。

もし、この記事を見て興味を持った方はぜひチャレンジしてみてください!!

ErgoDox EZのキースイッチとキーキャップを変えてみた

趣味の話です!

私は会社用と自宅用にErgoDox EZを使っているのですが、自宅用のは今年に入ってから購入しました。 ErgoDox EZは、現在販売されているものはキースイッチが差し替えられるようになっているため、最初購入したキースイッチが気に入らない場合は変更することが可能です。

ちなみに私は会社用に購入したものは2年以上前のもののためキースイッチは差し替えることができません。

いろいろなキースイッチを試してみたいな~、という欲求には勝てず、ついついキースイッチとキーキャップを新しく購入してしまいました。

購入元はTALPさんです。何回かお世話になってますが、良いところですよ。

talpkeyboard.stores.jp

さて、購入したものを組み込むビフォー・アフターはこちら。

  • Before

f:id:iyemon018:20190907143938j:plain

  • After

f:id:iyemon018:20190907144001j:plain

プロファイルはDCSからDSAに変更しました。また、妻もPCを使用するので印字タイプのキーキャップにしています。 写真じゃわかりにくいですが、水色のキーが白のErgoDox EZと大変マッチしております。

また、キースイッチも少量購入しました。(右側の写真は取り忘れてますが)よく使い、かつ小指や薬指といった力の入りづらい位置に赤軸と青軸のキーを割り当てています。

ErgoDox EZは、キースイッチが変えられるものの、差し替えの際には結構注意が必要です。 キーボードの性質上のせいなのでしょうか、思ったよりもガッチリとキーが固定されており、結構力を入れないとキーが取り外せません。 また、力を入れすぎるとこんな事になりかねません…

取り扱いの際には最新のご注意を!

さて、これで私のErgoDox EZは茶・赤・青の3軸の組み合わせとなりました。 使った感じとしては思った通り、小指・薬指の操作は多少楽になりました。やっぱり青軸のクリック感はキー入力の醍醐味ですね。

予想していなかったのは、赤・青に比べて茶軸の入力が若干違和感があることでしょうか。 これは恐らく私が会社用に使用しているErgoDox EXのキーが赤軸だからでしょう。 1つのキーボードに複数のキースイッチが混ざっていると余計にその差異がわかります。

いっそのこと全部青軸に変えようかな?

(WPF)App Centerでクラッシュ レポートを送る

前回、App Center + WPF を使ってユーザー利用状況を収集する機能を試してみました。 今回はクラッシュ レポートで遊んでみます。

参考資料

docs.microsoft.com

今回はこの資料をベースに進めていきます。

意図的にクラッシュさせる

App Center の API をテスト的に使用したい場合は、以下のコードを呼び出してクラッシュさせてね、とのこと。

Microsoft.AppCenter.Crashes.Crashes.GenerateTestCrash();

多分、私がやっているような検証のためのコードだと思うんですが、あってるのかな?

前回クラッシュした情報を取得する

前回アプリがクラッシュしたかどうか、クラッシュした時の情報を取得する方法です。 用途としてはユーザーにより詳細な操作内容を入力してもらうためのクラッシュ レポートを送る、などでしょうか。

前回クラッシュしたかどうかを取得するには、以下のようなコードで実現できます。

bool didAppCrash = await Microsoft.AppCenter.Crashes.Crashes.HasCrashedInLastSessionAsync();

クラッシュ時の情報を取得する方法はこう。

Microsoft.AppCenter.Crashes.ErrorReport crashReport = await Microsoft.AppCenter.Crashes.Crashes.GetLastSessionCrashReportAsync();

Microsoft.AppCenter.Crashes.ErrorReport型では以下のような情報が取得できます。 この辺の情報は見つけられなかったので、クラス定義を翻訳しただけです。間違ってたらごめんなさい。

  • Id : クラッシュ レポートの UUID。クラッシュ情報を一位に識別するためのもの。
  • AppStartTime : 前回クラッシュしたときのアプリが起動した日時。
  • AppErrorTime : 前回クラッシュしたときの日時。
  • Device : クラッシュしたデバイスの情報。Microsoft.AppCenter.Device型。OS の情報とか取れるっぽい。
  • Exception : 前回クラッシュした際にスローされた例外インスタンス。App Center に通知される内容と同じもの。
  • AndroidDetails : Android 特有のエラー情報。多分、Javathrowableとの互換性のためのもの。
  • iOSDetails : iOS 特有のエラー情報。こちらも iOS との五感のためのものっぽい。

さて、試しに前回クラッシュした情報をエラーメッセージで表示するようなサンプルを作りました。 こんな感じです。

namespace AppCenter.Wpf
{
    using System;
    using System.Collections.Generic;
    using System.Windows;
    using Microsoft.AppCenter.Analytics;
    using Microsoft.AppCenter.Crashes;

    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
        {
            bool didAppCrash = await Crashes.HasCrashedInLastSessionAsync();
            if (didAppCrash)
            {
                ErrorReport crashReport = await Crashes.GetLastSessionCrashReportAsync();

                MessageBox.Show($"前回セッションでクラッシュしました。{Environment.NewLine}"
                                + $"- 発生日時 : {crashReport.AppErrorTime}{Environment.NewLine}"
                                + $"- エラー内容 : {crashReport.Exception.Message}"
                              , "Report"
                              , MessageBoxButton.OK
                              , MessageBoxImage.Information);
            }
        }
    }
}

例外を適当にスローすると App Center にクラッシュレポートが通知されます。初回、レポートが通知されるまで2,3分かかったのでちょっと待ったほうがいいかもしれません。 レポートされると以下のような結果が表示されます。

f:id:iyemon018:20190817190138p:plain

f:id:iyemon018:20190817190211p:plain

レポートを送信するかどうか判断する

何らかの条件によってクラッシュ レポートを送信したくない場合は次のようにコールバックを呼び出します。 例えば、アプリ固有の例外がスローされた場合、あるレベル以下の例外がスローされても通知しない、といった使い方でしょうか。 このコールバックはAppCenter.Start()よりも前に呼び出す必要があります。

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    // アプリ固有の CustomException がスローされた場合、ShouldReportProcessed = true (= 処理済み) であればレポートを出力しない。
    // ShouldReportProcessed = false (= 未処理) もしくは CustomException 以外の例外はレポートに通知する。
    Crashes.ShouldProcessErrorReport = (ErrorReport report)
                                            => !(report.Exception is CustomException customException)
                                                || !customException.ShouldReportProcessed;
    AppCenter.Start("xxxxxxxxxxxxxxxxxxxxxxxx", typeof(Analytics), typeof(Crashes));
}

クラッシュ レポートを送信するかどうかユーザーに確認する

クラッシュ レポートに個人情報などが含まれる場合、予めユーザーに確認しておくことでトラブルを防ぐことができます。 あるいは、プロダクトのポリシーによっては個人情報の収集はユーザーの意思がなくてはならない場合もあるでしょう。 Microsoft でもユーザーへの確認を推奨しています。

App Center でもこの仕組みを使うことができます。 Microsoft.AppCenter.Crashes.Crashes.ShouldAwaitUserConfirmationコールバックを呼び出すことで、レポート送信するかどうかを決定することができます。 このコールバックを使うことで、ユーザーが操作するまでクラッシュ レポートが送られなくなります。 ただし、このコールバックは UI を提供するものではないため、ダイアログは自前で呼び出す必要があります。

また、アプリとしてクラッシュ レポートを送信するかどうかは以下の API によって決定します。

Microsoft.AppCenter.Crashes.Crashes.NotifyUserConfirmation(Microsoft.AppCenter.Crashes.UserConfirmation.Send);

引数の Microsoft.AppCenter.Crashes.UserConfirmationによって、"送信する", "送信しない", "常に送信する" から選択することができます。

試しに書いてみたサンプルはこちら。

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    Crashes.ShouldAwaitUserConfirmation = () =>
                                            {
                                                MessageBoxResult result = MessageBox.Show("クラッシュレポートを送信します。よろしいでしょうか?"
                                                                                        , "Question"
                                                                                        , MessageBoxButton.YesNo
                                                                                        , MessageBoxImage.Question);

                                                if (result == MessageBoxResult.Yes)
                                                {
                                                    Crashes.NotifyUserConfirmation(UserConfirmation.Send);
                                                    return true;
                                                }
                                                else
                                                {
                                                    Crashes.NotifyUserConfirmation(UserConfirmation.DontSend);
                                                    return false;
                                                }
                                            };
    AppCenter.Start("xxxxxxxxxxxxxxxxxxxxxxxx", typeof(Analytics), typeof(Crashes));
}

クラシュ レポートのログ送信状態を取得する

Microsoft.AppCenter.Crashes.Crashes.ShouldAwaitUserConfirmationコールバックでクラッシュ レポートを送信する前と送信成功・失敗それぞれの場合ごとにコールバックが用意されています。 用途としてはレポートの送信ダイアログを表示したりとかでしょうか。 API は次のとおりです。

  • クラッシュ レポートを送信する前に呼ばれるコールバック
Microsoft.AppCenter.Crashes.Crashes.SendingErrorReport
  • クラッシュ レポートの送信成功後に呼ばれるコールバック
Microsoft.AppCenter.Crashes.Crashes.SentErrorReport
  • クラッシュ レポートの送信失敗後に呼ばれるコールバック
Microsoft.AppCenter.Crashes.Crashes.FailedToSendErrorReport

クラッシュ レポートに添付ファイルを追加する

レポートに画像ファイルやテキストファイル(添付ファイル)を追加する仕組みがあります。 例えばクラッシュした時点のキャプチャなどを送ったりする場合に使用するのでしょうか。 添付ファイルを送信するコールバックは次のとおりです。

Microsoft.AppCenter.Crashes.Crashes.GetErrorAttachments

注意点として、送信可能な添付ファイルのサイズは 7MB となっています。 この容量を超える場合、送信エラーとなります。 検証してませんが、多分Crashes.FailedToSendErrorReportが呼ばれるんじゃないでしょうか。

なお、呼びだされるコールバックの順序は次のようになります。

  1. Crashes.ShouldAwaitUserConfirmation
  2. Crashes.GetErrorAttachments
  3. Crashes.SendingErrorReport
  4. Crashes.SentErrorReport / Crashes.FailedToSendErrorReport

これらのコールバックを実行するサンプルを書いてみました。

protected override void OnStartup(StartupEventArgs e)
{
    base.OnStartup(e);

    Crashes.SendingErrorReport += (object sender, SendingErrorReportEventArgs sere) =>
                                    {
                                        // Your code, e.g. to present a custom UI.
                                        MessageBox.Show("クラッシュ レポートを送っています。"
                                                    , "Sending Crash Report"
                                                    , MessageBoxButton.OK
                                                    , MessageBoxImage.Information);
                                    };

    Crashes.SentErrorReport += (object sender, SentErrorReportEventArgs sere) =>
                                {
                                    // Your code, e.g. to hide a custom UI.
                                    MessageBox.Show("クラッシュ レポートの送信を完了しました。"
                                                    , "Send Crash Report Completed"
                                                    , MessageBoxButton.OK
                                                    , MessageBoxImage.Information);
                                };

    Crashes.FailedToSendErrorReport += (object sender, FailedToSendErrorReportEventArgs ftsere) =>
                                        {
                                            // Your code goes here.
                                            MessageBox.Show("クラッシュ レポートの送信に失敗しました。"
                                                            , "Send Crash Report Failed"
                                                            , MessageBoxButton.OK
                                                            , MessageBoxImage.Information);
                                        };

    Crashes.GetErrorAttachments = (ErrorReport report) =>
                                    {
                                        ErrorAttachmentLog textLog =
                                            ErrorAttachmentLog.AttachmentWithText("This is a text attachment.", "text.txt");

                                        byte[] imageBuffer;
                                        using (var bitmap = new Bitmap("test-image.png"))
                                            using (var ms = new MemoryStream())
                                        {
                                            bitmap.Save(ms, ImageFormat.Png);
                                            imageBuffer = ms.GetBuffer();
                                        }

                                        ErrorAttachmentLog binaryLog =
                                            ErrorAttachmentLog.AttachmentWithBinary(imageBuffer, "test-image.png", "image/jpeg");

                                        MessageBox.Show("Get Error Attachments");

                                        return new List<ErrorAttachmentLog> {textLog, binaryLog};
                                    };

    AppCenter.Start("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", typeof(Analytics), typeof(Crashes));
}

なお、送信された添付ファイルは Dignostics から該当のレポートを選択して ATTACHEMENTS タブから参照することができます。

f:id:iyemon018:20190818180121p:plain

クラッシュ レポートの送信有無を設定する

総合テストや受け入れテストなど、後工程のテストでクラッシュ レポートを送信したくないようなケースで以下の API を使用することで、レポートを送信するかどうかを決定することができます。

Microsoft.AppCenter.Crashes.Crashes.SetEnabledAsync

引数に true を設定することでレポートを送信し、false を設定することでレポート送信を無効化します。 この設定はデバイスのストレージ上(どこかは不明)に保存されるため、一度呼び出すと永続的に適応されるようです。

また、この送信有無を設定したかどうかは以下の API で判定することができます。

bool isEnabled = await Crashes.IsEnabledAsync();

エラー レポートを送信する

App Center ではクラッシュ レポートの他に、何らかの例外がスローされた場合にその内容を App Center に送信する機能があります。 そして、エラー レポートにはキーと値を持つプロパティを登録することができます。 エラー レポートはクラッシュ レポートと同じく Dignostics から参照することができます。

private void ExceptionButton_OnClick(object sender, RoutedEventArgs e)
{
    try
    {
        throw new ApplicationException($"これは手動で発生さました。- Exception ({DateTime.Now:yyyy-MM-dd HH:mm:ss})");
    }
    catch (ApplicationException exception)
    {
        var properties = new Dictionary<string, string>
                            {
                                { "Category", "Test" },
                            };
        Microsoft.AppCenter.Crashes.Crashes.TrackError(exception, properties);
    }
}

最後に

クラッシュ レポートに関してもいろいろな API が用意されていて検証した感じだとプレビュー版でも特に問題なく使えています。 機能的にも十分なのであとはどう使うかに重点をおいてプロダクトを開発していけば良さげです。

(WPF)App CenterにWPFが対応したので使ってみた

devblogs.microsoft.com

上記ページでApp Centerに WPF/WinForms が対応したとのことなので早速使ってみました。 私自身これまで App Center を使ったことがなかったため、このエントリでは導入から Analytics までをチュートリアル形式で記載しています。

開発環境

開発環境はこんな感じ。

なお、2019年8月8日時点で App Center の WPF/WinForms はプレビュー版のみの提供となっています。 そのため、.NET Coreには対応していません。 .NET Framework 4.5以上が対象となっています。 今後は.NET Core 3.0にも対応予定とのことなので期待したいところです。App Center のロードマップについては以下を参照してください。

github.com

また、今回私は以下のページを参考にしています。

docs.microsoft.com

App Centerの設定

App Center を開き、[Add new] - [Add new app] を選択して以下の様に OS は Windows, Platform は WPF を選択します。あとは[Add new app] を選択します。

f:id:iyemon018:20190808221428p:plain

[Settings] を開いて右上のメニューから [Copy app secret] を選択します。 ここでコピーした Secret は後ほど使うのでエディタにでもコピペしておいてください。

f:id:iyemon018:20190808221948p:plain

さて、これで App Center の準備は整いました。

アプリに組み込む

次はアプリです。 適当に.NET Framework 4.5以上の WPF アプリを作成してください。

まずは、NuGet から App Center のパッケージを取得します。

Install-Package Microsoft.AppCenter.Analytics -Version 2.2.1-preview
Install-Package Microsoft.AppCenter.Crashes -Version 2.2.1-preview

これを実行すると大量のアセンブリが追加されるのでしばらく待ちます。

準備が整ったらApp.xaml.csを以下のようにします。 まずは、アプリ セッションを開始して App Center と接続できることを確認します。 using は忘れずに。

namespace AppCenter.Wpf
{
    using System.Windows;
    using Microsoft.AppCenter;
    using Microsoft.AppCenter.Analytics;
    using Microsoft.AppCenter.Crashes;

    public partial class App : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            AppCenter.Start("<ここに Secret を貼り付ける>", typeof(Analytics), typeof(Crashes));
        }
    }
}

以上! 簡単ですね。

このままアプリを起動すると App Center の[Analytics] - [Overview] は次のようになります。

f:id:iyemon018:20190808222745p:plain

まずはこれでアプリが App Center に接続できたことを確認できたので、もうちょっと触ってみます。

チュートリアルはいくつかあるのですが、わかりやすいのが Event だったのでこちらを実装します。 ちなみに以下のページを参考にしています。

docs.microsoft.com

Event には以下のようなコードを追加して予めどのようなイベントが有るのかを定義します。

// 第一引数は name, 第二引数は parameter
Analytics.TrackEvent("Video clicked", new Dictionary<string, string> {
    { "Category", "Music" },
    { "FileName", "favorite.avi"}
});

MS Docs ページとコードのコメントを読む限りだとname, parameterには以下のような制限があるようです。 元の文章が英語なので間違ってたらごめんなさい…

  • Event に定義できる数は最大で 200 個。これはnameに最大 200 種類のイベント名称を定義できるということ。
  • nameは最大 256 byte まで。
  • 1種類の Event につき、プロパティ パラメータは 5 つまで。これはparameterに 5 種類のプロパティ名を設定できるということ。
  • parameterのプロパティ名は最大 64 byte まで。

実質 Event 名称 : 200 x パラメータ 5 で 1000 種類のイベントを計測することができそうです。

これをApp.xaml.csに追加します。

namespace AppCenter.Wpf
{
    using System.Collections.Generic;
    using System.Windows;
    using Microsoft.AppCenter;
    using Microsoft.AppCenter.Analytics;
    using Microsoft.AppCenter.Crashes;

    public partial class App : Application
    {
        #region Methods

        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);

            AppCenter.Start("<ここに Secret を貼り付ける>", typeof(Analytics), typeof(Crashes));

            Analytics.TrackEvent("Example", new Dictionary<string, string>
                                            {
                                                {"Category", "Music"}, {"FileName", "favorite.avi"}
                                            });
        }
    }
}

例えば上記のExampleイベントを App Center で取得するには以下のようにします。

Analytics.TrackEvent("Example");

あとはこれを画面からボタンクリックなどのイベントハンドラから呼び出してやればOKです。

namespace AppCenter.Wpf
{
    using System.Collections.Generic;
    using System.Windows;
    using Microsoft.AppCenter.Analytics;

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void TestButton_OnClick(object sender, RoutedEventArgs e)
        {
            Analytics.TrackEvent("Example");
        }

        private void MusicEventButton_OnClick(object sender, RoutedEventArgs e)
        {
            Analytics.TrackEvent("Example", new Dictionary<string, string> {{"Category", "Music"}});
        }
    }
}

Analytics.TrackEventメソッドの第二引数にプロパティ名を指定することでより詳細なイベントの振り分けができそうです。

さて、これでアプリを起動して何回かボタンを押すと、 App Center の[Analytics] - [Events] は以下のようになります。

f:id:iyemon018:20190808224726p:plain

Exampleというイベントが追加されました。 イベントをクリックするとさらに詳細な情報を取得できます。

f:id:iyemon018:20190808224917p:plain f:id:iyemon018:20190808224934p:plain

Event はうまく使えばユーザーの利用状況とかかなり具体的かつ正確に収集できそうです。

最後に

さて、他にもクラッシュログとかプッシュ通知、アプリの自動配布なんかもあるそうです。 クラッシュログは Android の Crashlytics みたいなイメージでしょうか? アプリの自動配布もこれまでは Click once しかなかったので、代替案になるかも検証していきたいと思っています。

ちなみに今回のサンプルは App Center 初利用の私でも 1時間程度で組み込むことができました。

まだまだ App Center は触り始めたばかりで勉強不足な部分が多々あるので、もう少し色々触ってみようと思います。

Surface Bookがモニターアーム+ノートPCスタンドで快適に使えるようになった

私は自宅の開発環境はSurface Bookを使っています。 SurfaceなのでもちろんSurface Dockも使っています。

自宅の環境で悩んだ末にモニターアーム+ノートPCスタンドの環境が自分にピッタリだったので紹介したいと思います。

これまでの問題点

私は開発するときはできるだけ作業スペースを確保したいのですが、ノートPCってキーボードが邪魔じゃないですか? Surface Bookのいいところは、キーボードとディスプレイが分離型であることとディスプレイを前後入れ替えても問題なく使用できるところにあると思っています。 なので、ディスプレイを通常とは逆向きに接続して作業スペースを確保するように使用していました。

f:id:iyemon018:20190714191928j:plain

ただ、これでもテーブルとの接地面積は結構広く、ちょくちょくSurface Bookの位置を調整することがありました。 これが面倒だったのでどうにかできないかなーといろいろ調べてみました。

モニターアーム+ノートPCスタンドを使ってみた

で、調べた結果、すでに使ってたモニターアームに取り付けられるノートPCスタンドを見つけました。

設置するとこんな感じになります。

f:id:iyemon018:20190714192001j:plain

ディスプレイを反転して使ってるのはモニタの高さと合わせるためです。 Surface Book以外のコンバーチブルでないノートPCだとそのまま乗せるしかないのですがこういうところが地味に便利ですね。

さて、モニターアームに取り付けるということはある程度の高さがります。 なので地震なんかで揺れたりすると落ちたりするのかと思いましたが、ガッチリ固定することができるため問題ありません。 手で位置を調整しても気にならないレベルです。

f:id:iyemon018:20190714192031j:plain

モニターアームに乗せることでテーブル上の作業面積が広がりました。

これで快適ですね!!

さいごに

これ、会社にも導入したいなーと思ってたんですが、会社のデスクはモニターアームが設置できない構造になっているため、あえなく断念しています。 利用しているテーブルの構造やノートPCによっては使用できないパターンはあるかもしれませんので、現在の環境で利用できるかどうかきちんと確認する必要があります。

特に、モニターアームはクランプ式とグロメット式があるため、どちらの設置方法で使うかによって利用不可能なシーンも有ることでしょう。 参考程度に私が使用しているモニターアームはこちらです。

みなさんも試行錯誤してベストな作業環境を作ってみてください。