Quantcast
Channel: @jsakamoto
Viewing all 146 articles
Browse latest View live

気が付いたら Visual Studio Code / Visual Studio 2017 IDE で TypeScript の import 文が自動挿入されてた件

$
0
0
import 文をコーディングするのがストレス!TypeScript による Web フロントエンド実装をコーディングしていての話。



個人的に、長らくの間、import 文を記述するのが億劫でならなかった。


いくらインテリセンス/コード補完があるとはいえ、下記 GIF アニメでわかるように、カレットを右に・左に・また右に、みたく忙しく動かさねばならないからだ。
d0079457_22234990.gif

左から右へ流れるようにタイプするだけの構文だったら、まだ救われたのかもしれないが...。



あと、例えば上記例のように、Angular4+ の HttpClinet サービスを必要とするコンポーネントやサービスをコーディングするときは毎回、この import 文を書かねば HttpClinet 型の参照すらままならず、正直、かなりイライラさせられていた。


あれ、何ですか、この神機能!ところがである。


ふと、ある日、Visual Studio 2017 IDE を使用して、またしても HttpClient を DI 機構で受け取るコンポーネントのコンストラ引数を (import 文書くより前に、ついうっかり) コーディングしていると、なんと、まだ import していない HttpClient クラスがインテリセンスの候補に挙がってきているではないですか (下図)。
d0079457_22241922.png

そして、何かの見間違いかと思いつつ、そのまま Tab キー押して候補を確定したところ、なんと、import 文が自動挿入されたのである。

d0079457_22240032.gif

あわてて (常用していない) Visual Studio Code を起動して同じプロジェクトを開いて動作を試してみたところ、Visual Studio Code でも、この、import 前の型の候補表示 & 自動 import 挿入が発動した。

d0079457_22240959.gif

これはきわめて便利!



これまでの import 文コーディングに関する不満が吹き飛んだ。


いつから使えてたのか/どうやって実現してるかは不明のまま放置この機能はいつのバージョンから使えるようになっていたのだろう?
とりあえず Visual Studio 2017 IDE については、v.15.7 に更新したあたりで気づいたのだが、もっと前からサポートされていたのだろうか?


リリースノートを確認すればはっきりしたことがわかるとは思うが、とにかく、2018年5月時点で最新版の Visual Studio Code 及び Visual Studio 2017 IDE を使っていれば、この機能の恩恵にあずかれるっぽい。


また、この import 前の型の候補表示 & 自動 import 挿入が、どのような仕掛けで機能しているかは、まだよくわかっていない。


packages.json を見てるのか、webpack.config.js を見ているのか、node_modules フォルダ以下をスキャンしているのか、はてさて。


しかし何はともあれ、数字としてタイピング量を減らすという以上に、コーディング時の心理的疲弊をばっちり回避できる、素晴らしい機能だと思った。


※ Atom や Sublime、Vim などなど、そのほかのリッチ系エディタについても、同様のサポートがあるのかどうかは知らない。ご存知の方は、ブログのコメントないしは Twitter 上などでお知らせいただけるとありがたい。



ASP.NET Core 2.1 の UseHttpsRedirection が Azure Web Apps 上では効かない?

$
0
0
HTTPS プロトコルでのアクセスを強制するASP.NET Core 2.1 から登場した、Microsoft.AspNetCore.HttpsPolicy NuGet パッケージにて提供されている HttpsRedirection ミドルウェアを使うと、ASP.NET Core 2.1 Web サイト/アプリに、HTTP プロトコルでのアクセスがあったら HTTPS プロトコルのアクセスにリダイレクトさせる機能を実装できる。


例えば Visual Studio IDE 2017 で開発している場合は、Visual Studio IDE のプロジェクト新規作成のダイアログにて、この HTTPS リダイレクト機能込みでプロジェクトを作成する選択肢が用意されている。
d0079457_18505533.png

こうして作成した ASP.NET Core 2.1 Web アプリの Startup.cs を覗いてみると、Configure メソッド内にて、「app.UseHttpsRedirection()」というように、HttpsRedirection ミドルウェアの組み込みを行っている行があるのがわかる。

d0079457_18505199.png

この状況で、ローカル開発環境で実行してみると、たしかに http://localhost:... のアクセスは https://localhost:... へのアクセスにリダイレクトされている。

d0079457_18504680.png

Azure Web Apps 上では、デプロイするだけでは NGそれではこの ASP.NET Core 2.1 Web アプリを Azure Web App に発行してみたらどうなるか。



残念ながら (?)、それだけでは HTTPS プロトコルへのリダイレクトは機能しない。
d0079457_18504183.png

ドキュメントを確認してみると、HttpsRedirect ミドルウェアによる HTTPS プロトコルへのリダイレクトは、ある一定の条件が必要らしい。



ここでは、「環境変数 ASPNETCORE_HTTPS_PORT に、HTTPS プロトコルで接続するときのポート番号が指定されている場合」を、Azure Web Apps に適用して、HTTPS プロトコルへのリダイレクトが発動するように試してみた。


具体的には、Azure ポータル上から、当該 Web App のアプリケーション構成の設定ブレードを開き、下図のように HTTPS_PORT = 443 (※ポート番号 443 は HTTPS プロトコルの標準のポート番号) に設定する。
d0079457_18503398.png

こうすると、Azure Web Apps 上に発行後でも、HTTPS プロトコルへのリダイレクトが機能するようになった。

d0079457_18530444.gif

以上!

 

空のフォルダをコンテンツに含む NuGet パッケージのソースコードを Git でバージョン管理する

$
0
0
何を言っているのかわからないかもしれないが、とにかく記す。



こんな感じの、空のフォルダをコンテンツに含む NuGet パッケージを作ったとする。
d0079457_22214342.png

この NuGet パッケージを作るための .nuspec ファイルはこんな感じで、
<?xml version="1.0" encoding="utf-8"?>
<!-- 例えばファイル名は Foo.nuspec とか。-->
<package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd">
<metadata>
<id>Foo</id>
<version>0.1.0</version>
<authors>(authors)</authors>
<description>(description)</description>
</metadata>
<files>
<file src="Content\" target="Content" />
</files>
</package>
上記、file 要素で指定している Content フォルダの中に、収録の目的である空のフォルダ "Controllers" を設けてある (下図)。
d0079457_22213924.png



これで、(nuget.exe が PATH 上にある前提で) "nuget.exe pack Foo.nuspec" を実行することで、空のフォルダをコンテンツに含む NuGet パッケージ "Foo.0.1.0.nupkg" ができあがる。


めでたしめでたし、なのだが...
Git では空のフォルダはバージョン管理下に登録されないこのような NuGet パッケージ作成プロジェクトを Git のバージョン管理下においたとする。


ところがである。


Git はその仕様上、空のフォルダはバージョン管理のリポジトリに登録できないのだ。


なので、このプロジェクトの Git リポジトリを clone すると、空のフォルダ (この例では "Content"、及びそのサブフォルダの "Controllers") が復元されない。


その結果、clone した先の作業フォルダで NuGet パッケージ生成を実行しても、下記のようなエラーになってしまう。
Attempting to build package from 'Foo.nuspec'.
Could not find a part of the path '...\Content'.
Git で空のフォルダをバージョン管理に含める方法自分が知る限り、Git で空のフォルダをバージョン管理に含める直接的な方法は存在しない 。


その代わり、".gitignore" や ".gitkeep" など、何かしらのファイルを配置し、そのファイルを Git バージョン管理に含める事をもって、clone 先でも確実に当該フォルダが作成されるようにする、という回避策が一般的かと思う。


ということで、この NuGet パッケージ作成プロジェクトでも、"Content\Controllers" フォルダに ".gitkeep" を配置し、これを Git バージョン管理に追加してみる (下図)。

d0079457_22213366.png



そうすると、当然のことながら、このリポジトリの clone 先でも "Content\Controllers" フォルダが再現され、nuget pack コマンドが成功するようになった。


しかし、である。


今度は、できあがった NuGet パッケージに、".gitkeep" ファイルが含まれてしまった (下図)。
d0079457_22212814.png



これでは、この NuGet パッケージをインストールすると、本当は空っぽのフォルダができあがるのが期待値であるところ、".gitkeep"
ファイルが「ゴミ」ファイルとして配置されてしまう。
exclude 指定してみるが...それではということで、.nuspec ファイルの指定にて、".gitkeep" を対象ファイルから除外してみた。
...
<files>
<file src="Content\" target="Content" exclude="**\.gitkeep" />
</files>
</package>
これでイケるだろうと思いきや、なんと、下記エラーとなってしまった。
Attempting to build package from 'Foo.nuspec'.
Error NU5017: Cannot create a package that has no dependencies nor content.
ぐぬぬ。
exclude 指定でもなく、".gitkeep" ファイルでもなくて...あーでもないこーでもないと、検索しまくっているうちに、下記の NuGet の Issue にたどり着いた。




なんと、"_._" というファイルは、NuGet パッケージ作成の際に無視されるらしい。


マジか。


半信半疑で下図のとおり配置し、nuget pack コマンドを実行したところ...
d0079457_22211806.png



ちゃんと、"_._" ファイル抜きで、空のフォルダを含むコンテンツが、.nupkg 内に再現できた!
d0079457_22214342.png

"_._" ファイルは、Git にとっては何の変哲もない "ただのファイル" に過ぎないので、普通に Git バージョン管理に登録できる。


これで万事解決と相成った。


[解決] スリープからの復帰後、Surface Pro (第5世代) のタイプカバーから文字が打てなくなる

$
0
0


最近、Surface Pro (第5世代) (US配列 Type Cover 込み) を入手、使い始めるようになった。



仕事上、デスクでの作業は外部モニタ + Apple Wireless Keyboard (Bluetooth 接続) をつないで、据え置きで業務を行なっている。


出張時に、Surface Pro 本体と Type Cover だけ持ち出して出かける格好だ。


ところが、入手そうそうトラブルに遭遇してしまった。


本ブログ記事のタイトルのとおり、スリープから復帰後、タイプカバーから文字が打てない状態となってしまうのだ。
再起動以外、復旧しない...いろいろ試したが、いちどこの現象が発生すると、再起動する以外、復旧しないようであった。


タイプカバーの脱着や、デバイスマネージャからの「Surface Type Cover Filter Device」の削除からの入れ直しも効果無し。


とはいえ、再起動すれば確実に復旧はする。
でも、ちょっと不思議な不具合...どうしたものかと、いろいろいじってはみたのだが、解決には至らない。


だが、そうやっていじっている内に、この不具合の詳細が見えてきた。


まず、Type Cover それ自体がデバイスとして機能しなくなるわけではなかった。


Type Cover 上のタッチパッドでマウスカーソルは動かしたり、クリックしたりすることは大丈夫なのだ。


さらに加えて、ディスプレイの輝度変更や音量変更などのファンクションキーも動作したのだ。


それ以外の、カーソルキーや文字キー、Caps ロックキーに限って、これらを押しても何の反応も示さない、という挙動なのである。


さらにこの現象発生後、Bluetooth 接続の Apple Wireless Keyboard でも、文字キーに反応しなくなってしまった。


Type Cover のみならず、である。
キーボード入力にかかわるソフトウェアの問題?ここまできて、これはもしかして、キーボード入力にかかわるソフトウェアが、何か互換性の問題を引き起こしているのでは、と思い至った。


ということで、まずはこの Surface Pro にインストールした、キーボード入力に関わりそうなソフトウェアを思い出しつつ挙げてみた。


- Mouse without Board
- Apple Wireless Keyboard Driver (WinA1314)
- ATOK Passport
- TeamViewer 13 Business


次に、これらを順にアンインストールしてみて、当不具合が解消されるか様子を見てみることにした。
犯人がわかった!その結果、上記リストの 2つ目、Apple Wireless Keyboard Driver をアンインストールしたところ、嬉しいことにこの不具合現象が解消された。


今にして思えば、文字キー入力を受け付けなくなる現象が、Type Cover のみならず、Apple Wireless Keyboard でも同時発生していた時点で、最有力候補として疑うべきだったかもしれない(まぁ、後知恵バイアスではあるが)。


ということで、ひとまず一件落着といったところであるか。


ただし、デスクトップ環境で、これまで長年使ってきた Apple Wireless Keyboard が併用できなくなると考えた方がよさそうなので(Apple Wireless Keyboard Driver を使っても、スリープ後キー入力出来ない不具合を解消できるかどうか、トラブルシュートしてる精神的余裕がない...)、デスクトップ環境でのキーボード環境をどうしたものか、今後の課題である。
 


ASP.NET Core のアプリケーション構成を上書きするいろいろ - 特に環境変数とコレクション

$
0
0
ASP.NET Core アプリのアプリケーション構成ASP.NET Core アプリは、標準のプロジェクトテンプレートで作成すると、プロジェクトのフォルダにある appSettings.json というファイルを読みこんで、アプリケーション構成として使用するようにできている。


この appSettings.json にアプリケーション固有の半固定パラメータなどの設定値を JSON 形式で記述しておき、アプリ中からその設定値を読み取って利用する、というものだ。
詳しくは下記を参照するとよいだろう。


例えば下記のような appSettings.json を用意したとして、

