『.NET Core 3最新情報セミナー』に参加しました

『.NET Core 3最新情報セミナー』に参加してきました。

connpass.com

セッション内容は以下な感じです。

  • .NET 最新ロードマップと今押さえておきたい技術要素
  • .NET Core 3.0時代のデスクトップアプリ開発戦略を考える
  • UIコンポーネントにおける .NET Core 3対応
  • .NET Core 3移行に向けてのテスト自動化戦略

それぞれのセッションで気になった点をまとめたいと思います。

.NET 最新ロードマップと今押さえておきたい技術要素

井上 章さん(@chack411)の発表。

www.slideshare.net

  • .net core 3.0 for winodws desktopのメリット
    • 単一exeファイルにまとめられる!
    • 未参照なライブラリの排除
  • app centerに.net core for windows desktopが対応
    • desktop appもテレメトリとかクラレポが取れる!
  • blazor app
  • aspnet core 3.0でgrpcとservice workerに対応!
  • c#だけでwebアプリのフロントからバックエンドまで実装可能に!
  • ml.net - 機械学習
    • 感情分析、価格予測、スパム判定とかで使える
    • ml.net model builder
  • .net 5で分散した.netを1つに統合する
  • .net 5に統合されないもの
    • webforms
      • blazorで代替
    • wcf
      • gRPC for WCF server and remotingで代替(移行ガイド公開予定)
    • wf
      • open source core workflow for windows workflowで代替

.NET Core 3.0時代のデスクトップアプリ開発戦略を考える

中村 充志さん(@nuits_jp)の発表。

github.com

  • .net 5が出るからと言って本当の意味ですべてが統合されるわけではない
    • デスクトップにはデスクトップの、モバイルにはモバイルの最適なruntimeが使われるべき
  • .net core 3.0からの新しいビルド設定
    • /p:PublishReadyToRun=true
    • --self-contained=false
      • シングルバイナリにはなるけど、ランタイムは同梱しない
  • .net core 3.0によるアーキテクチャ戦略
    • Side by SideやPublishSingleFileによるモジュール分割
      • モジュール毎にruntimeを変更出来る
      • 実行ファイルが分かれるので影響範囲が抑えられる
  • MSIX
  • .NET Framework -> .NET Coreへの移行の注意点
    • Frameworkにあったライブラリがnugetに移行されている -> 手動で取り直さないといけない
    • Core用に移行されたライブラリが大きく変更されている可能性がある
      • 例:EF Core -> 発行クエリの仕様が大きく変わっているのでパフォーマンスに影響するかも?

UIコンポーネントにおける .NET Core 3対応

池原 大然さん(@neri78)の発表。

www.slideshare.net

  • Infragisticsのコントロールクロスプラットフォーム
    • Core APIにより、デスクトップ、Web、モバイルどのプラットフォームでも同じ設計思想のコントロールが使える
    • どのプラットフォームでも機能は同じ!違うのはコードだけ!
  • .net core 3.0対応済み!
    • 現在はwpfのみ
    • nugetから利用可能(ただしライセンスは買ってネ)
  • これからの予定
    • 2019-Q4でWin, ASP.NET MVCへ対応
    • ASP.NET MVCは一部機能で対応
      • Excel, Word, PDF出力等ネイティブの機能に依存するものは検討中
  • セミナーに参加してくれた人はライセンス値引きするよ!

.NET Core 3移行に向けてのテスト自動化戦略

石川 達也さん(@StoneGuitar777)の発表。

  • 仕様化テスト
    • 前回の出力結果を正として、再テスト時に突き合わせするテスト方法
    • 自動化しやすい
  • 移行案件とかでテストの範囲が大きいときとても有効
  • システムテストのレベルで自動化するとカバレッジを稼ぎやすい
  • どれくらい作ればいいの?
    • カバレッジで判断(システムテストでは難しいかも?)
    • UIのレイアウト崩れは1回は目で確認 -> 以後は仕様化テスト
    • 仕様にプライオリティを付けてテストの粒度をコントロールする
  • Friendly!
    • .net coreに対応したテストライブラリ
    • プロセスを取得してオブジェクトを直接参照することでテストする
    • .net framework用に作ったFriendlyのテストコードをそのまま.net core移行アプリで利用可能
  • Friendlyはどうやって別プロセスのオブジェクトを参照しているか
    • HostAPIでdotnetのdllを読み込む
    • 要求を受け付けるdotnetサーバ(runtime?)にapi実行を要求
  • .netのdllは実行runtimeに応じて使用するシステムライブラリを切り替えてる?!
    • .net framework用に作ったdllを.net coreから実行するとSystem名前空間とかも.net coreのものを使ってくれる
    • 逆も然り
  • selenium
    • webテスト自動化ツール
    • Friendlyではseleniumを活用してwebのテストを実施してる
  • domにidを振っておくことでwebでもでテスト可能
  • test assistant pro
    • テストコード実装補助ツール
      • seleniumとFriendlyの使い方は知ってる必要がある
    • 実行中のユーザのUI操作からテストコードを自動生成してくれる

