銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

TeamsとSlackとVisual Studio Live ShareによるASP.NET Coreオンラインハンズオンの参加準備

この記事は、今度予定しているC# Tokyoでのオンラインハンズオンの準備を説明しています。C# TokyoではSlackをコミュニケーションの中心にしているので、SlackのチャットとTeamsのオンライン会議を分けていますが、ほかのケースではどちらかに統一したりするのもいいでしょう。

C# TokyoのSlackに参加する

質疑応答などテキストベースのやりとりはSlackを使うのでC# TokyoのSlackに参加してください。こちらのページの最下部にリンクがあります*1

csharp-tokyo.connpass.com

Teams会議にゲストアクセスする準備をする

Teams会議にはWebブラウザもしくはアプリから参加できます。参加方法についてわかりやすいリンクがあったのでご紹介します。

www.slideshare.net

今回はハンズオンなので、Teams会議はスマホから視聴して、PCはコーディングに使う、といった使い分けをしてもいいかもしれません。

Live Shareの環境を準備する

Live ShareはVisual Studio 2019、Visual Studio Code、さらにプレビューでWebブラウザで参加できます。ゲスト参加の場合は認証いりませんが、セッションをリクエストする場合は必要なのでMicrosoftアカウント、もしくはGitHubアカウントによる認証をお願いします。

visualstudio.microsoft.com

Visual Studioの場合

Live Share機能はVisual Studio 2019に含まれているはずです。コードなしでVisual Studioを開き、Live Shareビューを表示されていない場合、表示メニューから表示します。

f:id:tanaka733:20200419110121p:plain

次に、https://prod.liveshare.vsengsaas.visualstudio.com/join?885A5D488918739D76530D97E67263AC52AB といったURLを共有しますので、セッションに参加するアイコンをクリックし、URLをペーストして結合をクリックします。

f:id:tanaka733:20200419110319p:plain

f:id:tanaka733:20200419110456p:plain

するとこのように表示されます。今回は読み取り専用で共有する予定です。

f:id:tanaka733:20200419110905p:plain

自分の環境で開発する場合は、新たにウィンドウを立ち上げてそちらで作業を行います。そこでわからないことがあった場合、講師とヘルプ担当の人にLive Shareで自分の環境を見てもらえるようにする予定です*2。その場合、Live Shareビューでセッションの開始をクリックします*3

f:id:tanaka733:20200419111326p:plain

すると共有用のURLが自動でクリップボードにコピーされます。もう一度コピーしたい場合は、停止アイコンの右側のメニューからコピーメニューをクリックします。停止アイコンから提出するとLive Shareを終了します。

f:id:tanaka733:20200419111456p:plain

Visual Studio Codeの場合

まず、Live Share Extension Pack拡張機能をインストールします。

marketplace.visualstudio.com

参加する場合、新規にウィンドウ左端の拡張機能アイコンでLive Shareを選び、Join Collaboration Sessionを選びます。

f:id:tanaka733:20200419114825p:plain

共有したURLを入力します。

f:id:tanaka733:20200419114928p:plain

Visual Studio Codeの場合、ホスト側が開いているファイルを自動的に開く機能はないので、参加側で手動で開いてください。開いたファイルの中身は自動的に同期されます。

f:id:tanaka733:20200419115145p:plain

ヘルプが必要な場合は自分の環境でLive ShareのメニューからStart Collaboration Sessionを選びます。

f:id:tanaka733:20200419115207p:plain

リンクをコピーや終了するメニューはここにあります。(Macでスクショをとるとアイコンが消えるので位置だけ)

f:id:tanaka733:20200419115627p:plain

.NET Core SDKをインストールする

こちらのサイトから.NET Core SDK 3.1をインストールしてください。

dotnet.microsoft.com

*1:URLがたまに失効して都度更新しているので直リンクではないです

*2:参加者とヘルプ担当の人数次第

*3:読み取り専用の場合、こちらでコードを編集できないです。

ISUCON9の参考実装をC# (ASP.NET Core 3.1)に書き換えてみた

ISUCONは説明不要の一大イベントだと思います。簡単にいうと、お題に従いより高速なWebアプリケーションを構築するコンテストです。昨年行われたISUCON9の本戦問題についてはこちらに記事があります。

isucon.net

さて、このISUCON9、参加者は予め用意されている参考実装を使うことができます。これを元に書き換えることもできますし、参考に留めて自分で一から書くこともできます。

github.com

参考実装はいくつかの言語で実装されています。Go, Perl, Python, Ruby, PHP。そう、C#がないのです。ASP.NET Coreも3.1になりましたし、必要な機能も揃ってるだろうと思い、C#に書き直してみました。

