データベースの選定と百万件のレコードの用意
この際、データベースは PostgreSQL でも MySQL でも何でもよいのだが、とりあえず自分が慣れ親しんでいる Microsoft SQL Server を使うことにした。百万件の実レコードを含む実テーブルを作るのはだるかったので、下記の SQL を使って、On the fly で百万件のレコードを生成することにした。
SELECT TOP 1000000
Z.Id,
CONVERT(nvarchar(36), NEWID()) AS Text1,
CONVERT(nvarchar(36), NEWID()) AS Text2,
CONVERT(nvarchar(36), NEWID()) AS Text3
FROM
(SELECT ROW_NUMBER() OVER (ORDER BY X.object_id) AS Id
FROM sys.all_objects X
CROSS JOIN sys.all_objects Y) Z
これを実行すると、64 bit 整数値の列 x 1 つと ("Id" 列)、36文字の文字列型の列 x 3 列 ("Text1", "Text2", "Text3") をもつレコードが百万件ほど返る。雑な計算で、正味のデータ量だけも 110 MB 以上になるデータ量だ。
public class MyRecord
{
public Int64 Id { get; set; }
public string? Text1 { get; set; }
public string? Text2 { get; set; }
public string? Text3 { get; set; }
}
あとは接続のためのデータベースコンテキストクラスを用意して (下記)、
using Microsoft.EntityFrameworkCore;
public class MyDbContext : DbContext
{
public DbSet<MyRecord> MyRecords => Set<MyRecord>();
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
}
これらを使用して Program.cs を以下のように実装した。
using Microsoft.EntityFrameworkCore;
var option = new DbContextOptionsBuilder<MyDbContext>()
.UseSqlServer("Server=(省略)")
.Options;
using var dbContext = new MyDbContext(option);
var sql = @"(先に掲載の SQL)";
var records = dbContext.MyRecords.FromSqlRaw(sql).AsNoTracking();
foreach (var record in records)
{
Console.WriteLine($"{record.Id} - {record.Text1}, {record.Text2}, {record.Text3}");
とある要件で、ASP.NET Core Web アプリケーションを起動し、HTTP リッスン状態になるまでを待機して、それからその ASP.NET Core Web アプリに HTTP 要求を送信して応答を得るという、そのようなコンソールアプリケーションを作成する需要が発生した。
もちろん、.NET の標準ライブラリにある System.Diagnostics.Process クラスを使えば実装できる。Process クラスに対し、起動したい実行可能ファイルパスやコマンドライン引数を指定するのに加えて、標準出力や標準エラー出力のリダイレクトを指定し、および Process クラスから発火される、標準出力や標準エラー出力への書き込み発生のイベントをハンドルすればよい。
ASP.NET Core Web アプリケーションであれば、HTTP リッスン状態になると「Now listening on: https://localhost:7241」といったテキストが標準出力への書き込まれるので (下図例)、これを検知すればよい。
具体的な実装例は以下のとおり。
using System.Diagnostics;
// ASP.NET Core Web アプリプロジェクトを "dotnet run"
// コマンドで起動する Process オブジェクトを準備。
// その際、標準出力のリダイレクトを指定。
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = "run",
WorkingDirectory = @"...",
RedirectStandardOutput = true
}
};
// "Now listening on..." の出力を待ち合わせるための
// タスク完了ソースを用意
var tcs = new TaskCompletionSource();
// 標準出力発生のイベントをハンドルし、"Now listening on..." が出力
// されたら、タスクを完了させる
process.OutputDataReceived += (_, e) =>
{
var output = e.Data?.Trim() ?? string.Empty;
if (output.StartsWith("Now listening on: http://localhost:"))
{
tcs.TrySetResult();
}
};
// ここまで来たら、起動した ASP.NET Core Web アプリは、
// HTTP リッスン開始している
Console.WriteLine("DETECT START LISTENING!");
ちゃんと実装するとなると大変!ただ、上記実装例は、もう既にずいぶんと冗長な感じがすることに加え、標準エラー出力については実装していない。また、正常に動作しているぶんにはよいが、起動した ASP.NET Core Web アプリケーションプロセスが何らかのエラーを発生していた場合に、コンソールには標準出力や標準エラー出力の内容は表示されないため (出力をリダイレクトしてイベントで拾うようになってしまっているので)、何かあったときのトラブルシューティングが困難極める。タイムアウトの仕組みもないので、"Now listening on:..." の出力がされないと永遠に await し続けてしまい、エラーの発生に気づくことすらできなさそうだ。その他にも、このコンソールアプリケーション自体が例外を発生した時に、起動した ASP.NET Core Web アプリケーションプロセスをどう始末するのか、といったことも考慮が必要だろう。
それら考慮点をすべて網羅していくと、結構なコード量に膨れ上がることになる。
Process クラスの強化版的なライブラリ世の中には、こういった需要に応えるような、Process クラスの強化版的なライブラリが、無償利用可能な NuGet パッケージとして公開されていたりする。それらライブラリのうち、自分の中での推しのひとつは、ProcessX というライブラリだ (下記リンク先)。
だが今回は実験方式で、 ASP.NET Core Minimal API による HTTP Web API サーバーと、その API サーバーに HttpClient を使って HTTP 要求を送信するコンソールアプリケーションとを C# でそれぞれ実装して、実際に実行して動作を観察してみた。
API サーバー側の C# ソースコードは以下。"/hello" の URL パスに HTTP GET 要求を送ると、"World!" の文字列が返却される。
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.MapGet("/hello", () => "World!");
app.Run();
コンソールアプリケーション側の C# ソースコードは以下。前述の API サーバーの "/hello" へ HTTP GET 要求で文字列取得を試みるコードだ。以下では HttpClient の Timeout には 3 秒を設定してある。
using var httpClient = new HttpClient {
Timeout = TimeSpan.FromSeconds(3),
BaseAddress = new Uri("http://localhost:****")
};
var message = await httpClient.GetStringAsync("hello");
Console.WriteLine($"message: [{message}]");
TCP 接続が確立できないとき接続先のホスト上でこの API サーバーが起動していない場合や、指定した IP アドレスではホストが TCP ネットワーク上に存在しない場合など、そもそも TCP 接続が確立しない場合を確認してみた。
この場合、HttpClient の Timeout プロパティに指定した時間が経過したのち、TaskCanceledException 例外が発生した。
Unhandled exception. System.Threading.Tasks.TaskCanceledException:
The request was canceled due to the configured HttpClient.Timeout of 3 seconds elapsing.
---> System.TimeoutException: A task was canceled.
---> System.Threading.Tasks.TaskCanceledException: A task was canceled.
Unhandled exception. System.Threading.Tasks.TaskCanceledException:
The request was canceled due to the configured HttpClient.Timeout of 3 seconds elapsing.
---> System.TimeoutException: The operation was canceled.
---> System.Threading.Tasks.TaskCanceledException: The operation was canceled.
---> System.IO.IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request.
TCP 接続は成功し、最初の応答もすぐ返るが、すべての応答に時間がかかるときさらに API サーバー側実装を以下のように変え、最初の応答はすぐに返るが、すべての応答を実施しきるまでに数百ミリ秒の待機を挟みながら、だらだら応答を続けるようにしてみる。
Unhandled exception. System.Threading.Tasks.TaskCanceledException:
The request was canceled due to the configured HttpClient.Timeout of 3 seconds elapsing.
---> System.TimeoutException: The operation was canceled.
---> System.Threading.Tasks.TaskCanceledException: The operation was canceled.
---> System.IO.IOException: Unable to read data from the transport connection: The I/O operation has been aborted because of either a thread exit or an application request.
スクレイピングして iCal で公開する Web API を自作
...という背景事情があったので、今回、.NET Conf 公式サイトの Agenda ページの HTML をスクレイピングし、記載されているセッションスケジュールを iCal 形式に変換して返す、そのような Web API サイトをインターネット上に公開した (下記リンク先)。
すごく難しいことをしている訳ではないので、様々な言語・フレームワーク・処理系で同様のものを実装できると思うが、今回は自分が得意な C# と ASP.NET Core を用い、Minimal API 形式で実装した。実装した Web API アプリケーションは、Microsoft のパブリッククラウドサービスである Azure App Service に配置して公開した。気が早いかもしれないが、前述のとおり今日現在では公式リリース前の .NET 8 のリリース候補第2版を使って実装・稼働させている。
この時刻表現を C# 上では最終的に UTC 日時に変換する必要があったので、そのために、"PST" というタイムゾーンの「略称」から、System.TimeZoneInfo を取得する必要がある。しかし TimeZoneInfo.FindSystemTimeZoneById("PST") を実行したところ、該当するタイムゾーンは存在しないという例外になってしまった。興味深いことに、ASP.NET Core Web アプリケーションプロジェクトではなく、素の .NET コンソールアプリケーションでこれを実行すると、ちゃんと期待どおりの TimeZoneInfo が返ってくる。プロジェクト形式の違いでどうしてこのような違いが生まれるのか理解できていないが、とりあえず今回は深追いするのはやめておいた。その代わりに、TimeZoneNames というライブラリを使わせてもらうことにした。
このライブラリは、TimeZoneInfo.GetSystemTimeZones() が返すタイムゾーンの ID から、そのタイムゾーンの略称を取得することができる。タイムゾーンの略称から ID を直接表引きできれば良かったのだが、どうもそのような機能提供はなかったようなので、自前のコードで自分でそのような変換辞書を構築することにした。
この辞書で "PST" をキーに値を取得すると "Pacific Standard Time" という正式なタイムゾーン ID を取得できる、という寸法だ。この変換辞書を介することで、"PST" というタイムゾーンの略称から TimeZoneInfo を取得し、最終的に UTC 日時への換算を実装することができた。
HTML のスクレイピングで実装したので、.NET Conf 公式サイトの Agenda ページの構造が変わってしまうとすぐに破綻してしまう仕組みなので、できれば公式のほうで iCal 形式での公開をサポートしてくれるに越したことはない。しかしそうはいっても、今時点でないものはないので、当面はこの Web API サイトが役に立ってくれるはずだ。
]]>Git のローカルリポジトリを Zip して送ったら、解凍先で Git の履歴を閲覧できないhttp://devadjust.exblog.jp/29695786/2023-09-17T22:04:00+09:002023-09-17T22:06:33+09:002023-09-17T22:04:47+09:00developer-adjustその他IT系
説明が面倒なのでどのような事情か詳細を省くが、どのようにソースコードを変更していったかの履歴情報込みで、そのソースコード一式をメール添付で送信する必要が発生した。Git は分散型バージョン管理システムということで、ローカルの履歴情報はすべて .git サブフォルダ以下に収録されている。なので、その .git サブフォルダごと Zip 圧縮してメールに添付すれば、目的を達成することができる。
ただ今回は、バージョン管理というよりは、コードをどのように変更していったのかその履歴から学んでもらうような学習用コンテンツだったので、rebase、reset、cherrypick、squash を駆使して、履歴を整理した。そのためこのローカルリポジトリには、ブランチからは到達不可能なコミットとそのオブジェクトが含まれていることになる。せっかくなので、そのような不要な履歴情報はすべてきれいに削除された状態で Zip して送ろうと考え、以下のように reflog の全削除と prune による到達不可能なコミットの削除、および再圧縮を実施。
Windows のエクスプローラーによる Zip 圧縮機能の振る舞い
Windows のエクスプローラーでは空フォルダは Zip に圧縮できないという仕様であるのはうっすら知ってはいた。実際、一階層の空フォルダを Windows のエクスプローラーで Zip 圧縮しようとすると、「空のフォルダは Zip 圧縮できない」というメッセージが表示される。ところが今回のように、「サブフォルダの中に、空のサブフォルダがある」状態だと、このメッセージが表示されずにさくっと Zip 圧縮処理が完了してしまうようなのだ。そのため、そもそも .git/refs サブフォルダの必要性がわかっていたとしても、気づかずにその Zip ファイルをそのまま送ってしまったかもしれない。
そこで "Microsoft.Extensions.Configuration.UserSecrets" NuGet パッケージの参照バージョンを現時点での最新バージョンの 7.0 に更新した。その上で動作確認のため、ひとまずこの開発環境で Function を実行してみた。
すると予期しなかったことに、下記実行時エラーが発生した。
Could not load file or assembly 'Microsoft.Extensions.Configuration.Abstractions, Version=7.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. The system cannot find the file specified.
]]>Microsoft Azure 上に Windows 11 Pro 22H2 の仮想マシンを構築してリモートデスクトップ接続するhttp://devadjust.exblog.jp/29565972/2023-05-06T15:56:00+09:002023-05-07T21:03:14+09:002023-05-05T21:06:02+09:00developer-adjustWindows
Windows 用デスクトップアプリケーション開発における話。
では、自分の PC 上で稼働しているその Windows 仮想マシンを、他のメンバーからリモート越しにいじれるようにリモートデスクトップ接続をインターネット上に公開するのはどうだろうか。これはこれで、自分は使用しないのに、他のメンバーが自 PC 上の仮想マシンを使用するというために、自 PC の CPU やメモリを持って行かれるのも微妙な気持になる。さらには、共有したいメンバーとタイムゾーンが一致しない場合は 24 時間自 PC を起動しっぱなしにしなければならないなど、これはこれでしんどい。
だったら、ローカル PC 上に仮想マシンを作るんじゃなくて、クラウドサービス上に Windows 仮想マシンを作って、そこにリモートデスクトップ接続して、使い合えばいいんじゃないかな、という結論に至った。
そこで今回は、クラウドサービスのひとつである Microsoft Azure 上に、Windows 11 Pro 22H2 の仮想マシンを新規構築し、リモートデスクトップで接続して利用できるようにする手順を記す。前提条件としては、すでに Microsoft Azure のサブスクリプションは契約済みであるものとして話を進める。
仮想マシンの作成
まずは Web ブラウザで Microsoft Azure のポータルサイト ( https://portal.azure.com/ ) を開く。人によって表示される画面は様々だと思うが、ここでは Azure ポータルの「ホーム」画面から、「リソースの作成」をクリックして、仮想マシンの新規作成を開始することにする。
管理者アカウントのユーザー名とパスワードを指定する。
これから構築する Windows 仮想マシン上に、ここで指定したユーザー名・パスワードのローカルアカウントが用意される。すなわち、この仮想マシン構築後、リモートデスクトップで接続する際に、このユーザー名とパスワードで認証することになる。
受信ポートの規則
これから構築する仮想マシンでどの TCP/IP ポートを受信用に解放するのかを指定する。
今回の要件では Windows リモートデスクトップで仮想マシンに接続するわけだが、それは Azure 上の仮想マシン利用におけるごく一般的なシナリオということで、既定でリモートデスクトッププロトコル (RDP) 用のポートが許可されている。よって、ここは既定のままとする。
ライセンス
しかるべきライセンスを保有していないことには当然 Windows OS は利用できない。
自分のシナリオでは共有したいメンバー同士含め、Visual Studio サブスクリプションを契約しており、また、開発・テストの用途での利用であるため、問題ない。ということでチェックを ON にする。
ディスク以上で必要最低限の指定が完了したので、「確認および作成」ボタンをクリックして仮想マシンの構築を実行することが可能だ。
しかしここでは、他の設定カテゴリの内容をもう少し見ておきたく、「次: ディスク >」ボタンをクリックして見てみる。
VM ディスクの暗号化 - ホストでの暗号化
自分のサブスクリプションでは利用不可なので、そのままスルー。
OS ディスク - OS ディスクの種類
既定で「Premium SSD (ローカル冗長ストレージ)」が選択されているが、ドロップダウンリストを開くと他にも Standard SSD や Standard HDD などが選べたりする。しかし今回は検証作業の用途であり、リモートデスクトップで接続していろいろいじるための仮想マシンを構築しようとしているので、反応の遅い HDD を選ぶ理由はないので、このまま Premium SSD が選択された状態で進める。
この仮想マシンの使用用途がなくなって仮想マシンを削除したときに、この仮想マシンに取り付けられていた OS ディスクも同時に削除されるようにするオプションらしい。前述のとおり、リソースグループの枠組みで、仮想マシン本体とそれに取り付け・接続される関連サービスをいっきに削除することもできると思うが、なんとなく今回はチェックを ON に変更してみた。
OS disk のところにも同じようなオプションがあったが、同じようなものと理解。必須ではないが後日この仮想マシンが不要になって削除する際に、関連するネットワークインターフェース類の Azure サービスも削除してくれてよいと考え (繰り返しになるが、今回は不具合の調査対応のための、単機の Windows 仮想マシン構築であるため、Azure 上で複数の仮想マシンを構築してそれらをネットワークで結んで連携させている、ということはないため)、このチェックを ON にしてみた。
ネットワークインターフェース - 高速ネットワークを有効にする
既定ではこのチェックは ON になっていたのだが、OFF にするとどういう影響があるのか、自分はよくは理解していない。こちらの資料を見ると「この機能は、仮想ネットワーク間で通信する場合やオンプレミスに接続する場合の待機時間への影響を最小限に抑えます」とあるので、今回の様に仮想マシン単機にインターネット越しにリモートデスクトップして使う形態だと、このオプションは ON でも OFF でも何の影響もないのかな、とも読める。とりあえず既定のまま進める。
負荷分散
今回は検証作業用途の仮想マシンを単機で利用するだけである。本番運用の Web サーバーを複数マシンで構成するとかではないので、ロードバランサーなどの負荷分散機能の利用は必要ない。そのため、既定の「なし」が選択された状態で先に進む。
管理続けて「次: 管理 >」ボタンをクリックして「管理」カテゴリの設定内容を見てみる。
Microsoft Defender for Cloud
とくに設定できる項目はない。
ID - システム割り当てマネージド ID の有効化
自分はこのチェックの効能を理解していない。とりあえず既定のチェック OFF のまま進める。
Azure AD - Azure AD でログインする
このチェックを ON にすれば、Azure AD (Active Directory) に基づく認証で仮想マシンにサインインできるということかと思う。今回は仮想マシンのローカルアカウントでサインインできればじゅうぶんなので、既定のチェック OFF のまま進める。
ちなみに今回構築の仮想マシンは1時間あたり約 16 円くらいのサイズの仮想マシンなのであるが、24時間 x 30 日間起動しっぱなしだと、16 x 24 x 30 = ざっくり一ヶ月 1 万円少々が必要となる。しかし今回の用途であれば、1日のうち 4 時間くらいしか使わず、だいたい 2 営業週くらいが利用のピークなので、16円 x 1日4時間 x 10日間 = ざっくり 700円弱程度が実際の課金となる見込みだ。
ホットパッチとは、仮想マシンの OS を再起動することなく、OS の更新プログラムを適用する仕組みらしい。ただしこの機能は Windows Server 2022 Datacenter Azure Edition でのみ有効とのことなので (出典はこちら)、どのみち選択変更はできないのでこのままスルー。
ゲスト OS の更新プログラム - パッチオーケストレーションオプション
OS の更新プログラムをどのように適用するのか (あるいはしないのか) を選択するオプションらしい。本番運用に利用するような仮想マシンだと、予期せずに OS の更新プログラムの適用が走り再起動とかされてはたまらないので、このようなオプションがあるのだろう。今回はそこまでの用途はないので、「OS による自動処理 (Windows の自動更新)」を選択しておいた。
仮想マシンへのデスクトップ接続さて、ではこうして構築した Windows 11 の仮想マシンに、インターネット経由で Windows リモートデスクトップで接続するにはどうしたらよいか。
そのためにはまず、Azure ポータル上で、その仮想マシンの構成画面を開いて見る。すると、その画面にはその仮想マシンに割り当てられた「パブリック IP アドレス」 (グローバルな IPv4 アドレス) が表示されている。なので、その IP アドレスを指定して、「リモートデスクトップ接続」アプリケーションから接続することができる。
これで、はい、おめでとうございます、Azure 上に構築した Windows 11 仮想マシンに、手元の自 PC から Windows リモートデスクトップを使って接続できるようになった。
なお、このようにして構築される Windows 仮想マシンの Windows OS の言語設定は英語となっているので、そこは悪しからず。上図のとおり、仮想マシン構築後の初回の接続では、その Windows のウェルカム画面が表示されるが、そもそもここからして英語となっているので、頑張って対応する必要がる。なおもちろん、Windows の「設定」("Settings") アプリから、OS の言語設定に日本語を追加して、日本語版 Windows の表示に変更することができる。
あとこれは余談だが、自分はこのようなリモートデスクトップ接続のクライアントとして、Windows に標準で付属している (上記でも使用した) 「リモートデスクトップ接続」アプリではなく、Microsoft Store から別途インストールする「Microsoft リモートデスクトップ」アプリを利用している (下記リンク先)。
「Microoft リモートデスクトップ」アプリだと、このアプリのウィンドウサイズを変更時に、接続先のリモートの Windows の解像度を、ウィンドウサイズぴったりに自動で変更してくれるのが気に入って使っている。ご参考までに。
]]>日本語がメインの HTML 文書を作成中、英数字の単語の前後に半角空白は入れるべきか・入れないべきか? 2023年再びhttp://devadjust.exblog.jp/29549895/2023-04-17T12:47:00+09:002023-05-08T21:05:17+09:002023-04-17T12:47:44+09:00developer-adjust雑記
つまり、そのような空間を設ける処理は組み版システムやワードプロセッサーがやるべき処理であり (実際、Microsoft Word などのワードプロセッサーでは、英数字の単語の前後に空間が設けられる設定が既定で On になっている)、故に、筆者がちまちまそのような空間の目的で手作業で半角空白文字を入れるのはおかしい、という主張だ。別の言い方をすれば「段落を右寄せにしたいときに、空白文字列を行頭に並べるんじゃねぇ!」というのと同じ話、ということだ。
なるほど、この点は自分も非常に強く共感できる。実際、自分も、Microsoft Word などのワードプロセッサーで文書を作成するときは、いちいち手作業で英数字の単語の前後に半角空白を入れるようなことはせず、Microsoft Word がレイアウトしてくれるのに任せるし、細かい調整をしたいときも、Microsoft Word のレイアウトの設定を調整している。
Big Tech 各社や Web 技術系の Web サイトはどうなのよ?
理屈はともかく、実績としてはどうなのだろうか。ということで、軽くであるが、Big Tech 各社や各種 Web 技術関連の、日本語の Web サイトを調べてみた。結果は以下のとおり。
英数単語の前後に空白入れてる
Google Store (https://store.google.com/jp/)Mozilla Developer Network (https://developer.mozilla.org/ja/docs/Web)Rect の日本語ドキュメントサイト (https://ja.reactjs.org/tutorial/tutorial.html)Vue の日本語ドキュメントサイト (https://v2.ja.vuejs.org/v2/guide/)Microsoft の日本語技術文書サイト (https://learn.microsoft.com/ja-jp/docs/)
英数単語の前後に空白入れてない
Apple の公式サイト (https://www.apple.com/jp/mac/)Meta の公式サイト (https://about.meta.com/ja/)Angular の日本語ドキュメント (https://angular.jp/guide/what-is-angular) (でもたまに空白入っている文章があったりする)
あくまでも思いついたがままの自分調べなので、空白入れる派/入れない派のどちらの勢力が強いかは、その正確なところは明らかではない。とはいえ、どうやら HTML 文書作成において英数字の単語の前後に空白を入れるのは、決して珍しい訳ではないのではないか、という予想・仮説はじゅうぶん成り立つのではないかと考えている。
結論 - HTML 文書では、英数字の単語の前後に空白入れてもいいんじゃないかな!
ということで、HTML 文書の作成時、英数字の単語の前後に空白を入れることを勧めもしないけれども、さりとて、自分が半角空白を入れていることに対し「気持ち悪い」とか「時間の無駄」とかは言って欲しくないなぁ... と思った次第。
using OpenQA.Selenium;
using var driver = new OpenQA.Selenium.Chrome.ChromeDriver();
driver.Navigate().GoToUrl("https://www.bing.com/");
で、このプログラムをビルドして実行すると、よくあることとして、以下のエラーに遭遇することがある。
The path to the driver executable must be set by the webdriver.chrome.driver
system property; for more information, see https://chromedriver.chromium.org/. The latest version can be
downloaded from https://chromedriver.chromium.org/downloads
その処理が遅くなる現象を自分の手元の開発環境でも再現できる場合は、自分は開発環境に Visual Studio を使っているため、当該 .NET アプリケーションのソースコード/プロジェクトを Visual Studio で開いてデバッグ実行すれば、Visual Studio のデバッガ機能により、どこのメソッドがどの程度の割合、CPU 時間を消費しているのかを記録・測定、グラフィカルユーザーインターフェースで探索することができる。
ところが今回の件は、どうにも自分の手元で再現ができず、どうも実行時の環境で発生している様子。しかも自分はその現象が発生している現地・現場には行けず、かつ、なんらかの PC 画面共有ツールを活用してのリモート保守も不可能、という制約付き。
ただし代わりに、その問題現象に遭遇しているユーザーにお願いすれば、こちらから送ったデバッグビルドした版の .NET アプリケーションをインストール・実行してくれたり、スクリーンショットを撮って送ってくれたりなど、ターミナル (PowerShell) での作業を含めて、それなりの PC 操作は実施してくれる。
解決できた
こうして .nettrace を手に入れたら、Visual Studio をインストール済みの開発環境 Windows PC 上で適当な作業フォルダに保存し、この .nettrace ファイルをエクスプローラー上でダブルクリックする。すると、拡張子関連付けにより Visual Studio が起動してこの .nettrace ファイルを開いてくれる。あとは Visual Studio のグラフィカルユーザーインターフェースを介して、どこのメソッドでどれだけ CPU 時間を消費しているのかを探索することができる。
こうしてどうにかこうにか CPU 時間を消費しているソースコード上の箇所を特定することができ、そこから「ああー、そういうこと!?」という環境依存であった問題原因を特定、対処して改善された新バージョンをリリースしてお届けすることができた。
using var smtpClient = new SmtpClient( host: "smtp.sendgrid.com", port: 587)
{
Credentials = new NetworkCredential(
userName: "apikey",
password: "SG....")
};
using var smtpClient = new SmtpClient(...)
{
Credentials = new NetworkCredential(...), EnableSsl = true // 👈 追加!
};
どうせ開発用のキーだしそうそう心配したものではないと思いつつ、そうは言っても実際にインターネット経由で API キーを含む通信を平文で流してしまったのはそうなので、念のため API キーも再発行を済ませた。
その上で今一度メール送信を試験実行。
すると予期しないことに、実行時例外が発生してしまった。例外メッセージは下記のとおり。
Unhandled exception.
System.Security.Authentication.AuthenticationException: The remote
certificate is invalid according to the validation procedure:
RemoteCertificateNameMismatch
at CompleteHandshake(SslAuthenticationOptions)
at SslStream.ForceAuthenticationAsync[TIOAdapter](Boolean, Byte[], CancellationToken)
at SslStream.AuthenticateAsClient(SslClientAuthenticationOptions
sslClientAuthenticationOptions)
at TlsStream.AuthenticateAsClient()
at SmtpConnection.GetConnection(String host, Int32 port)
at SmtpTransport.GetConnection(String host, Int32 port)
at SmtpClient.Send(MailMessage message)
at Program.<Main>$(String[] args)
]]>Azure App Service に配置した ASP.NET Core アプリで、App Service 上に設定した接続文字列を GetConnectionString で取得できない?http://devadjust.exblog.jp/29454749/2022-12-25T21:45:00+09:002022-12-25T21:52:03+09:002022-12-25T21:45:06+09:00developer-adjust.NET
先日、Qiita 上の下記投稿が目に留まった。
上記投稿内容によると、Azure App Service 上に配置した ASP.NET Core アプリ (Blazor Server) で、App Service 上で設定した接続文字列を GetConnectionString メソッドで取得できないというのだ。
自分はこれを読んで、えぇ、そんなことあるのだろうか? と非常に疑問を抱いた。というのも、自分は普段から Azure App Service 上に Blazor Server を含む各種 ASP.NET Core アプリを配置しており、App Service 上で設定した接続文字列も普通に GetConnectionString メソッドで取得できていたからだ。
ただし、確かに下記記事にあるとおり、Azure App Service 上で設定した接続文字列は環境変数として登録され、その環境変数を通して GetConnectionString メソッドで取得できるのだが、その環境変数名には、App Service 上での接続文字列設定時に選択した「種類 (Type)」に応じた "プレフィクス" が付く、という話はあるにはある。
そのように App Service 上で設定したデータベース接続文字列を含む環境変数名には上記プレフィクスが付くのに、なぜ GetConnectionString ではそのプレフィクスを気にせずに接続文字列を取得できていたのか、そういえばよく考えたこともなかった。
- MySQL
- SQLServer
- SQLAzure
- PostgreSQL
- Custom
そこで、上記 5 種の接続文字列設定が、ちゃんと ASP.NET Core アプリから取得できるか試してみることにした。
Blazor Server アプリを書いて試してみた
動作確認用に、接続文字列と環境変数とを一覧表示する Blazor Server アプリを書いて Azure App Service 上に配置する。
そうしたところ、なんと、それら App Service 上の接続文字列設定に関するプレフィクスがソースコード上にハードコードされて、これらプレフィクスが付いた環境変数を特別に処理していたのだ。これが、App Service 上での接続文字列設定が環境変数上はその「種類 (Type)」に応じたプレフィクスが付くにも関わらず、GetConnectionString メソッドでいずれのプレフィクスが付いていても接続文字列を取得できる仕掛けだった。