所感

.net core 3.0の登場によりc#でのアプリケーション開発の幅がとても広がったように感じました。 .net core 3.0による新しいビルド設定は地味ではありますが、とても嬉しい機能ですね。 特にblazorの正式リリースにより、フロントエンドからバックエンドまでフルスタックにc#での開発が可能になった点はとても興味深いです。 また、Friendlyによるwpfのテスト自動化はテストで可能な限り楽をしたい私にとってはとてもおいしい情報でした。

来年には.net 5がリリースされます。.netの進化に置いて行かれないように頑張りたいと思いました。

『C#エンジニアのための dockerコンテナ・kubernetesハンズオン (再)』に参加しました

C#エンジニアのための dockerコンテナ・kubernetesハンズオン (再)』に参加しました。

csharp-tokyo.connpass.com

やったこと

  • ハンズオン
    • dockerによるaspnet coreアプリのホスト
    • kubernetesによるdockerコンテナの起動
  • LTの聴講

ハンズオン

ハンズオンはKatacodaというコンテナ技術の学習で推奨されているというオンライン上の開発環境で行いました。(Katacode知らなかった...)

www.katacoda.com

前半パートでdockerとkubernetesについて座学をおこない、 後半パートで実際にハンズオンをおこなうスタイルでした。

(スライドは前回実施分のものですが、実際には少し説明スライドが追加されていました。)

(最新のスライドが公開されましたので差し替えます。)

www.slideshare.net

私は最後の手動スケーリング以外は順調に進められ、概ねコンテナの舵を切る体験を得ることが出来ました。

セミナー参加の目的はノータッチだったkubernetesの知見を得ることでしたが、どんなものか、どう操作するかの基本は理解出来たと思います。

筆者dockerもまだまだ知見が浅いので、行き着く先はAKSとかでkubernetes扱えるようになるまで、コンテナ技術のスキルが身につけられるように頑張りたいと思います。

LTの聴講

ふるかわ(@futa_ttjh)さんのコンソールアプリのススメな話

speakerdeck.com

XamarinとかASP.NETとかやってみたけど、やっぱりC#の勉強するならコンソールアプリからだよねっていう体験談を話して頂きました。 テストを書きたかったそうですが、フレームワークが絡むと難しい...、 コンソールアプリならシンプルでC#自身の学習とテストコードの記述がスッキリしました!とのことです。素晴らしい✨

Tomohiro Suzuki(@hiro128_777)さんのSORACOM Buttonのお話

(資料が公開されたらここに貼ります)

SORACOM ButtonというIoTデバイスに関するお話。

soracom.jp

...とここでハプニング!

肝心なSORACOM Buttonを忘れてしまったそうです...。 正直、これにより笑いがおき、内容が記憶から吹き飛んでしまいましたので資料が公開されることを期待しましょう!

確か、Azure Functionsと組み合わせてIoTなことをしてみたみたいな話だったと思います。(多分)

xamlだけでウィンドウを閉じる処理を実装

以下のパッケージをインストールします。

www.nuget.org

ウィンドウを閉じる処理を実装したいWindowに対して以下のxamlコードのボタンを実装しましょう。

<Button Content="Close">
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Click">
            <i:CallMethodAction MethodName="Close"
                                TargetObject="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Window}}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
</Button>

仕組み的には、CallMethodActionのTargetObjectでxamlの文章構造?的に親階層になるものの中からWindows型のものを探しているのだと思います。 そのTargetObjectのMethodNameで指定したメソッド、つまりCloseメソッドを実行するという感じです。

System.Windows.Interactivityを利用すれば、いちいちViewModelに実装しなくて良い面倒な画面固有イベントも簡単に実装出来そうですね。

C#のTaskと非同期処理についてまとめる

最近非同期処理関係で詰まるところがあったので、頭の整理も兼ねてまとめたいと思います。

Taskとは

C#のTaskとはTaskです。 これ自体は非同期処理でもなんでもない、ただの処理の手順書に過ぎません。

// 返ってきてるのはHTTP GETするという手順のみ
Task<HttpResponseMessage> task = httpClient.GetAsync(url);

このtaskを実行することで結果を得られます。