github.com

動作確認はMacで行っています。ちなみにベンチマーカーを完走させるのに必要なインデックスを貼っただけの状態での初期スコアは 約2200点です。

f:id:tanaka733:20200306195458p:plain

なお、書き直しにあたっては、ASP.NET Core 3.1 Web APIを採用し、データベースアクセスにはDapperを使っています。主にGo言語の参考実装を参考にしましたが、テストケースを回したわけではないので実装ミスがきっとあると思われます。ステータスコードやレスポンス形式は完全に同一になるように実装した(つもり)です。Go言語版の参考実装では極力1ファイルにまとめているようでしたが、流石にASP.NET Coreで1ファイルに全てのクラスをまとめてるのはあまり見かけないので、クラスをファイルごとに分けています。また、.NET Coreのコンテナ開発ではあまりやらないと思いますが、ホスト側のコード変更が即コンテナ側に反映されるようにホストパスをマウントしdotent watchでアプリを起動しています。

さて、この書き直しにあたってASP.NET Coreのいろいろな機能を活用したのですが*1、これについて4/3開催予定のC# Tokyoのオンラインmeetupで話す予定です。興味のある方はぜひこちらからご参加ください。

csharp-tokyo.connpass.com

*1:最近公開しているASP.NET Coreの記事はこれ関連だったりする

.NET Standard の HttpClientで送信時のBodyをgzip圧縮したい

HttpClientでPOSTやPUTのような送信を行う場合に、Bodyで送信するコンテンツをGZip圧縮したいと思ったのですが、意外とすぐに使えるサンプルコードが見当たりませんでした。

docs.microsoft.com

とりあえず動くコードは書けたので、メモしておきたいと思います。Bodyで送信するコンテンツは、抽象クラスであるHttpContentで扱うのですが、単純な文字列の場合はStringContent、ストリームの場合はStreamContentなど用意されている具象クラスを利用できることがあります。

docs.microsoft.com

docs.microsoft.com

が、GZip圧縮できるクラスはないようなので、自分でHttpContentを継承する必要がありそうです。主に必要なのは、SerializeToStreamAsyncで引数のStreamにGZip圧縮したコンテンツを書き込む処理です。また、実装する際には送信するコンテンツをコンストラクタなどで受け取る必要があります。今回送信するコンテンツは文字列結合で生成しているので、ZStringのUtf8ValueStringBuilderで渡してストリームに書き込むことにしました。

github.com

というわけでこのように実装しました。

using Cysharp.Text;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;

public class GZipZStringContent : HttpContent
{
    private readonly Utf8ValueStringBuilder sb;

    public GZipZStringContent(Utf8ValueStringBuilder sb)
    {
        Headers.TryAddWithoutValidation("Content-Type", "application/gzip");
        Headers.ContentEncoding.Add("gzip");
        this.sb = sb;
    }

    protected override Task SerializeToStreamAsync(Stream stream, TransportContext context)
    {
        var gzipStream = new GZipStream(stream, CompressionMode.Compress, leaveOpen: true);
        return sb.WriteToAsync(gzipStream).ContinueWith(_ =>
        {
            gzipStream?.Dispose();
        });
    }

    protected override bool TryComputeLength(out long length)
    {
        length = -1L;
        return false;
    }
}

SerializeToStreamAsyncメソッドでは、Utf8ValueStringBuilder のWriteToAsyncメソッドを使って生成したGZipSteamにコンテンツを書き込んでいます。また、完了したあとに、GZipStreamのDisposeが呼び出されるようにしています。ZStringではない別のもので受け渡したい場合は、コンストラクタで受け取ったものをGZipStreamに書き込むように処理を変えれば問題ないはずです。 また、コンストラクタではContent-TypeContent-Encodingヘッダーを設定しています。TryComputeLengthはコンテンツのサイズが低コストでわかる場合のみ正しい数値を返すので、今回は計算しないように実装しています。

docs.microsoft.com

利用するときはこのようになります。

// サンプルなので都度生成しているが、HttpClientのインスタンス生成は適切に。
var client = new HttpClient();
var endpoint = "https://example.com/log/v1";
using var sb = ZString.CreateUtf8StringBuilder();
sb.Append("[{\"logs\": [");
// さらにsbに追記
sb.Append("]}]");
var res = await client.PostAsync(Endpoint, new GZipZStringContent(sb));
res.EnsureSuccessStatusCode();

なお、処理全体としては最近公開したライブラリのこのクラスに記述しています。

github.com