{
"SiteSettings": {
"Title": "Hello World",
"TimeZone": {
"Name": "Tokyo Standard Time",
"Offset": 9
},
"Tags": [
{ "Text": "Foo", "Priority": 0.7 },
{ "Text": "Bar", "Priority": 0.3 }
]
}
}
ASP.NET Core アプリの実装コード (今回は C#) で、下記のように、上記 appSettings.json の JSON 構造にマッピングされるクラスを用意して、
public class SiteSettings {
public string Title { get; set; }


public TimeZoneSettings TimeZone { get; set; }


public class TimeZoneSettings {
public string Name { get; set; }
public int Offset { get; set; }
}


public TagSettings[] Tags { get; set; }


public class TagSettings {
public string Text { get; set; }
public double Priority { get; set; }
}
}
ASP.NET Core アプリの Startup クラスにて、下記のように、appSettings.json ファイル中の SiteSettings ノード以下を DI コンテナへサービス登録することで、
public class Startup
{
public IConfiguration Configuration { get; }

public Startup(IConfiguration configuration)
{
Configuration = configuration;
}


public void ConfigureServices(IServiceCollection services)
{
services.Configure<SiteSettings>(Configuration.GetSection("SiteSettings"));
...
`IOptions<SiteSettings>` 型のインスタンスを DI 機構によって入手できるようになる。


この `IOptions<SiteSettings>` オブジェクトの各プロパティを参照すると、appSettings.json の記載内容が SiteSettings オブジェクトにマッピングされて読み取られ、参照可能となっている寸法だ。
// 変数 settings は、DI 機構から入手した IOptions<SiteSettings> オブジェクトへの参照
settings.Value.Title; // <- "Hello World"
settings.Value.TimeZone.Name; // <- "Tokyo Standard Time"
settings.Value.TimeZone.Offset; // <- 9
settings.Value.Tags[0].Text; // <- "Foo"
settings.Value.Tags[1].Text; // <- "Bar"
アプリケーション構成の "上書き" この appSettings.json に記載されている設定値だが、いくつかの方法で "上書き" が可能だ。


ひとつは appSettings.Develop.json に記述する方法。
開発環境での実行時は、appSettings.json の設定値に対して、appSettings.Develop.json に記述した設定値で上書きされたように、アプリ側では設定値を読み取るようにできているのだ。


この仕組みにより、appSettings.json には既定値を、appSettings.Develop.json には各開発者固有の環境依存の設定値など(例えばデータベースの接続先設定値とか)を記載しておいたりして、アプリの動作を調整できる。


例えば先の例だと、appSettings.Develop.json に以下のように記述しておくと、
{
"SiteSettings": {
"TimeZone": {
"Name": "Asia/Tokyo",
}
}
}
アプリ側の読み取り結果は下記のとおりとなる。
settings.Value.Title; // <- "Hello World"
settings.Value.TimeZone.Name; // <- "Asia/Tokyo" ... 上書きされた!
settings.Value.TimeZone.Offset; // <- 9
settings.Value.Tags[0].Text; // <- "Foo"
settings.Value.Tags[1].Text; // <- "Bar"
なお、appSettings.Develop.json の "Develop" のファイル名部分は、実は環境変数 ASPNETCORE_ENVIRONMENT の値である。
なので、環境変数 ASPNETCORE_ENVIRONMENT に "FooBar" を設定すれば、appSettings.FooBar.json が上書き用に読み込まれる。


アプリケーション構成のセクション名は英字の大小が区別されない話は逸れるが、面白い(?)ことに、appSettings.Develop.json で appSettings.json の設定値を上書きする際、セクション名 (ノード名、プロパティ名) の英字の大文字と小文字は一致しなくても構わない。
アプリケーション構成のセクション名は Case Insensitive として動作するのだ。


なので、appSettings.json に、英字の大小違いの同一セクションを設けると(例えば下記)、JSON としては間違いではないのだが、その ASP.NET Core アプリは起動時にクラッシュしてしまう。
{
"SiteSettings": {
"title": "hello world", // <- セクション名の先頭が小文字
"Title": "Hello World", // <- セクション名の先頭が大文字
IIS 経由で実行した場合は
「HTTP Error 502.5 - Process Failure - The application process failed to start」
として現れるし、「dotnet run」で実行すると、
「Unhandled Exception: System.FormatException: A duplicate key 'SiteSettings:Title' was found.」
というように、重複キー発生である例外メッセージがコンソール出力から読み取れる。
d0079457_23131528.png



環境変数で "上書き"アプリケーション構成を上書きするもうひとつの方法は、環境変数で "上書き" する方法である。


appSettings.json 中の各セクション (ノード、プロパティ) 名を、コロン (:) 区切りで連結した名前で環境変数を定義すると、その環境変数の値がアプリケーション構成として読み取れるのだ。
言い換えると、C# や JavaScript 上、ドット (.) で区切るところをコロン区切りに置き換えた名前、とも言える。


先の例でいくと、例えば環境変数 "SiteSettings:TimeZone:Offset" に "8" と設定してから実行すると、アプリでの読み取り結果は下記のとおりとなる。
settings.Value.Title; // <- "Hello World"
settings.Value.TimeZone.Name; // <- "Tokyo Standard Time"
settings.Value.TimeZone.Offset; // <- 8 ... 9 から 8 に上書きされた!
settings.Value.Tags[0].Text; // <- "Foo"
settings.Value.Tags[1].Text; // <- "Bar"
Windows OS 上で Visual Studio を用いて開発・実行している場合は、Visual Studio の画面上、プロジェクトのプロパティ画面から環境変数を設定することもできる。
d0079457_23144057.png

このやり方だと、環境変数の設定を変更して保存 (Ctrl +S) すると、Visual Studio が自動で ASP.NET Core アプリのプロセスを再起動してくれ、アプリケーション構成が再読み取りされて、設定内容が即反映されるのだ。


appSettings.json 及び appSettings.Develop.json を編集・保存した場合は、そのようなプロセス再起動をはじめ、自動でアプリケーション構成を再読み取りされることはないようである。
(どこかで、appSettings.Develop.json が変更されたら再読み取りするオプション指定を見かけたような気もするのだが。)


ちなみに、こうして Visual Studio 上から設定した環境変数設定は、プロジェクトのフォルダ以下、Properties フォルダ内の launchSettings.json 内に書き込まれる。


「dotnet run」で実行する際も、この launchSettings.json の内容は読み取られて使われる。
なので、launchSettings.json に環境変数を設定しておいて「dotnet run」で実行すると、その環境変数設定が反映されて実行される。
ご参考までに。


また、これも至極当然のことではあるが、こうして環境変数で上書きする際も、セクション名 (環境変数名) の英字の大小は区別されない。


Microsoft Azure の App Service 設定での "上書き"

実は、Microsoft Azure の App Service 上に Web アプリを配置したときに、Azure ポータルから「アプリケーション構成」で設定する値も、アプリ側には環境変数として現れる。
なので、この仕組みで、ASP.NET Core アプリのアプリケーション構成を Azure ポータルから上書き設定することができるのだ。


その際のセクション名の指定方法は、上記のとおりコロン区切りでセクション名を連結した名前を、Azure ポータル「アプリケーション構成」の欄の "APP SETTING NAME" のところに指定してやれば良い。
d0079457_23263924.png





"上書き" というよりは "合成" と言ったほうがイメージあってる?

これら "上書き" の動作だが、優先順位としては次のとおり。


環境変数 > appSettings.Develop.json > appSettings.json


上記3者で同じセクション名で異なる値をそれぞれ設定していた場合、環境変数による設定が勝つ。


また、"上書き" とは言ったが、appSettings.json 内に記述のないセクション名のアプリケーション構成を、環境変数で設定してあれば、アプリ側ではちゃんと読み取れる。


例えば appSettings.json に SiteSettings.Title がなくても、
"SiteSettings": {
"TimeZone": { // <- "Title" プロパティがない!
...
環境変数 "SiteSettings:Title" に "HELLO!" が設定されていれば、アプリ側ではちゃんと読み取れる。
settings.Value.Title; // <- "HELLO!" ... 環境変数での設定が見えている!
なお、上記のような例を見ると、appSettings.json に存在しないセクションなので、存在しないものに対して "上書き" という言い方には違和感を覚えるかもしれない。
上書きというよりかは、Key-Value の辞書のツリーを "合成" (マージ) していくようなイメージのほうが理解しやすいかもしれない。


よくよく考えたら、環境変数を読むのに使えたり。ここまでを振り返ってよくよく突き詰めて考えてくと、実は ASP.NET Core のアプリケーション構成の仕組みで、環境変数がまるっと取得できてしまったりもするのだ。


ここまで紹介してきた例では、Startup にて、"Configuration.GetSection("SiteSettings")" と設定していたので、すべての環境変数は見えず、"SiteSettings:~" で始まる名前の環境変数だけ見えていた。


これを改め、単に "Configuration" オブジェクトそのものを渡してバインドすれば、すべての環境変数値がバインド可能だ。


また、ASP.NET Core アプリケーション構成の仕組みは、必ずしも静的型付けしなくては使えないわけではなく、Configuration オブジェクトを参照し、その GetValue などのメソッドを使って、文字列でセクション名 (= 環境変数名) を指定して取得することもできる (下記など参考に)。

本来の用途ではないが、ASP.NET Core のアプリケーション構成の仕組みを介して、プロセスの環境変数をすべて参照することも可能だ。



ところで、コレクションって何にバインドできる?ところで、今回の例にある、"Tags" セクションは、JSONN 上、配列になっており、これのバインド先の SiteSettings クラスでも配列としている。
public class SiteSettings {
...
public TagSettings[] Tags ... // <- TagSettings 型の "配列"
...
試したところ、実はここ、配列じゃなくても大丈夫だった。
"List<T>" でも "IEnumerable<T>" でも "ICollection<T>" でもバインドできた。
public class SiteSettings {
...
public IEnumerable<TagSettings> Tags ... // <- これでも OK!
...
果たして "T[]"、"List<T>"、"IEnumerable<T>"、"ICollection<T>"、etc. のいずれでバインドするのが最適なのか、自分にははっきりとは判断つきかねる。


ただ、どちらかといえばいちばん書き換えできなさそうな "IEnumerable<T>" がいいのかなぁ、と漠然と考えている。




環境変数からコレクションの設定を上書きするには?さて今回の例の "Tags" セクション内の配列構成を、環境変数で上書きするには、どのような変数名を指定すればよいのだろう?


最初は、"SiteSettings:Tags[0]:Text" というような、C# や JavaScript などでよくあるインデクサ表記を環境変数名とすることで上書きできるのかと考えた。
が、試したところこれは間違い。


正解は "SiteSettings:Tags:0:Text" だ。


これは "JavaScrit 的な考え方" に慣れてるとわかりやすいと思う。
すなわち、配列の要素を、例えば0番目の要素であれば「Tags オブジェクトの、"0" という名前のプロパティ」というように捉えるのだ。


今回の例の、上書き前のオリジナルの appSettings.json の構成内容だと、Tags プロパティの内容は下記のとおりとなっている。
settings.Value.Tags[0].Text; // <- "Foo"
settings.Value.Tags[0].Priority; // <- 0.7
settings.Value.Tags[1].Text; // <- "Bar"
settings.Value.Tags[1].Priority; // <- 0.3
ここで環境変数 "SiteSettings:Tags:1:Text" に "Hoge" を設定すると、下記のとおり上書きされる。
settings.Value.Tags[0].Text; // <- "Foo"
settings.Value.Tags[0].Priority; // <- 0.7
settings.Value.Tags[1].Text; // <- "Hoge" ... "Bar" から上書きされた!
settings.Value.Tags[1].Priority; // <- 0.3 ... ここはそのまま!
上書き前の配列要素数を超えたら...?

さて、では今回の例において、環境変数 "SiteSettings:Tags:999:Text" に "Hoge" を設定したらどうなるのか!?


アプリ側で読み取った結果はこうなった。
settings.Value.Tags[0].Text; // <- "Foo"
settings.Value.Tags[0].Priority; // <- 0.7
settings.Value.Tags[1].Text; // <- "Bar"
settings.Value.Tags[1].Priority; // <- 0.3


// Tags[2] が増えた!
settings.Value.Tags[2].Text; // <- "Hoge"
settings.Value.Tags[2].Priority; // <- 0.0
一瞬戸惑ってしまったが、改めて JavaScript 脳になって考えてみると、何のことはない、環境変数名の "999" は「配列の添え字」ではなく、プロパティ名なのだ。


どうやら、ASP.NET Core のアプリケーション構成の仕組みは、バインド先の型がコレクションである場合、バインド元の値の "プロパティ" を列挙して、その値を順繰り、バインド先のコレクションの要素に編成しているようである。


配列、じゃなくて、結局は Key-Value の辞書であるなので、数字だけじゃなくて、任意の識別子を当てはめられる。


例えば環境変数名を、"SiteSettings:Tags:999:Text" ではなく、"SiteSettings:Tags:FizzBuzz:Text" としても、結果は同じになるのだ。


また、環境変数からの上書きというのではなく、appSettings.json の記述そのものを辞書形式で記述することもできる。
"SiteSettings": {
...
"Tags": [
{ "Text": "Foo", "Priority": 0.7 },
{ "Text": "Bar", "Priority": 0.3 }
]
...
は、アプリケーション構成的観点では以下のように記述してるのと同義と言える。
実際、アプリでの読み取り結果も変らなかった。
"SiteSettings": {
...
"Tags": {
"0": { "Text": "Foo", "Priority": 0.7 },
"1": { "Text": "Bar", "Priority": 0.3 }
}
...
さらには、プロパティ名 (Key) は数字である必要すらない。
下記でも TagSetting[] にバインドされ、読み取り結果は変わりない。
"SiteSettings": {
...
"Tags": {
"Alpha": { "Text": "Foo", "Priority": 0.7 },
"Omega": { "Text": "Bar", "Priority": 0.3 }
}
...
ちなみに、上記のような感じで Key-Value 辞書オブジェクトをコレクションにバインドした場合、その要素の追加順序は、プロパティ名 (Key) のオーダーとなるようだ。


削除はできなさそうなお、このような仕掛けであるようなので、appSettings.json で設定した配列構成要素を、appSettings.Develop.json や環境変数から "削除" することはできなさそうだ。


例えば今回の例において、appSettings.Develop.json を以下のとおり記述し、Tags の設定内容を要素数 0 件に潰そうとしたとする。
"SiteSettings": {
...
"Tags": [] // オリジナルの Tags 構成値を0件にしてやる!
...
しかしこれは意図したとおりには動作しない。


配列ではなく、"0" や "1" といった配列序数を名前としたプロパティを持つオブジェクトを "上書き" するのだ、と JavaScript 脳で考える必要がある。


JavaScript で言うならば、オリジナルの Tags オブジェクト に対し、空のオブジェクトで Object.assign するようなものだ。
var tags = {"0":{...}, "1":{...}};
var tagsDevlop = {}; // "Tags":[] は空オブジェクトと同義


// 空オブジェクトを assign しても、tags は何の変化もなし!
Object.assign(tags, tagsDevelop);
結果として、上記 appSettings.Develop.json は、何の効果ももたらさないこととなる。


そう、JavaScript の Object.assign を知っているならば、ASP.NET Core アプリケーション構成の "上書き" の仕組みは、Object.assign であると考えると間違いがないだろう。


まとめ以上、ASP.NET Core のアプリケーション構成の上書き (というか、"合成") の振る舞いについて色々試した結果となる。


ざっくりおさらいすると下記のとおり。


英字の大文字・小文字は区別されない。
appSettings.Develope.json より環境変数のほうが勝つ。
環境変数ではセクション名をコロンで区切って設定。
Visual Studio での環境変数設定は Properties\launchSettings.json ファイルに保存される。
コレクションのバインド先は "T[]", "List<T>", "IEnumerable<T>", "ICollection<T>" のいずれでも OK。環境変数でコレクションの要素を上書きするには、序数をプロパティ名として設定。
コレクションもその正体は、"0" や "1" といった序数をプロパティ名とした JavaScript オブジェクト (辞書) であるだけ。
なので、序数じゃない任意の識別子も指定できちゃう - その場合、プロパティ名 (Key) のオーダーでコレクションが編成される。
結局、JavaScript の Object.assign() で重ね書きしてる感じ。
なので、appsettings.json に設定済みの要素を、削除することはできないと思われる。



あと、最後になってしまったが補足しておくと、ここまでのアプリケーション構成の仕組みは ASP.NET Core アプリに限らず、Microsoft.Extensions.Options でアプリケーション構成を扱う .NET アプリ全てに適用される。


今回の投稿はかなりの大作となってしまった。
これでアプリケーション構成の "上書き" で迷うことはなくなった...と思いたい。


.NET Framework 上での xUnit 単体テストを SDK スタイルのプロジェクト形式で作る

$
0
0
xUnit 単体テストプロジェクトも SDK スタイルでやりたい!Windows OS 上で Visual Studio 2017 を使ってのプログラム開発における、.NET Core ではなく .NET Framework 用の単体テストプロジェクトの話。



C# + .NET Framework 用の単体テストプロジェクトを作ると、そのプロジェクトファイルの形式は、新しい "SDK スタイル" と呼ばれる形式ではなく、旧来からのプロジェクト形式となる。



もっとも、単体テストのプロジェクト形式が従来形式だからといって、そんなに困ることもない。


しかし最近、xUnit を使った単体テストプロジェクトにおける NuGet パッケージ参照で躓いてしまった。


というのも、従来形式のプロジェクトだと、基本的にソリューションファイルからの相対位置で、packages サブフォルダに参照パッケージがダウンロードされる。
そのせいで、ちょっと別のフォルダ階層のソリューションから同プロジェクトを参照すると、".dll が見つからない!" といったビルドエラーになってしまったのだ。


SDK スタイルのプロジェクト形式だと、NuGet パッケージ参照の方式が変わるので、このようなトラブルはなくなる。


そこで、この xUnit 単体テストプロジェクトを、SDK スタイルのプロジェクト形式に変更してみた。


SDK スタイル形式で xUnit 単体テストプロジェクトを作る変更といっても、.csporj ファイルを書き換えると、手落ちがあっては後々面倒である。
そこで改めて新規プロジェクト作成から進めてみた。


Visual Studio を起動し、プロジェクトの新規作成から、(今回対象のプロジェクトは、.NET Core ではなく .NET Framework である必要があるのだが、まずは)「.NET Core」のカテゴリを選択、xUnit の単体テストプロジェクトを作成する。
d0079457_00252250.png

今回対象のプロジェクトは、.NET Core ではなく .NET Framework である必要があるのに、あえて「.NET Core」用の単体テストプロジェクトを作ったのは、SDK スタイルの C# + xUnit な単体テストプロジェクトファイルを作成するのにいちばん近道だったからだ。



そして続けて、できあがった xUnit 単体テストプロジェクトファイルを編集し、対象フレームワークを .NET Core から .NET Framework に書き換える。


今回の .NET Framework の対象バージョンは 4.5 だったので、.csproj ファイルには "net45" と記した。

d0079457_00252265.png

これで、対象フレームワークは .NET Framework 4.5 としつつ、プロジェクト形式が SDK スタイルの、 C# + xUnit 単体テストプロジェクトが出来上がった。



上手くいったと思ったのだが...早速テストコードを移植しビルドしてみると、当然のことながらビルドは成功し、Visual Studio のテストエクスプローラにもテストが検出されて表示された。
d0079457_00252204.png

ここまでは順風満帆。



では単体テストを実行してみると...






あれ?


テストエクスプローラの表示上、各テストがいずれも「未実行」のアイコンのまま。


Visual Studio の出力ウィンドウをよく見てみると、
d0079457_00252238.png

xunit.execution.desktop.dll がないので単体テストを実行できません、というではないか。




たしかに、単体テストプロジェクトの出力フォルダを見てみると、言われた xunit.execution.desktop.dll ファイルは存在しない。
(.NET Core プロジェクトではなく .NET Framework のプロジェクトなので、GAC に存在しない必要なDLLファイルは出力フォルダに出現する)
d0079457_00252208.png

.NET Framework 4.5.2 なら OK!?どうしてこういう事態になるのか、あまり深く考えずにとりあえずはとばかりにネットで検索してみた。


そうしたところ、xUnit のリリースノートに答えが。




.NET Framework のバージョン 4 および 4.5.1 は公式サポート期間を過ぎたので、4.5 ではなく、4.5.2 に引き上げろ、とのこと。


公式サポート期間、なるほど、そうでした。



自分のこのケースでは、たまたま古い内製ライブラリの保守の関係で、当時、.NET Framework 4.5 を対象にしていたので単体テストプロジェクトも .NET Framework 4.5 を対象に考えていて、こうなってしまったのであった。


ということで、単体テストプロジェクトの対象 .NET Framework のバージョンを、4.5 から、4.5.2 に引き上げてみた。


その上でリビルドし、Visual Studio のテストエクスプローラからテスト実行したところ、今度は期待どおりに実行されたのであった。

d0079457_00252231.png



ちなみに、.NET Framework のバージョンを 4.5.2 に引き上げたプロジェクトでの出力フォルダを見てみると、ちゃんと xunit.execution.desktop.dll ファイルが配置されていた。
d0079457_00252247.png





まとめ以上、とりあえず、


(.NET Core ではなく) .NET Framework + C# + xUnit な単体テストプロジェクトを、SDK スタイルのプロジェクト形式で Visual Studio 上で開発・実行することは可能
但し、対象 .NET Framework のバージョンが 4.5 だと期待どおりに動作しないので、4.5.2 に引き上げる必要がある



ということがわかった。


npm パッケージのバージョンを上げてたら webpack 実行時に _ValidationError2.default 例外が発生

$
0
0
JavaScript を webpack を使ってモジュールバンドルする、そんな SPA な Web アプリ開発の話。



個人的には早く Blazor に移行したくてたまらない今日この頃であるが、仮に Blazor が公式リリースされたとしても、これまで開発した webpack な JavaScript 製 SPA の保守は必要である。


さて、今回、そのような事情から、過去に作ったプロダクトの保守が発生した。


折角の保守の機会なので、60本ほど参照している npm パッケージを確認し、せめてパッチバージョンくらいは更新しておこう、とちまちま参照 npm パッケージのバージョンを、場合によってはマイナーバージョンも含めて手作業で上げていった。
(※ ncu は知ってるけど、諸事情により今回は出番なし。)


さてこんな感じかなー、と落ち着いたところで、webpack を実行してみたところ、下記の例外が出るようになってしまった。
...\node_modules\schema-utils\dist\validateOptions.js:42
throw new _ValidationError2.default(ajv.errors, name);
^
せっかくのエラー詳細がわからないコンソール出力を読むに、schema-utils モジュールで JSON スキーマに基づく入力 JSON の検証で、検証エラーが発生したっぽい。


ということは、おそらくは、webpack.config.js の記載内容のどこかに誤りがあるのだろう、というところまでは察しがついた。


しかしここまでの情報では、いったい webpack.config.js 中のどこでどんな問題で JSON スキーマ検証エラーになったのか、見当がつかない。


いちおう、ネットで検索もしてはみたけれども、まぁ、いろいろヒットするにはするのだが(下記は一例)、




いかんせん、このエラーを引き起こす原因は、ようするに JSON の書き損じなので、それこそシチュエーションは十人十色。
ネット検索の結果では、自分のケースを解決できそうにもない。


上記コンソール出力を見るに、せっかく "ajv.errors" や "name" といった有益そうな情報を例外オブジェクトにまとめて throw してくれているのに、その内容が見られないことには如何ともしがたい。


webpack の機能(例えばコマンドラインスイッチなど?)で例外内容を見る事ってできるんでしょうか。


console.log で出力するように書き換えてしまえ!正攻法はわからず仕舞いだったが、幸い、上記コンソール出力には、大変有り難いことに、例外を発射している箇所の JavaScript ファイル名と行番号が書いてある。


そこで、この JavaScript ファイル ― .\node_modules\schema-utils\dist\validateOptions.js ― の該当箇所に console.log() を書き足して、エラーの内容、すなわち "ajv.errors" と "name" の内容をコンソール出力するようにした。
d0079457_22161070.png

これでもういちど webpack を実行すると、エラーの詳細がコンソールに出力された!
d0079457_22165075.png

これでようやくわかったのは、



この検証エラーは UglifyJs Plugin に関する JSON で発生していること、
そして JSON 中、"parallel" プロパティの指定は boolean か interger のいずれかである必要があるのに、それに違反している


ということだ。


改めて webpack.config.js 中の UglifyJs プラグインを構成しているところを確認してみる。
new UglifyJSPlugin({ parallel: { cache: true, workers: 2 } })
たしかに、parallel プロパティに、boolean ないしは integer ではなく、オブジェクトを渡していた。
かつてはこの設定方式でよかったのだろうけれども、新しいバージョンでは不可となったということであろう。


自分は Windows OS 上で Visual Studio IDE を使って開発をしているのだが、幸い、Visual Studio のエディタの支援によって、現在バージョンにおけるオプション指定をガイドしてもらうことができた。
d0079457_22172414.png

ということで、 UglifyJs プラグインの構成を下記に更新。

new UglifyJSPlugin({ parallel: true, cache: true })
これで再び webpack を実行すると、今度は無事成功した。
解決!

はじめての C# からの Selenium、そして NET Core ではエラーになる場合

$
0
0
Selenium とはここで言う Selenium とは、Web ブラウザをプログラミング言語から自動操縦することを可能にするソフトウェアだ。


本稿では、C# から Selenium の .NET バインディングを使用して Web ブラウザを操縦できるようになるまでの手順を紹介する。


なお、Selenium は、Firefox や Google Chrome などなど、各種 Web ブラウザを操縦可能だが、話を簡単にするためにとりあえず今回は Google Chrome を操縦することとする。
.NET Framework + Visual Studio の場合まずは Visual Studio を起動し、プロジェクトの新規作成から適当な C# プロジェクトを作成する。


ここではコンソールアプリとしたが、必要に応じて WinForms などの GUI プロジェクトや単体テストプロジェクトでも構わない。
d0079457_22342786.png

続けて、NuGet パッケージマネージャーから、以下の 2 つのNuGet パッケージをこの C# プロジェクトに追加する。


Selenium.WebDriver
Selenium.WebDriver.ChromeDriver

d0079457_22342254.png

ちなみに、Firefox を操縦したければ Selenium.WebDriver.GeckoDriver NuGet パッケージをプロジェクトに追加すればよい。



あとはブラウザを操縦する C# プログラムを記述すればよい。
下記は Chrome で Bing 検索を開き、"Selenium WebDriver" という語句を検索実行する例だ。


using OpenQA.Selenium.Chrome;
...
using (var driver = new ChromeDriver())
{
driver.Navigate().GoToUrl("https://www.bing.com/");


driver
.FindElementById("sb_form_q")
.SendKeys("Selenium WebDriver");


driver
.FindElementById("sb_form_go")
.Click();


Console.WriteLine("OK");
Console.ReadKey(intercept: true);
}
d0079457_22341684.png

以上でビルド & 実行 (Visual Studio 上で Ctrl + F5) すれば、そのとおり実行される。
d0079457_22341144.png

以上である。



上記手順にあるとおり、ブラウザを Selenium 経由で操縦するための通訳を行なうプログラム、"WebDriver" も NuGet パッケージで配布されていて、これを参照する格好となっている。


例えば Google Chrome 用の WebDriver は (Windows OS の場合) "chromedriver.exe" というプログラムファイルである。
この "chromedriver.exe" が NuGet パッケージ参照によって、ビルド先の出力フォルダに自動でコピーされるのだ。


このため、


「このプロジェクトを git clone してビルド & 実行するには、事前に自分で WebDriver プログラムをダウンロードして、Path の通った適当なフォルダに解凍しておいてね」


みたいな事前の環境整備が不要である。
.NET Core + dotnet CLI の場合.NET Core がインストールされている環境で、dotent コマンドを使い、同じようにやってみることにしよう。


コンソール (ターミナル) を開き、まずは適当な作業フォルダを作成しよう。


カレントディレクトリをこうして作成した作業フォルダの中に移動し、以下のコマンドで .NET Core なコンソールアプリの C# プロジェクトを新規作成する。
> dotnet new console
続けて、以下のコマンドで、Selenium.WebDriver、及び Selenium.WebDriver.ChromeDriver の両 NuGet パッケージの参照を追加する。
> dotnet add package Selenium.WebDriver
> dotnet add package Selenium.WebDriver.ChromeDriver
d0079457_22340684.png

これでプロジェクトができたので、適当なテキストエディタを使って C# プログラムを同じように書きあげればよい。

d0079457_22335972.png

ビルドと実行はコンソール (ターミナル) に戻って「dotnet run」コマンドを実行だ。
dotnet run
これで Google Chrome が起動...








と思ったら起動せずに下記エラーになってしまった。


Unhandled Exception: OpenQA.Selenium.DriverServiceNotFoundException: The chromedriver.exe file does not exist in the current directory or in a directory on the PATH environment variable.


d0079457_22335399.png
"chromedriver.exe" が見つからない!?



だがしかし、.NET Framework 版のときと同じように、"chromedriver.exe" はたしかに出力フォルダにコピーされている。


d0079457_22334328.png

.NET Framework 版と同じように作成したのに、一体何故!?
Selennium が WebDriver を探す場所実は Selenium が WebDriver ファイルを探す場所は、環境変数 PATH が指すフォルダ群の他には、Selenium の .NET アセンブリファイルである WebDriver.dll が配置されているフォルダが対象となっている。


いっぽうで、.NET Core では NuGet パッケージ参照しているアセンブリファイル (.dll) は、出力フォルダにコピーされないのが既定の仕様である。


出力フォルダにコピーされない代わりに、%HOMEPATH%\.nuget\packages フォルダ以下に共有して保存されているアセンブリファイルが実行時にプロセスに読み込まれる仕組みだ。


そのため、.NET Core 版プロジェクトでは、Selenium の WebDriver.dll アセンブリがある場所 = (プロジェクトの出力フォルダではなく) %HOMEPATH%\.nuget\packages\...\selenium.webdriver\... みたいな場所で、"chromedriver.exe" を探してしまい、しかしもちろん、そんなところに "chromedriver.exe" は存在しないから件のエラーになってしまうという顛末である。
回避策 A - 出力フォルダにコピーしちゃうC# プロジェクトファイル .csproj 内に指定を追記することで、.NET Core なプロジェクトであっても、参照している NuGet パッケージのアセンブリファイルを出力フォルダにコピーすることが可能だ。


具体的には、.csproj ファイル内の PropertyGroup ノード内に以下の記述を追加する。
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
d0079457_22334874.png

こうしてビルドすると、WebDriver.dll が (.NET Framework のときと同じように) プロジェクトの出力フォルダにコピーされ、この状態であれば "chromedriver.exe" が見つかるので、無事実行できるようになる。

d0079457_22333706.png

ただ、.NET Core なプログラムだと、小さな NuGet パッケージをたくさん参照する傾向がありがちで、発行時はさすがにともかくも、通常のデバッグビルド・デバッグ実行のたびに大量の .dll が出力フォルダに毎回コピーされるのもどうかという気がしないでもない。
回避策 B - Selenium に明示的に教える.NET Core なプロジェクトで、出力フォルダを確実に指す実行時情報がある。


AppDomain クラスの BaseDirectory プロパティだ。


そして幸い、Google Chrome 用 Web Driver "chromediver.exe" との通信を司る ChromeDriver クラスのコンストラクタは、"chromediver.exe" のありかを指定する1引数をもつオーバーロードバージョンがある。


この2者を組み合わせて以下のように "chromediver.exe" のあるフォルダを明示的に指定すれば良い。
var baseDir = AppDomain.CurrentDomain.BaseDirectory;
var driver = new ChromeDriver(baseDir);
d0079457_22333091.png

こうすれば、.csproj をいじらずとも、期待どおり実行されるようになる。
まとめC# なプログラミングにおいては、.NET Framework 版、.NET Core 版のいずれであっても、


    プロジェクト作成
    NuGet パッケージ参照追加
    コーディング



の 3ステップで Selenium によるブラウザ操縦プログラムが実装できる。


このとき、WebDriver ファイルの手動による事前配置は不要で、NuGet パッケージ参照によって WebDriver ファイルは解決される。


ただ、.NET Core のときは .NET Framework のときと比べてちょっとだけ手を掛けてやらねばならず、


プロジェクト設定を書き換えて、NuGet パッケージのアセンブリファイルをすべて出力フォルダにコピーするようにするか、
ソースコード上で、WebbDriver ファイルのあるフォルダを明示するか



しないと実行時に WebDriver ファイルが見つからないエラーとなるので注意。


下記 Issue が参考になるかと思う。




以上。

ASP.NET Core Web アプリの開発中、開発機の外から接続できるようにする

$
0
0
背景Visual Studio や dotnet CLI + お好みのエディタなどを使っての、ASP.NET Core Web アプリを開発中の話。


開発中の ASP.NET Core Web アプリを、エミュレータではない実機のモバイル端末 (iOS や Android など) から接続して動作確認したいことはよくある。


その実現方法としては、例えば ngrok を使ってローカル開発中の ASP.NET Core Web アプリをインターネット上に晒し、そこにモバイル端末実機のブラウザから接続しにいくのもアリではある。


とはいえ、開発途上のものを、一時的とはいえインターネット上に晒すのは危険が伴う。
そもそも通信経路が長くなる上、ngrok の処理性能やモバイル回線の速度・帯域にも大きく左右されて、すばやい動作確認、トライ&エラーの繰り返しがやりにくい。


ということで、顧客に見てもらうなどの理由がない限りは、開発機と同一ローカルエリアネットワークに Wi-Fi 接続したモバイル端末実機から、同ネットワーク経由で接続するのが普通かと思う。


ただ、ASP.NET Core Web アプリの既定のプロジェクト構成では、開発中の ASP.NET Core Web アプリはローカルホスト (127.0.0.0/24, ::1) でしか待ち受けしていない。
なので、当該開発機の外部からは、そのままでは接続できない。


そこで、プロジェクト構成を変更する必要がある。


Visual Studio 上からの設定変更Visual Studio で開発中の場合、お勧めの方法は以下のとおり。


    まず実行形態を、IIS Express によるホスティングではなく、プロジェクト実行 (いわゆる、"dotnet run" 実行) を選択しておく。
    Visual Studio 上で当該 ASP.NET Core Web アプリプロジェクトのプロパティ表示を開き、[デバッグ] カテゴリを開く。
    この画面で、[ブラウザを起動] の設定で、「https://localhost:5001」というような絶対 URL を明記。
    続けて [環境変数] のところに、[名前] が「ASPNETCORE_URLS」である項目を追加、[値] には「https://*:5001;http://*:5000」というように、ホスト名を 「*」で指定した待ち受け URL を、複数ある場合はセミコロン (;) 区切りで設定する。
    [アプリケーション URL] の欄は空欄にする。

d0079457_22243549.png

以上で Ctrl+F5 で実行などすれば、当該開発機の外部からも、この ASP.NET Core Web アプリが接続を受理、応答を返すようになる。



なお、上記で行なった設定内容は、Properties フォルダ内にある、「launchSettings.json」というファイル名の JSON ファイルにその設定内容が保存されている。


例えば次のとおり。
{
"iisSettings": ...,
"profiles": {
"IIS Express": ...,
"WebApplication1": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "https://localhost:5001",
"environmentVariables": {
"ASPNETCORE_URLS": "https://*:5001;http://*:5000",
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": ""
}
}
}
"dotnet run" で実行する場合dotnet CLI を用いた "dotnet run" による実行でも、上記 launchSettings.json に基づいて ASP.NET Core Web アプリを立ち上げる。


なので、 dotnet CLI を使っての開発中の場合でも、上記のように launchSettings.json を作成・記述しておけばそれだけでよい。


また、先の説明では端折ってしまったが、設定のキモは、環境変数 ASPNETCORE_URLS の設定である。


標準のプロジェクトテンプレートで作成された ASP.NET Core Web アプリであれば、環境変数 ASPNETCORE_URLS を見て、そこに設定されている待ち受け URL を採用するようにできているのだ。
先で説明している launchSettings.json の内容も、要するに環境変数 ASPNETCORE_URLS を設定しているわけである。


なので、launchSettings.json がなくても、コマンドプロンプト (ターミナル) で下記のように環境変数 ASPNETCORE_URLS を設定してから dotnet run を実行することでも OK だ。
> rem 以下は Windows OS のコマンドプロンプトでの例
> cd \path\to\yourprojectdir
> set ASPNETCORE_URLS=https://*:5001;http://*:5000
> dotnet run
※ "dotnet run" を実行したコマンドプロンプト (ターミナル) 内で環境変数 ASPNETCORE_URLS を設定しつつ、 launchSettings.json でも環境変数 ASPNETCORE_URLS を設定していた場合は、 launchSettings.json の設定のほうが優先する。


補足: アプリケーション URL を設定すればよいのでは?ホストアドレスに「0.0.0.0」を指定実は、Visual Studio での設定時、環境変数 ASPNETCORE_URLS ではなく、[アプリケーション URL] に、「https://0.0.0.0:5001;http://0.0.0.0:5000」というように、ホストアドレスを「0.0.0.0」とした待ち受け URL を設定することでも、外部からの接続を受け付けるようになる。
d0079457_22243059.png

但し上図の設定だと、IPv4 アドレスでのみ待ち受けすることになるので注意。


すなわち、開発機上のブラウザでアドレスバーに「https://localhost:5001」や「http://127.0.0.1:5001」と入力するぶんには当該 Web アプリにアクセスできるものの、IPv6 アドレスである「https://[::1]:5001」を入力すると、接続不能となるのである。


まぁ、「開発中の Web アプリをモバイル端末実機で確認」という用途であれば、IPv4 アドレスのみでの待ち受けで十分事足りる気もする。
なので、実際上は、上図のようなアプリケーション URL 設定による方式で十分かもしれない。


もっとも、IPv6 アドレスでも待ち受けさせることももちろん可能で、その場合はホストアドレスとして「[::]」を指定すればよい。
例えば「https://0.0.0.0:5001;http://0.0.0.0:5000;https://[::]:5001;http://[::]:5000」というようにアプリケーション URL を設定すれば、IPv4 でも IPv6 でも、外部からつながるようになる。


ただ、ちょっと設定が長すぎではある。


アプリケーション URL 設定で、ホストアドレスに「*」を指定しないのはなぜ?アプリケーション URL 設定に、(環境変数 ASPNETCORE_URLS での設定と同じように)「https://+:5001」とか「https://*:5001」 のように、ホストアドレスとして「+」や「*」を指定すれば、 IPv4 と IPv6 の設定を併記しなくて済むのでは? とも思われた。


試しにやってみたところ、 launchSettings.json を直接編集して "applicationUrl" に "https://*:5001" を設定し、dotnet run で実行するぶんにはうまくいった。


しかし launchSettings.json がこの状態で、当該プロジェクトを Visual Studio で開き、Ctrl+F5 実行すると、「Invalid URI: The hostname could not be parsed.」というエラーメッセージボックスを表示して起動してくれない。


また、Visual Studio 上からは、アプリケーション URL に + や * をホストアドレスとした URL を設定すると入力検証エラーになって設定できない。


加えて、先のとおり launchSettings.json を直接編集した状態でプロジェクトのプロパティ画面から [デバッグ] セクションを開いてしまうと、場合によっては入力検証エラーに阻まれ、プロパティ画面を閉じたり保存したりができなくなり、どうにもならない "詰んだ" 状況に陥ってしまう。




...これらのことから、環境変数 ASPNETCORE_URLS に待ち受け URL を設定する方式がいちばん一貫性もあり、自分のお勧めの方法と結論づけている。


補足: [ブラウザを起動] の URL を明記するのはなぜ?dotnet run で実行するぶんには、 launchSettings.json の "launchUrl" は無くても (あるいは空文字であっても) なんら問題ない。
本稿作成時点では、dotnet run では、ブラウザの起動までは行なわないからだ。


しかし Visual Studio 上で開発・実行してブラウザも開きたいというケースでは、[ブラウザを起動] の URL を明記しておかないと厄介なことになる。


まず、環境変数 ASPNETCORE_URLS に待ち受け URL を設定する方式の場合、Ctrl+F5 実行すると、Web アプリのプロセスは起動するものの、Visual Studio がエラーメッセージボックスを出してブラウザ起動に至らない。


また、アプリケーション URL にホストアドレス 0.0.0.0 で設定する方式の場合、Ctrl+F5 実行すると、ブラウザは起動するものの、「https://0.0.0.0:5001」という URL を開いてしまってアクセスできない結果となる。


なので、少なくとも Visual Studio 上での実行でブラウザも同時に開きたいケースでは、ちゃんと [ブラウザを起動] の URL を明記しておくに越したことはない。


補足: IIS Express での実行時について書かれてないけど?需要と余力があれば調べて試して本ブログに投稿する...かもしれない。

Angular + SignalR な Web アプリで、HubConnection.invoke() 時の変更検知発動を抑止する

$
0
0
背景サーバー側は ASP.NET Core 2.1、クライアント側は Angular 5.2.5 を使用し、さらにここに SignalR (JavaScript クライアントは 1.0.4) を加えてライアント/サーバー間でのリアルタイム通信を行なう Web アプリ実装における話。


SignalR は、Node.js で言うところの Socket.IO に相当するようなライブラリ。
ASP.NET Core Web アプリに、ブラウザを含めたクライアントへのプッシュ通信など、リアルタイム Web 機能を実現できるようにするものだ。


クライアント(ブラウザ)側では、サーバー側からのプッシュ通信を待ち受け(ハンドラ関数を登録しておく)するほか、SignalR の通信チャンネルを通じてサーバー側を呼び出すこともできる。


具体的には、クライアント(ブラウザ)側の JavaScript コードにて、SignalR の通信チャンネルを表す HubConnection オブジェクトの invoke メソッドを呼び出すことで実装する。
HubConnection.invoke() で発生する変更検知を debounce したい!さて、SignalR を使用して、クライアント/サーバー間通信を頻繁に行なう、クライアント側は Angular を採用した SPA 実装を行なっていると、ちょっと処理速度性能的に問題となることがある。


あまりに頻繁に SignalR の HubConnection.invoke() を呼び出す必要があると、Angular の変更検知処理が都度都度走ってしまい、アプリが重くなってしまうのだ。


そのため、Angular の NgZone の機能を使い、HubConnection.invoke() 呼び出し時の Angular の変更検知発生を明示的に抑止し、デバウンス処理などを自前で組み込むことにしていた。
前提知識: Angular の変更検知を走らせないようにする方法下記記事が参考になる。

記事タイトルは「Angularの外部でイベントが発生した時の変更検知の方法」だが、「逆に Angular に変更検知をさせたくない場合」の節に変更検知を抑止する方法が記載されている。



具体的には、NgZone オブジェクトを DI 機構で受け取っておき、その上で、変更検知を走らせたくない処理を、NgZone.runOutsideAngular() メソッドのコールバック内で実行すればよい。
HubConnection.invoke() での変更検知を抑止さて、SignalR の HubConnection.invoke() 呼び出し時、Angular の変更検知を走らせないようにするには、結論としては以下の3点。


    SignalR の接続開始処理 (HubConnection.start()) を、NgZone.runOutsideAngular() 内で実行する
    HubConnection.invoke() を NgZone.runOutsideAngular() 内で実行する
    HubConnection.invoke() の呼び出し結果を、async/await ではなく .then() による Promise メソッドチェーンで処理する

なぜ async/await してはいけないか?上記 3 の「async/await 使うな」についてはちょっと説明が要るかもしれない。


上記 3 を記載したのは、NgZone.runOutsideAngular() を使って変更検知抑止を実施したとしても、その処理を内包している async 関数の Promise が解決されるときに、結局変更検知が走るからである。


もっとも、その async 関数自体をさらに NgZone.runOutsideAngular() 内で実行すればこれまた変更検知は抑止できるので、絶対に async/await を使ってはいけない、と単純に言えるものではない。


また、もしかしたら、「とある async 関数 foo 内での HubConnection.invoke() 呼び出し時は、変更検知は抑止したいが、それら処理がひととおり終わって async 関数 foo から抜け出したら、変更検知が走って欲しい」というケースのほうが多いかも知れない。


そのような要件・シナリオの場合は、むしろ async/await で実装したほうが、コードも読みやすいし、期待した動作結果・挙動となることと思う。
start() と invoke()、片方だけ runOutsideAngular しても効果無しちなみに、HubConnection.start() と、HubConnection.invoke()、いずれか片方だけ NgZone.runOutsideAngular() 内で実行しても変更検知は走ってしまう。


特に HubConnection.invoke() だけ NgZone.runOutsideAngular() 内で実行した場合、変更検知は抑止されず、しかし変更検知のタイミングが invoke() の結果処理後にずれこみ、レンダリング内容に反映されないなど面倒なことになるようだ。


いっぽう、 HubConnection.start() を NgZone.runOutsideAngular() 内で実行しておいた場合は、


HubConnection.invoke() をそのまま実行した場合は従来どおり変更検知が実施され、
HubConnection.invoke() を NgZone.runOutsideAngular() 内で実行した場合は変更検知が抑止される、



という具合に呼び分けられるようになる。


つまり、SignalR 通信に関して Angular 変更検知を抑止するつもりなら、HubConnection.start() を NgZone.runOutsideAngular() 内で実行しておくのが肝要なようだ。
注意: HubConnection.on() で登録したハンドラ関数実行時の変更検知も抑止されてしまうしかしながら、前述の変更検知抑止処置 ― HubConnection.start() をNgZone.runOutsideAngular() 内で実行 ― をしてしまうと、サーバー側からのプッシュ通信を受け取るハンドラ関数呼び出し (HubConnection.on() で登録したコールバック関数の実行時) についても、一切、変更検知が走らなくなってしまう。


より正確に言えば、HubConnection.on() だけではなく、HubConnection.onclose() コールバックでも変更検知は発生しなかった。


これらコールバックでも変更検知を発動させるには、面倒だが、ハンドラ関数内で個別に NgZone.run() 呼び出しを使って明示的に変更検知を走らせるほかないようだ。


もっとも、「SignalR 通信のサーバー側プッシュのハンドラでは、 Angular 変更検知を抑止したい」ということであれば、HubConnection.start() をNgZone.runOutsideAngular() 内で実行すればよい、ということでもある。


コード例実際のコード例を GitHub にあげておく。

とくにクライアント側コード例の抜粋を以下に貼っておく。




ASP.NET Core Web アプリプロジェクトで、Node.js 使わずに Sass (SCSS) をコンパイルする

$
0
0
.NET Core SDK 環境のみで Sass (SCSS) をコンパイルするC# による Web アプリ・サーバー側実装のフレームワーク ASP.NET Core を使った、Web アプリプログラム開発での話。

最近はフロント側を Angular で書くようになったため、Sass (SCSS) を CSS にコンパイルするのは、webpack によるモジュールバンドリング処理の一環として webpack の loader で行なう必要があり、実際そのようにしている。


しかし他方、Angular などのフロント側フレームワークに依らない、静的なちょっとした Web サイト作成や、C# でフロント側を実装する Blazor のようなフレームワークを使うケースでは、Sass (SCSS) ファイルを css ファイルにコンパイルする方法は、必ずしも webpack に頼る必要はない。


実のところ、.NET Core プロジェクトのビルドシステムにおいては、nuget.org で公開されているライブラリ (NuGet パッケージ) による支援もあって、Sass (SCSS) を css にコンパイルすることが可能だ。


.NET Core SDK さえ開発マシンにインストールされていれば、Node.js などがインストールされていなくても、Sass (SCSS) コンパイルが可能なのである。
ただ、現時点では .NET Core プログラムのビルドの一環として Sass (SCSS) コンパイルを実施することが可能、という状況なので、プロジェクトファイル (.csproj など) が必須である。


実際にやってみる.NET Core プログラムのビルドの一環として Sass (SCSS) コンパイルを実施するには、まず、以下の NuGet パッケージを当該プロジェクトにパッケージ参照追加する。

dotnet CLI であれば、対象プロジェクトがあるフォルダをカレントディレクトリにして、下記を実行すればよい。
> dotnet add package BuildWebCompilerWindows OS 上で Visual Studio を使っている場合は、GUI のパッケージマネージャから追加してもよいだろう。
d0079457_21554818.png

次に、対象プロジェクトのルートフォルダに以下のような内容の設定ファイル "compilerconfig.json" を作成する。
[
{
"inputFile": "wwwroot/css/test.scss",
"outputFile": "wwwroot/css/test.css"
}
]
以上で設定完了だ。


上記 compilerconfig.json の inputFile で指定した Sass (上記例だと SCSS 形式だが) ファイル "wwwroot/css/test.scss" を作成し、ビルドを実行する。


そうすると、この Sass (SCSS) ファイルがコンパイルされて、outputFile で指定されたファイル名の CSS ファイルが出力されるのが確認できるはずだ。
さらにオマケとして、出力先 css ファイルは、そのミニファイ版も同時に生成される(ファイル名は .../test.min.css となっている)。


compilerconfig.json にどのような記述ができるかは、あまり詳しいリファレンスは見つけていないものの、先の NuGet パッケージの GitHub リポジトリにある README (下記 URL) が参考になる。


https://github.com/madskristensen/WebCompiler#compilerconfigjson



このように、.NET Core SDK 環境だけでも、プロジェクトのビルドの一環として、Sass (SCSS) ファイルを css にコンパイルすることが可能である。


ちなみにこの BuildWebCompiler NuGet パッケージ、実は Sass (SCSS) -> css コンパイルのみならず、LESS や CoffeeScript などなど、様々な変換が可能だ。
自分では実際には試したことがないが (※CoffeeScript 書くスキルがない...)、もし需要があるなら試してみるとよいかもしれない。


Sass (SCSS) -> CSS コンパイルだけ実行するビルド時に Sass (SCSS) -> CSS コンパイルができるようになったが、しかしながら開発中は、.scss を編集するだけでいちいちプロジェクト全体をビルドはしたくないところである。

dotnet CLI を使うなら、対象プロジェクトがあるフォルダ上で下記 dotnet コマンドを実行することで、Sass (SCSS) -> CSS コンパイルだけを実施することが可能だ。
> dotnet msbuild -t:WebCompile -nologo

ウォッチさらにできれば、.scss ファイルを保存したら自動で .css にコンパイルしてくれたら嬉しい。
そのためのやり方のひとつとして、「ファイルの変更を監視し、変更されたらタスク (この場合は .scss > .css のコンパイル) を実行する」という "ウォッチ" 方式がある。


このウォッチ方式による .scss > .css 自動コンパイルの実現は、dotnet CLI のウォッチ機能を利用することで可能となる。
ただし残念ながら自分が知る限り、ちょっとだけ事前の手順が必要。


まず対象プロジェクトファイルをテキストエディタで編集し、下記の要領で .scss ファイルを監視対象に含めるようプロジェクトを構成する必要がある。
<Project Sdk="Microsoft.NET.Sdk.Web">
...
<ItemGroup>
...
<Watch Include="wwwroot\css\*.scss" /> <!-- この行を追加 -->
</ItemGroup>
...
上記変更を施したら、対象プロジェクトがあるフォルダ上で下記 dotnet コマンドを実行することで、ファイル変更の監視を行なう dotnet CLI が常駐する。
> dotnet watch msbuild -t:WebCompile -nologoこの状態で .scss ファイルを上書き保存すると、.scss > .css コンパイルが実行されるようになる。


なお、dotnet CLI の watch 機能は、監視対象のファイルを状況に応じて使い分けることが難しいようで、例えばこの状態で、同プロジェクトに含まれる C# ソースコード (.cs ファイル) を上書き保存しても、.scss > .css コンパイルが走ってしまう。


dotnet CLI の watch 機能を、このプロジェクトでは .scss > .css コンパイルにしか使わない、という場合は、プロジェクトファイルに以下のとおり追記して、C# ソースコードファイルを監視対象から除外することが可能だ。
<Project Sdk="Microsoft.NET.Sdk.Web">
...
<ItemGroup>
...
<Compile Update="**/*.cs" Watch="false" /> <!-- この行を追加 -->
</ItemGroup>
...


IDE/エディタの拡張機能もあるが...以上ここまで、.NET Core SDK 環境で、プロジェクトのビルドの一環として Sass (SCSS) を CSS にコンパイルする方法について説明してきた。


なお、Sass (SCSS) を CSS にコンパイルする方法としては他にも、Visual Studio のような IDE や、Visual Studio Code などのエディタにおける「拡張機能」を利用することもできる。


実際、Windows OS 上で Visual Studio を使う場合は下記拡張を Visual Studio にインストールしておくと、Visual Studio 上での .scss ファイルの編集・保存時に .css へのコンパイルが実行されるようになる。

実のところ、これまで説明してきた、.NET Core アプリプロジェクトのビルド時に Sass (SCSS) コンパイルが実行されるやり方で使用した に BuildWebCompiler NuGet パッケージと上記 Visual Studio 拡張は同じコードベースで作られている。
そのため、先に説明した compilerconfig.json の設定を読んで同じように動作するので大変使い勝手が良い。
あと、同じ理由で、この Visual Studio 拡張も Sass (SCSS) のみならず LESS やら CoffeeScript やらもファイル保存時のコンパイルが可能だ。


また、Visual Studio Code であれば、下記の拡張が人気があるようだ。

このように、IDE やエディタの拡張機能を用いることで、.scss ファイル保存時に自動で .css にコンパイルすることができるようになり、このほうが開発者体験としてはより良いように感じる。


とはいえ、開発メンバー全員の IDE/エディタ環境を揃えるのが手間だったりうまくいかないことがあるかもしれない。
開発環境を再構築後に、うっかり拡張を入れ忘れてしまい、.scss を変更・保存したけれども、対応する .css が更新されずしまい、というバグだって引き起こしてしまうかも知れない。


そうしたケースに備える目的で、ソースコードをリポジトリから git clone するだけで、開発環境への各種拡張の追加インストールすることなく、最低限ビルドすればちゃんと .scss が .css にコンパイルされるようになるのは、これはこれで便利で大事なことだと思う。


補足これまでの説明では、あくまでも .NET Core アプリ開発としての説明としてきたが、実は BuildWebCompiler NuGet パッケージは、.NET Framework のプロジェクトに追加しても同じように動作する。


これまでの説明における dotnet CLI を使った用法はできないが、Visual Studio 上で .NET Framework 用アプリ開発時でも、同じようにビルド時に Sass (SCSS) ファイルを CSS ファイルにコンパイルすることが可能となる。


C# プログラムでの Excel シートの複製 (コピー) がうまくいかず ClosedXML から EPPlus に乗り換えた話

$
0
0
Excel ファイル (.xlsx) を、C# プログラムから読み書きするときの話。

C# プログラム (というか、.NET 系言語) から Excel ファイル (.xlsx) を読み書きするためのライブラリとして、自分はこれまで、ClosedXML を愛用してきた。

ClosedXML を使用してきた格別の理由があったわけではないが、大して公式ドキュメントに目をとおさずとも、自分の予想どおりな感じで使える API 体系がなじんだのかなと思う。
ClosedXML の限界(?) に遭遇!さてそんな ClosedXML だが、自分のとある要件の用途で、利用に堪えない制限があることが、最近になってわかった。


Excel ファイル中のシートをコピー (複製) したときに、シェイプ (図形) がコピー先に複製されないのだ。


具体的にはこんな感じのコードで、シート「Sheet1」を、シート名「Sheet2」として複製したとする。
using (var book = new ClosedXML.Excel.XLWorkbook(bookPath))
{
book.Worksheet("Sheet1").CopyTo("Sheet2");
book.SaveAs(bookPath);
}
そうしてできた Excel ファイルを MS Excel などで開いてみると、「Sheet1」にはあった図形 (シェイプ) が、「Sheet2」には複製されていないのだ (画像は複製されている)。


d0079457_22173680.gif

ここでの詳細は割愛するが、ClosedXML におけるシート複製のソースコードに目をとおした感じだと、シート中に含まれる様々なオブジェクトを列挙しつつ複製先のシートにそれらオブジェクトも複製していく実装になってるっぽいのだが、その複製処理のなかに、そもそもシェイプは対象として含まれていないように読めた。


代替案 - EPPlusさてどうしたものかな、と思案するに、.NET プログラミングにおける Excel ファイルの読み書きライブラリとして著名なもうひとつのライブラリ、EPPlus の存在を思い出した。

EPPlus を使った場合の Excel シートを複製する C# プログラムは、以下のような感じとなる。
using (var package = new OfficeOpenXml.ExcelPackage(new FileInfo(bookPath)))
using (var book = package.Workbook)
{
book.Worksheets.Add("Sheet2", book.Worksheets["Sheet1"]);
package.SaveAs(new FileInfo(bookPath));
}
EPPlus を使った上記コード例であれば、期待どおり、「Sheet1」に含まれる図形 (シェイプ) も、複製先のシートに複製された。


このような顛末から、今回案件では ClosedXML から EPPlus への乗り換えを行なった。


以上の動作を確認できるソースコード一式を GitHub に掲載しておいた。





補足乗り換えではなくて ClosedXML に対し、図形 (シェイプ) も複製されるようにプログラム変更する貢献をすることも選択肢にはあり得た。
しかしながら今回は少々急いでいたこともあり、ClosedXML への貢献ではなく EPPlus への乗り換えで済ませる選択肢をとった。


ということで、また、さすがに同様の件は、既に誰かが Issue あげてて対応が将来版で計画されているのではないかという気もしてて、ClosedXML には Issue すら報告していない。


それはいわゆる OSS に対する "タダ乗り" ではないか、という後ろめたさもあるのだが、そうはいっても取り急ぎ目先の問題を解消しなくてはいけない場面もある訳で (今回がまさそうだ)、また、OSS 全般という見方をすれば自分なりに活動はしているつもりなので (今回は取り上げなかったが、かつては C# から Excel ファイルを読み書きするときの定番であったライブラリ「NPOI」には、少しばかりだが contribute していたこともあり)、そこは大目に見てもらいたいなと考えている次第である。


なお、ClosedXML は MIT、EPPlus は LGPL でライセンスされているようなので、必要であればこの点の考慮をお忘れ無く。




.NET HTTP REPL を使ってみた

$
0
0
".NET HTTP REPL" とは?.NET HTTP REPL とは、.NET Core 上で動作するコマンドラインツール。


既存のコマンドラインツールで類似なものを挙げるとすれば、curl や wget が近いだろうか。


すなわち、コマンドプロンプト (ターミナル) 上から指定した HTTP 要求を送信し、返信されてきた応答を表示する類いの CUI (コマンドライン ユーザー インターフェース) プログラムである。


しかしながら、このツールの名前に "REPL" と付いているように、基本的に対話形式で使用する形態となっているのが .NET HTP REPL の特徴である。
REPL (Read-Eval-Print Loop) とは、入力・評価・出力のループのこと。主にインタプリタ言語において、ユーザーとインタプリタが対話的にコードを実行する。
出典: フリー百科事典『ウィキペディア(Wikipedia)』

ちなみに、.NET HTTP REPL のソースコードは GitHub 上の AspLabs というリポジトリでホスティングされており、Apache 2.0 ライセンスで公開されている。
このリポジトリの README には、"Repo for ASP.NET experiments that are not ready for a production release." と書かれており、この先どうなるか、心配すべきかわくわくすべきか迷うようなプロダクトが詰め込まれているように感じた。

ということで、実は .NET HTTP REPL は本記事を投稿の時点では公式リリース版ではなくプレビュー扱いである。
適宜注意されたし。


.NET HTTP REPL のインストールさて、.NET HTTP REPL を使用するには、まずは .NET Core SDK のインストールが必要である。


.NET Core SDK がインストールされ、コマンドプロンプト (ターミナル) 上から dotnet コマンドが実行可能になっていれば、.NET HTTP REPL のインストールは以下のコマンドを実行すればよい。

> dotnet tool install --global dotnet-httprepl --version 0.1.0-preview.19119.4このようにすることで、dotent Global Tools として nuget.org 経由で .NET HTTP REPL 本体がダウンロード、インストールされる。


これで .NET HTTP REPL が使えるようになる。


.NET HTTP REPL の起動・使い方.NET HTTP REPL は dotnet コマンドのサブコマンドという形態で実装されている。
そのため、コマンドプロンプト (ターミナル) から以下のコマンドを実行することで起動され、.NET HTTP REPL の対話モードに入る。
> dotnet httprepl最初に書いたとおり、.NET HTTP REPL は対話形式で使用する、HTTP 要求を送信するツールである。


まずは送信先 HTTP サーバーのホスト、プロトコル、ポートを、"base" という HTTP 要求送信の基点として設定しておく必要がある。
これは、.NET HTTP REPL の対話モードのコンソールにて、以下のように "set base" コマンドで設定する。
~ set base http://localhost:50893/base 設定が完了すると、.NET HTTP REPL のプロンプトも "(Disconnected)~" から "http://localhost:50893/~" というように設定された base に変る。
d0079457_22050805.png

こうして base 設定が済んだら、あとは、HTTP 動詞と同じ名前で用意されているコマンドを入力すればよい。

例えば、URL = api/SampleData/WeatherForecasts に HTTP GET 要求を送信する場合は下記のような感じで、割と直感的に使える。
~ GET api/SampleData/WeatherForecastsHTTP サーバーからの返信結果は、例えば JSON 形式で返された場合はよしなに色づけ・書式化されて表示される。
d0079457_22050834.png

当然 POST をはじめ、PUT, DELETE などの HTTP 動詞を送信するも可能で、また、POST や PUT で JSON コンテンツを送信したい場合は、-c スイッチに続けて送信したいコンテンツを記述すればよい。(以下は「ちょまど問題サーバー」に回答を送信して正答数を得る例)

POST https://chomado-problem-server.azurewebsites.net/answer -c [1,2,4,4,1,2,3,4,1,2]対話モードを抜けて .NET HTTP REPL を終了するには exit コマンドを実行すればよい。
~ exit.NET HTTP REPL には cd コマンドがあるさて .NET HTTP REPL のおもしろいところとして、対話形式のツールであることから状態が持てるので、"cd コマンド" が用意されていることが挙げられる。


つまり、HTTP 要求送信先 URL をスラッシュ区切りとしてディレクトリに見立てて、"現在ディレクトリ" を移動できる、という仕組みである。


例えば以下のように api/SampleData "ディレクトリ" まで、cd コマンドで "降りて" いれば、
~ cd api/SampleDataGET コマンドで api/SampleData/WeatherForecasts に要求送信するには、前半の api/SampleData を省略した、WeatherForecasts だけを送信先 URL として記述すればよい。
/api/SampleData~ GET WeatherForecasts"現在ディレクトリ" は .NET HTTP REPL のプロンプトに表示されているほか、引数なしの cd コマンドを実行することでも表示される。
d0079457_22050880.png

特定のリソースを指す REST 風な Web API に対して読み書き操作を色々試したいときなど、cd コマンドで URL の共通部分まで、あらかじめ "降りて" おけば、入力の手間が省けて良さそうな感じである。


※2019/04/22現在、cd コマンドが例外を発生させることがある (当方の環境。.NET Core 3.0 Preview 4)。但しとりあえず .NET HTTP REPL は動作継続し、cd コマンドによる "現在ディレクトリ" の変更は行なわれているようである。


Swagger にも対応!?.NET HTTP REPL の対話モードで help コマンドを入力すると、どんなコマンドがあるのかヘルプ表示される。
d0079457_22050809.png

これを見ると、まだ試せていないのだが、"set swagger" というコマンドもあるようで、どうやら Swagger 形式の Web API 定義 JSON を読み込んで便利に使えるようだ。



また、run コマンドによって "スクリプト" を実行できる、ともある。
これもまだ試せていないので、具体的にどのように使えるものなのか現時点ではわかっていないのだが、興味を引かれるところである。


感想.NET HTP REPL は、REPL と名前についているとおり対話形式で使用するものであり、起動時のコマンドライン引数で何か実行するということはどうやらできない模様だ(もしかしたら、スクリプト機能などで可能なのかもしれないが、まだよくわかっていない)。
そういう用途では、やはり curl などのほうが使えるということになりそうだ。


また、最近の Windows 10 OS には curl コマンドも標準で入るようになったので、自分がわかっている範囲だけでも、macOS/Linux各種ディストリ/Windows などなど、幅広い OS で使えるのも curl の良いところだろう。


他方、.NET HTTP REPL であるが、サーバーから返信された JSON コンテンツを、インデント付きで書式化・色づけして表示してくれるので、サーバー応答の JSON が読みやすいと感じた。
しかしながら、curl コマンドと jq コマンドの組み合わせ技のように、サーバー応答の JSON をフィルタしたり射影したりなどの凝った加工は、.NET HTTP REPL にはできないように見受けられる。


個人的には .NET HTTP REPL が直感的に使えるところが気に入った。
"GET /api/..." のように、自分が Web ブラウザになった気分で HTTP 要求を発行できるのが心地よい。


また、cd コマンドによる "現在ディレクトリ" の概念もおもしろいところだと思う。


さらに今後、Swagger 対応の機能の使い方がわかれば、もっと強力に使えるツールとなるかもしれないと期待もしている。


各種自動化やシェルスクリプトの記述などにおいては、相変わらず curl の出番となると思う。
そのいっぽうで、Web アプリ開発中に臨時的に Web API 呼び出しを試してみたいときなど、対話形式での使用のほうがよいケースでは、積極的に .NET HTTP REPL を使いそうな気がしている。


PowerShell の Invoke-WebRequest や Invoke-RestMethod は... うーん、自分が書く自動化系のスクリプトは PowerShell が俄然多いのでそういう PowerShell スクリプトファイル内ではよく登場するものの、今回の .NET HTTP REPL 的な、臨時的・対話的用途には、自分の手にはなじまなかったです、はい。



node のバージョンあげたら node-sass の npm instal でこけた話 - windows-build-tools 入れずに解決させる

$
0
0
Windows OS 上で SPA を開発していてのお話。



先日、しばらく前に構築した、とある Web アプリ (SPA) プロジェクトを保守する必要が出てきた。


久しぶりに当該プロジェクトをリモートリポジトリからローカル開発環境に git clone し、とりあえず npm install を実行したところ、いきなりエラーになってしまった。
npm ERR! node-sass@4.7.2 postinstall: `node scripts/build.js`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the node-sass@4.7.2 postinstall script.
どうやら、SaSS を CSS へ変換する Node.js 用ライブラリ node-sass のパッケージインストールでこけたっぽい。


使用している node-sass のバージョンは v.4.7.2。


開発当時はちゃんとクリーンビルド成功してたのに、なんで?


Node.js のバージョンが当時と今とで違う当該プロジェクトの開発当時と、今回とで環境で変っていたのは Node.js のバージョンだった。


開発当時、インストールされていたのは、たしか Node.js の v.8 系。


現在は Node.js を v.10.15.3 にアップデートしたあとの環境である。


どうも、この Node.js のバージョン違いが、開発当時は問題なくて、現在だと npm install できなくなる原因らしい。


さて、この npm install できなくなる問題をどうにかしないと、本題の保守作業に入ることもままならない。
どうしたものか。


Python や C/C++ の環境が必要?インターネット検索した感じだと、どうやら、node-sass のバイナリビルドのために、Python 2.x や C/C++ の環境が必要らしい。


で、macOS や Linux 系列だと、だいたい Python や gcc 入ってたりするので、すんなり node-sass のバイナリビルドが済んでしまう模様 (あくまでもネットで聞きかじった情報であるが)。


ところが Windows OS ではそのあたりでひっかかって、node-sass のバイナリビルドができずに npm install がコケて終わり、となるらしい。
たしかに自分の場合も、それっぽいログが表示されていた。
gyp ERR! stack Error: Can't find Python executable "python", you can set the PYTHON env variable.
gyp ERR! stack at PythonFinder.failNoPython (C:\Work\app1\node_modules\node-gyp\lib\configure.js:484:19)
gyp ERR! stack at PythonFinder.<anonymous> (C:\Work\app1\node_modules\node-gyp\lib\configure.js:509:16)
gyp ERR! stack at C:\Work\app1\node_modules\graceful-fs\polyfills.js:282:31
このような場合の定番の解決方法は、「npm install --global windows-build-tools」を実行して、Windows OS 上でもそれらビルドに必要な環境を npm でインストールしてしまえばよいようだ。




でもちょっと待て欲しい。


開発当時は、そのようなビルドツールが未インストールでも、すんなり npm install できていたのだ。
なんでだろう?


ビルド済みバイナリが GitHub 上にある!いろいろ調べていくうちにだんだんとわかってきた。


まず、node-sass の Node パッケージスクリプトは、常にはバイナリビルドを実行しようとはしない。


まずは node-sass の GitHub リポジトリに登録されている、ビルド済みバイナリのダウンロードを試みるようだ。


そして、ダウンロードしようとしている node-sass のビルド済みバイナリのファイル名の末尾には、対応している Node.js 実行環境の "モジュールバージョン" が含まれていた。


具体的には、Windows OS 用バイナリの場合、
「win32-x64-{ここにモジュールバージョン}_binding.node」
というファイル名になる。


node-sass の GitHub リポジトリで調べてみると、node-sass v.4.7.2 のビルド済みバイナリは、モジュールバージョン 57 まで登録がある。


しかし今回の開発環境 Node.js v.10.15.3 のモジュールバージョンは 64 であった。
d0079457_19274783.png

ということで、node-sass の GitHub リポジトリには登録のない、"v.4.7.2 で、且つモジュールバージョンが 64" であるビルド済みバイナリのダウンロードを試みて失敗 (HTTP 404 Not Found) となっていたようだ。

(下記はその証拠の npm install ログ表示。)
> node-sass@4.7.2 install C:\Work\app1\node_modules\node-sass
> node scripts/install.js


Downloading binary from https://github.com/sass/node-sass/releases/download/v4.7.2/win32-x64-64_binding.node
Cannot download "https://github.com/sass/node-sass/releases/download/v4.7.2/win32-x64-64_binding.node":


HTTP error 404 Not Found


なお、node-sass の Node パッケージスクリプトは次なる手段としてローカル環境でのバイナリビルドを開始する。


だがしかし自分の環境ではビルド用のツールの整備がなっていないので、これもコケてしまい結局エラーで終了、ということだったらしい。




さて、改めて node-sass の GitHub リポジトリの Release ページを見てみると、ちゃんとビルド済みバイナリが用意済みの、対応されている Node.js バージョンが明記されている。
d0079457_19293320.png

これを頼りに今回は node-sass v.4.9.0 以降を使用するよう packages.json を更新した。


その結果、自分のローカル環境にビルドツール類をインストールすることなく、無事 npm install が成功するようになった。



まとめということで、Windows OS 上で、必ずしも windows-build-tools Node パッケージの利用をはじめビルドツールの整備をしなくても、node-sass のバージョンを適切にアップデートしていけば、すんなり npm install を通すことはできる、という話。




なお、Node.js のいつのバージョンからかよく覚えていないが、最近の Node.js Windows 版のインストーラーでは、ビルドツールも一緒にインストールするかどうかの選択肢が出てきてるっぽい。
d0079457_19421919.png

この選択肢を有効にして Node.js をインストールしてある環境ならば、このような node-sass を npm install できない問題は起きないのだろうか。
検証したことはないが、気になるところである。

Azure Storage Emulator v.5.9 をインストールするも create database でエラーとなって動かない

$
0
0
Windows OS 上で C# による Azure Functions 開発を行なおうとしての話。


とある事情でまっさらの環境に Visual Studio 2019 をインストールするところから開始。


Visual Studio 2019 のインストーラにお任せで、Azure Storage Emulator (ver.5.9) や SQL Server 2017 Express および LocalDB も自動でインストールされるに任せた。


ということで早速にプロジェクトテンプレートからちゃっちゃっと C# で Azure Function プロジェクトを作り、Ctrl + F5 でビルド & 実行。


Azure Functions は背後で Azure Storage を使っている関係で、開発環境におけるローカル実行時は、Azure Storage Emulator がローカル環境で起動される。


...が、なんとここで想定外に、Azure Storage Emulator がエラーを吐いて起動に失敗してしまった。


エラーの原因は「データベースを作れません」!?コンソールには以下のとおり出力されていた。


Error: Cannot create database 'AzureStorageEmulatorDb59' : The database 'AzureStorageEmulatorDb59' does not exist. Supply a valid database name. To see available databases, use sys.databases..


Azure Storage Emulator がその永続化層に使用するために、SQL Server LocalDB に "AzureStorageEmulatorDb59" という名前のデータベースを作ろうとするも、何やら上手くいっていないようである。


とりあえずネットで検索してみると、StackOverflow に同様のトラブルでの質問投稿を発見した。


今までこんなトラブルに遭遇したこともなくて、Azure Storage Emulator を使い倒したこともなく知らなかったのだが、上記 StackOverflow のスレッドを見ると、Azure Storage Emulator には様々なコマンドライン引数指定ができるとのこと。



例えば、"/forceCreate" スイッチ付で、"init" を指定することで、データベースが壊れた場合でも再作成・最初期化できるとのこと。


> AzureStorageEmulator.exe init /forceCreate


まずは自分のこのケースでも上記コマンド実行を試してみた。
しかし残念ながら、自分のケースでは表示されるエラーメッセージは変らず。


ちゃんとエラーログを読もうさらに先の StackOverflow をスレッドを読み進めるに、まぁ、ちゃんとログ読もうぜ、という話らしくて、改めてちゃんと、Azure Storage Emulator のエラーログファイルを見てみることにした。


ちなみに Azure Storage Emulator のエラーログファイルは、下記フォルダに「error.log」というファイル名で保存されている。


%USERPROFILE%\AppData\Local\Microsoft\Microsoft SQL Server Local DB\Instances\MSSQLLocalDB


error.log ファイルの内容を "AzureStorageEmulatorDb59.mdf" で検索してみたところ、下記行を発見。


2019-06-09 11:24:13.15 spid52 CREATE FILE encountered operating system error 5(Access is denied.) while attempting to open or create the physical file 'C:\Users\jsakamotoAzureStorageEmulatorDb59.mdf'.


よーく見ると、作成しようとしているデータベースの物理パスがなんかおかしい。
'C:\Users\jsakamotoAzureStorageEmulatorDb....'


となっているが、あれ、ユーザーフォルダ名 (C:\Users\jsakamoto) とファイル名 (AzureStorageEmulatorDb59.mdf) との間のディレクトリ区切りが無くなってない?


正しくは
'C:\Users\jsakamoto\AzureStorageEmulatorDb....'
じゃないですか?



どうしてこうなった?


原因は SQL Server 側の不具合?先の StackOverflow のスレッドを見直していると、コメント欄に答えがあった。


Indeed this was the issue. I installed CU13 HotFix for SQL Server 2017 and it works now. – Andrii Mar 8 at 21:07

どうやら CU13 という SQL Server 用の Hotfix で本現象は解消できるらしい。
つまり SQL Server LocalDB 側の不具合だったということだろうか。


ということで上記コメントのリンクをたどりながら下記から CU13 Hotfix をダウンロードして適用すれば直るらしい。


https://www.microsoft.com/en-us/download/details.aspx?id=56128



他の回避策ただちょっと今回は事情があって、冒頭で "とある事情でまっさらな環境に..." とか書いたように、すぐにダウンロード & インストールできない、特殊な状況であった。
自分がこれまで今回のようなトラブルに遭遇しなかったのも、おそらくはインストールしたての環境ではなくて、ちゃんと最新の Update を適用済みの環境だったからかもしれない。


さておき、今の状況ではこれは詰んだかなー、と諦めようかと思っていたところ、別の回避策を発見。


先にも書いたが、Azure Storage Emulator にはいろいろなコマンドライン引数指定ができるのだが、その中に、永続化層に使用する SQL Server インスタンスを指定する方法があるとのこと。


具体的には、"/server" スイッチに続けて、使用する SQL Server インスタンスを指定して、"init" 動作を指定するらしい。




試しにと言うことで、この環境にインストール済みである、SQL Server 2017 Express のインスタンス (インスタンス名は "SQLEXPRESS") を使用するように、Azure Storage Emulator を構成・初期化してみることにした。


> AzureStorageEmulator.exe init /server .\SQLEXPRESS


すると、CU13 Hotfix が未適用でも、ちゃんと Azure Storage Emulator 用に、データベース AzureStorageEmulatorDb59 が SQL Server 2017 Express 上に作成された。


Windows Azure Storage Emulator 5.9.0.0 command line tool
Attempting to use server specified.
User specified an instance through /server or /sqlInstance options.
Probing SQL Instance: '.\SQLEXPRESS'.
Found SQL Instance .\SQLEXPRESS.
Creating database AzureStorageEmulatorDb59 on SQL instance '.\SQLEXPRESS'.


Granting database access to user MyPC\jsakamoto.
Database access for user MyPC\jsakamoto was granted.


Initialization successful. The storage emulator is now ready for use.
The storage emulator was successfully initialized and is ready to use.


もちろんその後の Azure Functions プログラムのローカル実行も難なく実行されるようになった。


もちろん CU13 Hotfix も有効!後日、CU13 Hotfix を適用後に、改めて "AzureStorageEmulator.exe init" を実行したところ、こちらも無事成功し、SQL Server LoacDB 上でも正常稼働するようになった。




まとめということで、 Azure Storage Emulator のコマンドライン引数指定、"init" コマンド実行に加えて付属のスイッチ群 "/forceCreate" "/server" などについて知っていると、何かトラブルの際に役立つかも知れない。


以上。



ASP.NET Core での単体テスト内で、オプションおよびサービスプロバイダを作成する

$
0
0
C# による ASP.NET Core プログラミングにおける、単体テストを実装していての話題。



ネットで検索すればすぐに見つけられるような内容かとも思うが、自分の為の備忘録を兼ねてここに記す。


さて、前述のとおりの単体テストを実装している際、IOption<T> をコンストラクタ引数に求めるオブジェクトをテスト対象とすることがある。


さらにたまーにではあるが、コンストラクタ引数に IServiceProvider を直に要求するオブジェクトを扱うこともある。


このようなケースで、どのように IOption<T> ならびに IServiceProvider をテストプログラム中でインスタンス化すればよいか、という話である。


IOption<T>ASP.NET Core におけるオプション構成は、通常は Startup クラス中の ConfigureServices メソッド内で初期化するのが通常だ (下記は MyOptions 型のオプションを appSettings.json などの構成情報から読み取って取得する例)。
public class Startup
{
private IConfiguration Configuration { get; }


public Startup(IConfiguration configuration)
{
Configuration = configuration;
}


public void ConfigureServices(IServiceCollection services)
{
services.Configure<MyOptions>(this.Configuration.GetSection("My"));
...
こうしておくと、MVC/API コントローラなどで MyOptions 型のオプション情報が必要な場合は、そのコンストラクタ引数として IOptions<MyOptions> 型の引数を用意しておけば、依存性注入機構によって引き渡されるようになっている (下記例)。
public class MyController : Controller
{
private IOptions<MyOptions> MyOptions { get; }


public MyController(IOptions<MyOptions> myOptions)
{
this.MyOptions = myOptions;
...
さてこのような IOption<T> を自前のテストコード中で直に生成するには、Microsoft.Extensions.Options 名前空間の Options クラスの Create<T>(T options) 静的メソッドを用いる。


この Create<T>(T options) 静的メソッドの引数に、任意の型 T のオブジェクトを渡せば、その戻り値で IOption<T> が返る (下記例)。
using Microsoft.Extensions.Options;
...
IOptions<MyOptions> myOptions = Options.Create(new MyOptions {...});
IServiceProviderIServiceProvider を自前のテストコード中で生成するには、Microsoft.Extensions.DependencyInjection 名前空間の、ServiceCollection クラスを使う。


手順としては、まずは ServiceCollection クラスをインスタンス化する。


次にこの ServiceCollection オブジェクトに対し、AddSingleton<T1,T2>() などをはじめとしたおなじみのメソッドを呼び出して、そのテストで必要となるサービスを登録しておく。
(すなわち、IServiceProvider を要求しているテスト対象のコード中では、その IServiceProvider に対して GetService<T1>() とか呼び出して、サービスを入手しているであろうため)


以上の準備が整ったら、ServiceCollection オブジェクトの BuildServiceProvider() メソッドを呼び出す。


そうすると、BuildServiceProvider() メソッドの戻り値として、IServiceProvider が返る。
var services = new ServiceCollection();
services.AddSingleton<IMyService, MyServiceMock>());
...
var serviceProvider = services.BuildServiceProvider();


使い終わったら、Dispose() メソッドを呼び出してリソース破棄しておくことを忘れずに。


以上!

Entity Framework Core でデータベースを読み書きする ASP.NET Core アプリ開発中に、発行される SQL 文字列を確認する

$
0
0
背景 - EFCore が出力する SQL 文を知りたいデータベースを読み書きする ASP.NET Core Web アプリ実装において、データベースアクセスライブラリの選択肢としてとりあえずオーソドックスな "Entity Framework Core" (以下 EFCore)。


EFCore およびその前身である "Entity Framework" の面白いところは、データベースクエリの結果をオブジェクトに "マップ" する O/R マッパーというだけでなく、C# コードとして記述されたラムダ式木に基づく SQL クエリビルダーでもあるという側面がある。


さてそんな EFCore であるが、だがしかし、期待していたのとは異なる SQL が発行される可能性も少なくない。


とくに EFCore の ver.2.x だと、GROUP JOIN ですら、簡素な SQL を発行して早々にデータベースから多量のレコードをオブジェクトとして読み込んでしまい、オンメモリで残りの処理を実行することもあるという。
なお、本ブログ執筆時点では未だリリースされていない、次バージョン EFCore 3.x では、このあたりの挙動が変更され、SQL に変換できないクエリは例外を発生させることになるらしい。
この辺りの話題は、下記などが参考になる。


そんなこともあり、とくにアプリ開発中は、EFCore が発行する SQL 文を確認したいところである。


なお、コンソールアプリなどで EFCore 使用時に、その発行する SQL を確認するには、その目的のロギング機構を組み立てて適用すればよい。
詳しくは下記などが参考になる。




実は開発時ログ出力には、既に含まれているさてさて、ASP.NET Core アプリ開発中は、一般的なプロジェクトテンプレートから起こしたプロジェクトであれば特に、すでにロギング機構が組み込まれており、且つ、開発時向けのログレベル構成が適用済みである。


下記は開発時用構成ファイルである appSettigs.Development.json 中のログレベル構成だ。
{

"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Information"
}
}
この構成だと、Visual Studio で開発中であれば、Visual Studio の出力ウィンドウに色々ログが流れているのが確認できる。
(※デバッガなし実行でも表示される)


また、dotnet CLI を使ってる場合も "dotnet run" などで実行すると、コンソールにログが流れるのを見ることができる。


実は既に、EFCore が出力する SQL 文は、このログの中に表示されているのだ。


よーく見ると、EFCore が出力する SQL 文が、ログの中に表示されているのを見つけることができる (下図、赤丸のところ)。
d0079457_20534430.png



大量のログ出力に埋もれてしまう...しかしである。


上図でもわかるように、このままでは、ブラウザ等からの要求ごとの ASP.NET Core 関連ログが大量に発生し、EFCore の SQL 文ログがそれらログに埋もれてしまう。
(ないしは、あっという間にスクロールアウトしてしまう)


実際問題として、この構成のままでは、大量のログの中から EFCore が出力する SQL 文を探し出して確認するという作業はあまり現実的ではない。


これは個人的な意見であるが、実のところ、ASP.NET Core 関連の要求処理時の情報ログは、あまり必要を感じない。
どのようなリクエストがブラウザから発生しているかを見るときは、どちらかといえば、ブラウザの開発者ツールで確認するだけで用が足りることがほとんどだ。


EFCore 以外のログレベルを調整ということで、appSettings.Development.json を改訂し、"Microsoft" カテゴリのログレベルを "Information (情報)" から "Warning (警告)" レベルに引き上げてしまうと良かろう。


そうすることで、ASP.NET Core 関連の大量の情報ログが表示されなくなる。


ただしそれだけだと、EFCore の SQL 文出力も同じく表示されなくなってしまう。


そこで、EFCore の SQL 出力だけはログ出力されるよう、"Microsoft.EntityFrameworkCore.Database.Command" カテゴリのログレベルを、再び "Information" としてオーバーライドするとよい。


最終的な appSettigs.Development.json は下記のような感じになる。
{

"Logging": {
"LogLevel": {
"Default": "Debug",
"System": "Information",
"Microsoft": "Warning",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}
これで下図のような感じで、EFCore の出力する SQL 文だけが都度流れるようになる。
d0079457_20535155.png

自分の開発スタイルとしてはこれくらいのログレベルでちょうどいい感じだ。


以上、すべてのケースでこのログレベル構成が最適とは言わないが、同じような境遇の人にこの情報が役に立てば幸いである。


補足データベースアクセスのためには、EFCore 以外にも選択肢はいろいろあるので、発行される SQL を完全に制御したい場合などは、無理に EFCore を使う必要もない。


下記など参考にされたし。





.NET Core 3.0 の単一実行可能ファイル生成を手なずける

$
0
0
.NET Core 3.0 の単一実行可能ファイル生成先日、公式リリースとなった .NET Core 3.0。
その新機能のひとつとして「単一ファイルの実行可能ファイル」を発行する機能がある。


これまでは、C# や VB、F# など .NET Core プラットフォームのプログラムをビルド・発行した場合、その最終成果物は、各種 .dll ファイル群がごちゃっと発行先フォルダに出力されるだけであった。


それが、.NET Core 3.0 の新機能「単一実行可能ファイル生成」を用いると、発行先フォルダには、たったひとつだけの実行可能ファイルが生成されるだけとなり、配布などの取り扱いが容易になる。
(なお、本機能の動作原理は、たとえて言うならば tar のような、どうやらある種の "アーカイブ" として必要 .dll ファイル群を単一実行可能ファイル内に収録しているようである)


もっとも、これまでも ILMerge や ILRepack などなど、.NET プラットフォームのバイナリを単一ファイルにまとめる手法やツールは存在していた。
とはいえ、.NET Core SDK 本体に類似の機能が標準で搭載されており、すぐに使える点は大変便利である。


ということで、Windows OS 上で Visual Studio 2019 を使った開発体制にて、「単一実行可能ファイル生成」を実際に試してみた。


まずは動作を確認まずは試しにということで、Visual Studio 2019 のプロジェクトテンプレートから .NET Core コンソールアプリケーション (今回は言語は C#) を生成。
"Hello World" を表示するだけの C# プログラムだ。


そして下記記事を参考に、.csproj ファイルに2行書き加えてみる。


<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
<PublishSingleFile>true</PublishSingleFile>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
</PropertyGroup>
</Project>


まずはここまででビルドして実行してみる。まぁ、普通に成功することを確認。


続けて、いよいよ発行である。
Visual Studio 2019 上の開発作業ということで、Visual Studio 上でフォルダへの発行プロファイルを作成し(下図)、発行を実行。
d0079457_18410626.png

すると、手順どおりにやっているのだから当たり前であるが、無事、発行先には .exe ファイルがひとつだけ生成された。(実行環境に 64bit Windows10 OS を指定しているので、生成される実行可能ファイルは拡張子 .exe の PE ファイルである。)



なお、たかが "Hello World" を表示するだけのコンソールアプリケーションであるが、その実行可能ファイルのサイズは 65.8MB になってしまった。
まぁ、ターゲットランタイムがポータブルでない、且つ、配置モードが自己完結 (Self-Contained) な発行成果物を、非圧縮でアーカイブしているだけのようなものなので、こんなものだろう。


他の OS 用にも発行できるなお、.NET Core といえばクロスプラットフォームである。


ということで、上記プロジェクトを、例えば 64bit Linux 用に単一実行可能ファイルを発行したい場合はどうするか?


これはもう、何も難しいことはない。
ただただ単純に、ターゲットランタイムとして "linux-64" を指定した発行プロファイルを作成して、その発行プロファイルで発行すれば良いだけである (下図)。
d0079457_18410666.png

だがしかし、そのプロジェクト、Windows 専用になってない?さてところで、上記までの手順だと、プロジェクトファイル内にターゲットランタイム指定が記載されているため、発行ではなくビルドの時点でも、64bit Windows10 用のバイナリを吐くように規定されてしまっている。


このようなプロジェクトファイルを、Linux や macOS など他の OS 上でビルドするとどうなるか?
まぁ、実はビルドは成功する。


だが、さすがに実行まではできない。
(※実は、Windows10 上の Windows Subsystem for Linux, いわゆる WSL の中だと実行できてしまったりもするのだが、それはまた別の話。)


かといって、ターゲットランタイムの指定をプロジェクトファイル内から記述削除し、ポータブル形式でてビルドしようとしても、今度はビルドが下記エラーで失敗するようになってしまう。


error NETSDK1097:
RuntimeIdentifier を指定せずにアプリケーションを単一ファイルに発行することはサポートされていません。
RuntimeIdentifier を指定するか、PublishSingleFile を false に設定してください。


".NET Core といえばクロスプラットフォームである" といっておきながら、これはいただけない。


どうしたものか?


発行プロファイルで指定する答えは
「単一実行可能ファイル生成の指定は、プロジェクトファイル (.csproj) に書かない。代わりに発行プロファイル (.pubxml) に書く。」
である。


Visual Studio で開発していての発行プロファイルとは、その実態は、Properties フォルダ以下に配置された拡張子 .pubxml のファイルであり (下図)、プロジェクトファイル (.csproj) に対する差分である。
d0079457_18410685.png

ということで、まずは .csproj ファイルからは、先ほど追加した単一実行可能ファイル生成およびターゲットランタイム指定の2行を削除して、元に戻す。

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
</Project>
次に、先に作成していた発行プロファイルを (Visual Studio の発行プロファイル編集 GUI 経由ではなく) エディタで開いて直接編集し、"<PublishSingleFile>true</PublishSingleFile>" の行を追加する。
(※ターゲットランタイム指定 ("<RuntimeIdentifier>~</RuntimeIdentifier>") は既に含まれているはず)
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
...
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<PublishSingleFile>true</PublishSingleFile> <!-- ← これを追記 -->
</PropertyGroup>
</Project>
以上で OK だ。


これでこのプロジェクトは、どの OS 上であっても、ターゲットランタイム = ポータブルとしてビルド・デバッグ実行可能となる。


その上で、上記発行プロファイルを使って発行すれば、64bit Windows 10 上で直接実行可能な (.NET Core Runtime の事前インストールも不要な) 単一ファイルの実行可能ファイルが手に入る。


もちろん、他の OS 用に発行プロファイルを変更したり発行プロファイルを追加したりしてもよい。


補足1 - dotnet CLI での開発時冒頭で示した公式ドキュメントを見ると、dotnet CLI を使う場合は、dotnet CLI 実行時のコマンドライン引数に、ターゲットランタイム指定と、単一実行可能ファイル生成指定を行なう方法が説明されている (下記例)。
> dotnet publish -r win10-x64 /p:PublishSingleFile=true
このように、やはりプロジェクトファイル内では単一実行可能ファイル生成の指定はせず、発行時に個別に単一実行可能ファイル生成の指定を行なう (この例で言えば、dotnet CLI のコマンドライン引数で指定する) のがよさそうである。


また、dotnet CLI を使った場合でも下記要領にて、今回説明したような発行プロファイルを用いた発行をすることもできる。
dotnet publish /p:PublishProfile=<ここに.pubxmlファイルのパス>
補足2 - Visual Studio 2019 の発行プロファイル編集 GUI がおかしいここまでわざと気づかぬフリをしてきたが、実はここまで説明してきた手順だと、本記事のスクリーンショットにもちょっと見えているが、Visual Studio 2019 の発行プロファイル編集 GUI の表示上は、配置モードが「フレームワーク依存」と表示されてしまっている。


しかし実際に発行される内容は、配置モード = 「自己完結」 (Self-Contained) な内容となっている。


実はこれ、ターゲットランタイム指定 (RuntimeIdentifier) が指定されていると、配置モード指定が明示的に設定されていない場合、どうやらビルドスクリプト上は配置モードのデフォルト値が、「フレームワーク依存」から「自己完結」に切り替わっているようなのである。


ただ、そのビルドスクリプトの仕様が Visual Studio 2019 の発行プロファイル編集 GUI には反映されていないようで、すなわち、Visual Studio 2019 の発行プロファイル編集 GUI では、配置モードの指定が明示的に設定されていない場合は「フレームワーク依存」と表示されているようだ。


ぐだぐだ書いたが、このような混乱を避けるには、配置モード指定を発行プロファイル内に明記してしまえばよい。
...
<SelfContained>true</SelfContained> <!-- ← これを明記 -->
</PropertyGroup>
</Project>
あるいは Visual Studio 2019 の発行プロファイル編集 GUI 上で、配置モードを明示的に「自己完結」に選択し直しておいても OK だ。
d0079457_18410609.png

このように常に配置モードの指定を明示的に設定しておくようにすれば、要らぬ混乱を避けられるであろう。



なお、この逆に、配置モードを「フレームワーク依存」として明示的に設定して単一実行可能ファイル生成指定で発行したらどうなるか?


そうすると、OS に別途 .NET Core ランタイムの事前インストールが必要となるが、サイズは極小となる、そのような単一実行可能ファイルが発行先に生成される。
.NET Core SDK がインストール済みであることが前提な開発者向けのツールなど、これはこれで需要あるかもしれない。




まとめ単一実行可能ファイル生成は、その機能の性質上、ターゲットランタイムの指定が必須である。


そのため、単一実行可能ファイル生成の指定は、プロジェクトファイルに記入するのではなく、発行プロファイルに記入しておくのが、自分的にはお勧めである。


EntityFramework Core 用にデバッグロガーを仕込む - EFCore v3 編

$
0
0
EntityFramework Core とは
EntityFramework Core (以下 EFCore) とは、C# などの .NET 言語で使用される、データベースアクセス用途のライブラリだ。


この EFCore を使用して、C# などのプログラムからデータベースなどを読み書きする際は、生の SQL 文文字列を記述することは多くなく、大抵の場合はデータベースの要素にマッピングされるクラスやそのプロパティなどの操作を介して行なう (※開発チームの指針に依るかも知れません)。


とはいえ、アプリケーション実装者が書くコード上から生 SQL 文文字列が消えただけで、最終的にデータベースシステムに送り込まれるのは SQL 文である。  
EFCore がその背後で SQL 文を生成してデータベースシステムとやりとりしてくれる訳だ。


EFCore は、プロジェクトの規模やチューニング要件次第ではあるものの、おそらくは多くの場面で便利に使える定番のライブラリであろう。


EFCore が吐く SQL 文を知りたい
そんな便利な EFCore ではあるが、その EFCore が生成・発行する SQL 文文字列を把握したい状況は往々にしてある。


とくに、少々入り組んだクエリを実装したときに、果たして、ちゃんと効率の良い SQL を EFCore が生成できているか、確認したいときなどだ。


ASP.NET Core による Web アプリサーバー側実装において、EFCore が発行する SQL 文を知る方法については、過去に自分のブログ記事としても書いた。




いっぽうで、ASP.NET Core とかではなく、コンソールアプリなど、任意の形態のプログラムにおいて、EFCore が吐く SQL 文を把握したいケースもあろうかと思う。


そのような場合は、ASP.NET Core 上でのときとは違い、自分でロギング周りを配線してやる必要があるらしい。


ネットで検索してみると、以下のような記事が見つかった。


他にもいくつか類似の記事が見つかるが、いずれも、ロガープロバイダを自分でインスタンス化 (new) して差し込むこととなる。



EFCore の ver.3 では使えない!
ところがである。


EFCore のバージョンが、ver.2.x までは上記記事の方法で目的を達成できるのだが、EFCore ver.3 になると、上記記事の実装ではコンパイルができなくなっていた。


例えば、デバッグ出力に SQL 文を出力するように実装してたコードを、EFCore v3 に昇格すると、以下のコンパイルエラーが発生する。


CS1729: 'DebugLoggerProvider' does not contain
a constructor that takes 1 arguments
このエラーメッセージからも察しが付くと思うが、厳密に言うと、EFCore のバージョンというよりも、EFCore v3 が依存している、ロギング周りのパッケージの v3 が、旧バージョン用の上記実装に対して破壊的変更があったのである。




具体的には、旧バージョンでは、ロガープロバイダ (ConsoleLoggerProvider や DebugLoggerProvider など) のコンストラクタには、ログの分類 (カテゴリ) やレベルに応じた絞り込みを行なうフィルタ関数を、そのコンストラクタ引数に受け取るオーバーロードバージョンが用意されていた。


それが ver.3 以降は、そのようなオーバーロードバージョンのコンストラクタが廃止されたのだ。
(ver.2.x 時代から、すでに Obsolete 属性でマーク、すなわち廃止予定であるとちゃんと示されていたようではある。)


EFCore ver.3 からは LoggerFactory.Create が便利
ではどうするのかというと、LoggerFactory.Create() を使ってロガーを構成するやり方が使えるらしい。


なお、下記公式ドキュメントを見ると、ロガーの構成方法はそれはもう色々ありすぎて習得するのも骨が折れる印象だ。


さておき、詳細は公式ドキュメントに譲るとして、ざっくり下記のようなコードにて、デバッグ出力に EFCore が発行する SQL 文が出力されるようになった。



using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
...


// 以下、LoggerFactory の構築方法が v.2.x 以前と異なる。
var loggerFactory = LoggerFactory.Create(builder =>
{
  builder
    .AddDebug()
    .AddFilter(
      category: DbLoggerCategory.Database.Command.Name,
      level: LogLevel.Information);
});


// ここから以降はこれまでと変わらず、構築した LoggerFactory を差し込むだけ。
var option = new DbContextOptionsBuilder<MyDbContext>()
  ...
  .UseLoggerFactory(loggerFactory)
  .Options;


var dbContext = new MyDbContext(option);
...

まとめ
他にも LoggerFilterOptions オブジェクトを使ってログフィルターを構成するとか、上記実装例にしても、AddFilter() 拡張メソッドには少なくとも 9 種のオーバーロードバージョンがあったりとか、様々なやり方があるようだ。


とりあえず自分が検証した範囲では、上記実装例が、この用途ではいちばんシンプルに書けたので、ここに紹介する次第。


以上、EFCore v3 で、発行される SQL 文をログ出力する方法であった。




File.ReadLines() で開いた Stream を確実に閉じる方法

$
0
0
巨大なテキストファイルでも1行ずつなら処理可能!
.NET Core 3.x 上の C# でのプログラミングにおける話。


改行で区切られた行指向のテキストファイルを、1行ずつ読み取っては何か処理をする、といった場合、自分がよくお世話になるのは、System.IO 名前空間の File クラスに備わっている ReadLines() 静的メソッドだ。


このメソッドに目的のテキストファイルのパスを渡して呼び出してやれば、そのテキストファイルの1行1行を文字列に読み込んで列挙する IEnumerabe<string> が返る。



あとはその返された IEnumerabe<string> を使って、foreach で列挙を回したり、LINQ で加工したりというように、1行1行の処理を実装すればよい。


この File.ReadLines() メソッドは、対象のファイルすべてをメモリに読み込むのではない。  
1行ずつ読み取っては結果の文字列をその都度返すので、未使用となった文字列は適宜ガベージコレクターに回収されるに任せられる。  
その結果、このメソッドは省メモリで動作し、大量の行から成る巨大なテキストファイルも一定の消費メモリだけで安定して処理が行える。


列挙を途中で抜けたらどうなるの?
そんな便利な File.ReadLines() であるが、あるとき、ちょっと困ったことになった。


あんまり遭遇することはないシチュエーションと思われるのだが、File.ReadLines() で行読み取りを開始したあと、最終行まで列挙せずに、途中で列挙を中断する要件に遭遇したのだ。


中断する実装自体は別に問題ではない。  
そうではなく心配なのは、列挙を中断したことで、File.ReadLines() が開いたテキストファイル読み取りの Stream が、開きっぱなしになってしまわないだろうか、という点だった。


もちろん、いずれガベージコレクターに回収されることで、最終的には Stream は閉じられる。  
しかしそれでは、Stream が閉じられるタイミングに予測性がない。


今回自分が遭遇したシチュエーションでは、列挙中断後、即座に Stream が閉じられている必要があったのだ。


ふーむどうしたものかと思いつつ、ちょっと試しに下記のような実験コードを書いて試してみた。
var lines = File.ReadLines(path);
foreach (var line in lines)
{
  Console.WriteLine($"line: {line}");
  break;
}
File.Delete(path);
すなわち、File.ReadLines() でテキストファイル中の行の列挙を開始し最初の1行をコンソールに表示するも、それだけで列挙を中断している。


で、その直後に当該ファイルの削除を試行している。


上記のように、foreach による列挙ループを break で中断したことによって、File.ReadLines() が開いた Stream が開きっぱなしになっているとしたら、続くファイル削除で IOException 例外が発生するはずだ。


実行したところ...
で、実行したところ...




なんと例外も発生せずに、正常にファイルの削除まで実行されたのだ。


自分の予測と違った振る舞いになったため、もう少し調べてみることに。


次は foreach で列挙ではなく、生の列挙子を自分で MoveNext() しながら while ループで列挙する実装を試してみた。


var lines = File.ReadLines(path);
var enumerator = lines.GetEnumerator();
while (enumerator.MoveNext())
{
  var line = enumerator.Current;
  Console.WriteLine($"line: {line}");
  break;
}
File.Delete(path);
するとこちらの実験コードでは、期待どおり (?) に、ファイルの削除で IOException 例外が発生した。  
例外メッセージはもちろん。他のプロセスで対象ファイルが使用中である、というものだ。


なにが起きているのかよくわからなくなってきたので、先の foreach 方式の実験コードをビルドした結果である .dll ファイルを ILSpy で見てみた。




すると、foreach って、using で囲ったコードに展開されているのであった...!  
(知らなかった...)


var lines = File.ReadLines(path);
using (var enumerator = lines.GetEnumerator())
{
  if (enumerator.MoveNext())
  {
    var line = enumerator.Current;
    Console.WriteLine("line: " + line);
  }
}
File.Delete(path);
とはいえ、IDisposable ではなく IEnumerator<string> を using するとは何事だろうか。


さらに ILSpy でコードをたどってようやくわかった。




File.ReadLines() が返す列挙オブジェクト、及び、それが返す列挙子であるが、ちゃんと IDisposable インターフェースを実装していたのだ。


言い換えると、File.ReadLines() が返す列挙オブジェクト、及び、それが返す列挙子を IDisposable インターフェースでキャストすると、ちゃんと IDisposable インターフェースとしての参照が手に入るのであった。
var lines = File.ReadLines(path);
var enumerator = lines.GetEnumerator();
var disposable = enumerator as IDisposable; // <- 非nullになる
それで列挙子を using 節で囲うことで、ちゃんと Stream を閉じる動作につながるのであった。


まとめ
これまで自分はまったく理解していなかったが、列挙子に適宜 IDisposable インターフェースを実装しつつ IEnumerator<T> として公開するのは、定番のイディオムなのであろう。


なので、C# の foreach は、上記のとおり、using で列挙子を囲いつつ MoveNext() で列挙を進めるというコードに展開される仕様なのであろうと理解した。


たぶん、公式を含め、ちゃんとそこかしこのドキュメントに明記されていることなのだろうが、お恥ずかしい、今の今までこの foreach の仕組みをわかっていなかった。


[2019/11/08 追記]
公式ドキュメントについてお知らせ頂いた。


教えて頂いた公式ドキュメントへのリンクはこちら。


たしかに、Dispose する仕様について明記されている。
[― 追記ここまで ―]


さておき、ということで、File.ReadLines() が返す列挙オブジェクトは、foreach で回したり、LINQ で使うぶんには、列挙が途中で中断しても、スコープを抜ける限りはちゃんと Dispose される、ということがわかった。


とはいえ、生の列挙子を自分で while ループで回すときなどは、もし列挙を中断する場合は自分で責任をもって Dispose する必要はある。  
そんな実装が必要になるケースはかなり希だとも思うが...。


なお、MoveNext() が false を返すまですべて列挙し終えた場合は、自前で Dispose しなくても、列挙完了時点で Dispose されるので安心して大丈夫だ。
(File.ReadLines() が返す列挙オブジェクトがそのように実装されているため)。



Viewing all 146 articles
Browse latest View live