Task<HttpResponseMessage> task = httpClient.GetAsync(url);
// awaitによって実行されるため、返ってくるのは手順を実施した結果
HttpResponseMessage response = await task;

上記の例では変数taskHttpResponseMessageを返すことを約束するただの関数オブジェクトのようなものです。 中身の処理が何かどうかは呼び出し元には関係なく、 変数urlを渡して実行したらHttpResponseMessageが取得できる手順書であるというのがTaskの本質です。

Taskと非同期処理

Taskは単なる手順書です。 手順通りに実行されていればTaskがどのスレッドで実行されていようが問題がありません。 そのため、Taskを実行すると自然とマルチスレッド処理として実行されます。

これがTaskと非同期処理の関係です。

async await

Taskは同期的に実行することも出来ます *1 が、通常は非同期で実行します。

Taskを非同期で実行するためにはawaitキーワードを使用します。 awaitキーワードの使用されたメソッドにはasyncキーワードを使用します。 asyncキーワードを使用したメソッドはTaskTask<T>voidのいずれかで返します。

そのため、以下のような記述が成り立ちます。

private async Task<HttpResponseMessage> CallGetAsync(string url)
{
    var httpClient = new HttpClient();
    var response = await httpClient.GetAsync(url);
    return response;
}

private async Task GetMarkdownAsync()
{
    var url = "https://neko3cs.hatenablog.com/";

    var response = await CallGetAsync(url);
    var text = await response.Content.ReadAsStringAsync();

    File.WriteAllText(_path, text);
}

上記では、CallGetAsyncメソッド内のhttpClient.GetAsyncはTaskを返すメソッドのため、awaitキーワードを使用して実行しています。 CallGetAsyncメソッド内でawaitキーワードが使用されたため、メソッドにはasyncキーワードを使用します。 返したい値はHttpResponseMessage型のresponseなので戻り値はTask<HttpResponseMessage>になります。

GetMarkdownAsyncメソッドも非同期メソッドであるCallGetAsyncを呼んでいるため、awaitasyncを使用します。 GetMarkdownAsyncメソッドは戻り値が無いため、Task型を変えします。

このように、非同期メソッドを呼ぶメソッドは数珠つなぎのように非同期メソッドになってゆきます。 これは仕様です。仕方ないので数珠つなぎで非同期メソッドにしましょう。

async voidについて

前項でasyncキーワードを使用したメソッドはTaskTask<T>voidのいずれかで返しますと言いました。 非同期処理でvoidを返すということはどういうことなのでしょうか?

TaskクラスにはTask.StatusTask.ExceptionのようなTaskの進行状況を把握するようなプロパティが存在します。 voidで返すということはこれらの情報を取得することが出来ないということになります。

つまり、実行したタスクが終わったのかも、 エラーが起こっても起こったのかすらもわからない、危険な状態になります。

そのため、async voidは特定の条件下以外では使用は推奨されません

その条件が呼び出し元メソッドのシグネチャを変更出来ない場合です。 その代表例がWinFormsやWPFでのUIイベントハンドラになります。

これらのメソッドはvoidを返すことが決められていますので勝手にTaskを返せばビルドエラーとなります。

そのため、必然的にasync voidを使用せざる負えないことになります。

Taskの待ち方について

Taskはawaitキーワードを使って待ちましょう。

他にもTask.Wait()Task.ResultTask.GetAwaiter().GetResult()等の方法でTaskを待つことが出来ますが、 これらはTaskが完了するまで呼び出し元スレッドの処理を停止させます。 *2

コンソールアプリの場合は慣例のConsole.ReadLine()でアプリが終了しないようにする代わりになるため問題ではありません。 ですが、WinFormsやWPFのようなUIのあるアプリの場合はどうでしょうか?

呼び出し元スレッド = UI実行スレッドが停止してアプリが固まります。 だめですね。

そのため、UIのあるアプリではTask.Wait()Task.Resultしてはいけません

非同期処理と排他制御

lockキーワードによる排他制御下でawaitキーワードを使用することは出来ません。

これはlockによる排他制御では排他ロックしたスレッドでロックを解放しないといけない制約があり、 awaitしてしまうとスレッドが切り替わってしまう可能性があるため文法レベルで使えなくなっています。

この代替策としてAsyncLockというものがあるのでこれを使用しましょう。 AsyncLockでは内部的にSemaphoreSlimクラスが使われています。

SemaphoreSlimによる排他制御はスレッドに依存しないため、 非同期処理によってスレッドが切り替わってしまっても排他ロックの解放が出来ます。

AsyncLockはSemaphoreSlimをより使いやすくしたクラスだと思って問題ないです。

参考

*1:Task.RunSynchronously Method

*2:Task.ResultもTask.GetAwaiter().GetResult()も内部的にはTask.Wait()を呼んでいます

ASP.NET CoreアプリをApacheでホストする

ASP.NET Coreの登場でLinuxでもC#で作成したWebアプリが動かせるようになったみたいです。 .NETは使いたいけど、Linuxの文化も取り入れたい!ということで簡単にApacheでホストしてみたいと思います。

本手順ではサーバ構築にDockerを使用します。事前にインストールしておきましょう。

サーバを用意する

Dockerを使用してCentOSのサーバを建てます。 Dockerfile書いてドヤりたかったんですが、筆者弱小エンジニアなので手作業()で構築します。

取り敢えず、最新のCentOSイメージを拾って起動し、中に入ります。

$ docker pull centos
$ docker run -p 80:80 -p 443:443 -d --privileged centos:latest /sbin/init
$ docker exec -it {CentOSのコンテナID} /bin/bash

CentOSコンテナの中に入れたら必要なパッケージをインストールします。

# yum update
# yum install -y httpd firewalld
# rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm
# yum install -y dotnet-sdk-2.2

httpdApache HTTP Serverでfirewalldはただのファイアウォールです。 .NET Core SDKは通常のパッケージマネージャーから取得出来ないのでパッケージを取得してからインストールします。

インストール出来たらサービスを起動してあげましょう。

# systemctl enable httpd
# systemctl start httpd
# systemctl status httpd
# systemctl enable firewalld
# systemctl start firewalld
# systemctl status firewalld

ちゃんと起動していれば、緑色でactive (running)って表示されるかと思います。

f:id:neko3cs:20190817024513p:plain

f:id:neko3cs:20190817024532p:plain

最後にファイアウォールに穴を開けてHTTP(S)通信出来るようにしてあげます。

# firewall-cmd --add-port=80/tcp --permanent
# firewall-cmd --reload
# firewall-cmd --list-all

f:id:neko3cs:20190817025035p:plain

赤枠のように80番が開通すればオッケーです。

※実はここでERROR: Exception DBusException: org.freedesktop.DBus.Error.AccessDeniedというエラーが発生しましたが、firewalldをreinstallしたり、CentOSコンテナを再起動したら直りました。原因はよくわかりません...。

アプリを用意する

デプロイ環境の構築手順の確認が目的なのでデフォルトで生成されるテンプレートをそのままホストします。

$ mkdir webapplication1; cd webapplication1
$ dotnet new sln; dotnet new mvc
$ dotnet publish -c Release -r linux-x64 -o ~/Desktop/publish

Linux環境で動かすため、dotnet publishする際に、-r linux-x64Linux用にコンパイルされるよう指定してあげましょう。

アプリをデプロイする

デスクトップに発行したバイナリをコンテナ内に配置して動くようにしましょう。

ASP.NET CoreではKestrelという独自のWebサーバがアプリケーションに内包されています。 これ単独でもWebアプリをホストすることは可能ですが、ApacheIISと比べると機能が不十分だと言われています。 そのため、通常はApacheIISをリバースプロキシサーバとして仲介させるのが良いそうです。

f:id:neko3cs:20190817163604p:plain

まずはバイナリを圧縮してコンテナ内に持っていきます。

$ tar -zcvf publish.tar.gz ~/Desktop/publish
$ docker cp publish.tar.gz {CentOSのコンテナID}:/var/www/publish.tar.gz

送れたら、中に入って解凍します。

$ docker exec -it {CentOSのコンテナID} /bin/bash
# cd /var/www
# tar -zxvf publish.tar.gz

ASP.NET Coreアプリはサービスとして起動することでKestrelを使用してアプリをホストすることが出来るっぽいです。 なので解凍したバイナリを実行するサービスを作成・実行します。

以下のパスにファイルを作成します。

# vi /etc/systemd/system/kestrel-webapplicaiton1.service

ファイルを開いたら以下の内容を記述します。

[Unit]
Description=Example .NET Web API App running on CentOS 7

[Service]
WorkingDirectory=/var/www/publish
ExecStart=/usr/bin/dotnet /var/www/publish/WebApplication1.dll
Restart=always
# Restart service after 10 seconds if the dotnet service crashes:
RestartSec=10
KillSignal=SIGINT
SyslogIdentifier=dotnet-example
User=apache
Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]
WantedBy=multi-user.target

基本公式のコピペですが、以下のみ変更が必要です。

項目 説明
WorkingDirectory バイナリのあるファイルのパス
ExecStart 実行コマンド。dotnetコマンドでアプリの.dllを実行します

作成したらサービスを有効化し起動します。

# systemctl enable kestrel-webapplicaiton1.service
# systemctl start kestrel-webapplicaiton1.service
# systemctl status kestrel-webapplicaiton1.service

以下のように緑色でactive (running)って表示されればOKです。

f:id:neko3cs:20190817170435p:plain

最後にApacheの設定をします。以下のパスにファイルを作成します。

# vi /etc/httpd/conf.modules.d/webapplicaiton1.conf

ファイルを開いたら以下の内容を記述します。

<VirtualHost *:80>
    ProxyPreserveHost On
    ProxyPass / http://localhost:5000/
    ProxyPassReverse / http://localhost:5000/
    ServerName www.neko3cs.com
    ServerAlias *.neko3cs.com
</VirtualHost>

最低限必要な記述は上記のようです。 公式には他にも設定がありますが今回は Apacheの設定方法をよく知らないので 動作確認の取れたものを記載しています。

設定ファイルを追加できたらApacheに設定を再読み込みさせましょう。

# systemctl reload httpd

アクセスしてみる

最初にCentOSコンテナを起動した際にコンテナ内部の80番ポートとローカルの80番ポートを繋ぐように設定しました。 なのでそのままローカルのブラウザでlocalhostを叩いたら実行されると思います。

f:id:neko3cs:20190817172225p:plain

最後に

今回は最低限の実行方法の確認のみを行いました。 なので、一部本番環境で使用するには不適切な部分(SSL通信してないとか)があるかもしれません。

今後はIISではなくApacheASP.NET Coreアプリを実行してゆきたいと思うので、CentOSApacheについてもう少し学んでゆきたいと思います。

xUnitがnet451で作れない件

xUnitは.NET Framework 4.5.1のサポートを打ち切っており、 .NET Frameworkでの作成時は4.5.2以上にする必要があるそうです。

github.com

業務でハマったので備忘録に。

VSCodeの拡張「mssql」で取得結果をJsonで保存して楽々アサーション

SQL Server 2017など新しいバージョンのSQL Serverでは取得結果をJsonで保存する機能があります。

このような機能を使用することでNewtonsoft.JsonJsonConvertクラスにより、 RepositoryクラスのGetAllメソッド等でデータを取得するテストの記述が比較が楽になります。

[
  {
    "id": 1,
    "name": "neko3cs",
    "age": 25
  },
  {
    "id": 2,
    "name": "neko3333333",
    "age": 26
  },
  {
    "id": 3,
    "name": "neko234",
    "age": 27
  }
]
    public class PersonRepositoryFixture
    {
        [Fact]
        public void GetAllFact()
        {
            var json = File.ReadAllText(Path.Combine(Directory.GetCurrentDirectory(), "..", "..", "..", "expected.json"));
            var expectetList = JsonConvert.DeserializeObject<List<Person>>(json);

            var target = new PersonRepository();

            var persons = target.GetAll();

            foreach (var i in Enumerable.Range(0, expectetList.Count))
            {
                Assert.Equal(expectetList[i].Id, persons[i].Id);
                Assert.Equal(expectetList[i].Name, persons[i].Name);
                Assert.Equal(expectetList[i].Age, persons[i].Age);
            }
        }
    }

上記では、JsonファイルにDBからGetAllメソッドで取得されるべきデータをselect * from PersonSQLで取得し保存しています。 そして、JsonConvert.DeserializeObjectメソッドを使ってデシリアライズ、このデータとGetAllメソッドで取得した結果を比較します。

expectedな値をコードに直書きしていると変更があった際に修正が大変です。 しかし上記のコードにより、テストの際にDBの状態が変わってしまうことがあっても簡単にRepositoryのテストが出来ます。

しかし、現実ではSQL Server 2008などレガシーに引きずられてSQL ServerJsonで保存するような機能が備わっていない場合があります。 その際にVisual Studio Codeの「mssql」機能拡張を利用することで取得結果をJsonで保存することが出来ます。

marketplace.visualstudio.com

Visual Studio Codeに上記の機能拡張を追加しましょう。

DBに接続し、select文を発行すると以下のような画面が表示されます。

f:id:neko3cs:20190422224544p:plain

Result画面の右端に{ }のマークがあります。 これをクリックするとJsonで保存することが出来ます。

f:id:neko3cs:20190422224724p:plain

赤い枠で囲われたボタンをクリックするとすぐにJsonファイルとして保存が可能です。

このやり方であれば、SQLさえあれば取得系のテストのexpectedなデータは簡単に用意できるようになります。 レガシー環境で開発をしていても出来ることはあるので、このように効率化していきましょう!