xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

XSitemaps - SEO のためのサイトマップファイルを作る

業務で (主に) EC サイトの実装 / 運営をしているのですが、EC サイトと言えば SEO!お客様が来ないことには商売にならないので、検索エンジンを上手に操って流入を確保することは非常に重要です。ということで、ページのクローリングを制御するために Google Search Console などを使うわけですが、そこで必要になるのがサイトマップファイルです。サイトマップファイルにはいくつか種類があるのですが、主に XML 形式が使われるのだと思います。ファイル仕様は sitemaps.org をご覧ください。

f:id:xin9le:20200112015442j:plain

サイトマップファイルを生成するライブラリは C# / .NET の世界にもいくつか転がっているのですが、どれも微妙に気が利いてなく、社内でもプロジェクト毎に独自に実装を持っていたりして自分的に全く気に入らなかったので、せっかくなのでと思って車輪の再発明をしました。それが今回ご紹介する XSitemaps です。.NET Standard 1.1 以上をサポートしているので、.NET Framework 4.5 でも利用できます。ただ XML ファイルを作るだけとあって幅広い。

主な機能

以下は主にサポートしている機能の一覧です。見る人が見ると案外痒い所に手が届くようになっている...と思います(たぶん。というのも、サイトマップファイルには以下のような地味に面倒な仕様 / 制限があります。

  • 1 ファイルに含められる URL は最大 50,000 件
  • 50 MB 以下

もしこの制限に引っ掛かったときはサイトマップファイルを適切に分割し、サイトマップインデックスファイルを作ってあげなければなりません。これを簡単に調整できないと困ってしまうわけです。XSitemaps はこの辺りの制約に対して、

  • サイトマップファイルを指定の URL 件数単位で分割
  • インデントの有無の調整
  • GZIP 形式でのファイル圧縮 (= 仕様で認められている)

を自然な形でサポートしているので、そこが推しポイントというか便利なところです。

使い方

サイトマップファイル (Sitemap.xml) は以下のような感じで作ります。Sitemap.Serialize()Stream に書き込むこともできますし、byte[] で返すこともできます。

// URL から Sitemap を作る
var modifiedAt = DateTimeOffset.Now;
var urls = new[]
{
    new SitemapUrl("https://blog.xin9le.net", modifiedAt, ChangeFrequency.Daily, priority: 1.0),
    new SitemapUrl("https://blog.xin9le.net/entry/rx-intro"),
    new SitemapUrl("https://blog.xin9le.net/entry/async-method-intro", frequency: ChangeFrequency.Weekly),
};
var sitemaps = Sitemap.Create(urls, maxUrlCount: 2);  // 2 件ごとに分割

// ファイル出力
for (var i = 0; i < sitemaps.Length; i++)
{
    var desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
    var path = Path.Combine(desktop, $"Sitemap_{i}.xml");
    using (var stream = new FileStream(path, FileMode.CreateNew))
    {
        // 地味に気の利いたオプション
        var options = new SerializeOptions
        {
            EnableIndent = true,
            EnableGzipCompression = false,
        };
        sitemaps[i].Serialize(stream, options);
    }
}

// Sitemap_0.xml
/*
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://blog.xin9le.net</loc>
    <lastmod>2020-01-12T00:07:12.2351485+09:00</lastmod>
    <changefreq>daily</changefreq>
    <priority>1</priority>
  </url>
  <url>
    <loc>https://blog.xin9le.net/entry/rx-intro</loc>
    <changefreq>never</changefreq>
    <priority>0.5</priority>
  </url>
</urlset>
*/

// Sitemap_1.xml
/*
<?xml version="1.0" encoding="utf-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://blog.xin9le.net/entry/async-method-intro</loc>
    <changefreq>weekly</changefreq>
    <priority>0.5</priority>
  </url>
</urlset>
*/

そして、サイトマップインデックスファイル (SitemapIndex.xml) は以下のような感じです。全然難しくない!

// 分割されたファイル情報から SitemapIndex を作る
var modifiedAt = DateTimeOffset.Now;
var info = new[]
{
    new SitemapInfo("https://example.com/Sitemap_0.xml", modifiedAt),
    new SitemapInfo("https://example.com/Sitemap_1.xml"),
};
var index = new SitemapIndex(info);

// ファイル出力
var desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var path = Path.Combine(desktop, $"SitemapIndex.xml");
using (var stream = new FileStream(path, FileMode.CreateNew))
{
    var options = new SerializeOptions
    {
        EnableIndent = true,
        EnableGzipCompression = false,
    };
    index.Serialize(stream, options);
}

/*
<?xml version="1.0" encoding="utf-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>https://example.com/Sitemap_0.xml</loc>
    <lastmod>2020-01-12T00:13:24.4802279+09:00</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://example.com/Sitemap_1.xml</loc>
  </sitemap>
</sitemapindex>
*/

まとめ

SEO はエンジニアからすると比較的縁遠いものかもしれませんが、流入を気にする Web サービスの運営においては重要なことなので、もしサイトマップファイルを作ることがあれば使ってみてください。

2019 年の振り返りと 2020 年の抱負

明けましておめでとうございます!今年もいい年にしよう ZE!

ということで年末年始。大事なひと区切りなので、忘れないように 2019 年の振り返りと 2020 年の抱負を書き残しておこうと思います。

f:id:xin9le:20200101234830j:plain

プライベート

まず、2019 年の振り返りから。「2019 年を漢字 1 文字で表すと?」というのが年末恒例の質問としてあるので、それの真似をしてみると、僕のプライベートは「福」だったと思っています。

  • 3 年半ぶりに東京での単身赴任から福井に帰省
  • 家族との時間が増え、幸福感が爆上がり
  • ソシャゲを辞めて (謎の) 心的負担が無くなった

2019 年 2 月にシグマコンサルティング株式会社に転職したのですが、弊社のみなさんが僕のわがまま (= リモートワーク) を許可してくれたことに他なりません。本当に本当に感謝しています。ありがとう。

転職してみて

イマドキはもう結構メジャーな方法なのかもしれないのですが、転職は SNS で「仕事探してます!」と書き込むことで行いました。もう 1 年前かぁと懐かしくなりますが、そのときのご様子はこんな感じ。2 ch まとめに載ったのは良い思い出ですw

僕が東京に単身赴任を始めたのは 2015 年 9 月。娘が小学 1 年生になったのを契機として「そろそろいいかな」と修行目的で東京に単身赴任しました。現在は解体されてしまいましたが、C# 全開の会社 Grani に入りました。素敵なメンバーに恵まれたし、Web のソシャゲ (= 神獄のヴァルハラゲート)、モバイルネイティブのソシャゲ (= 黒騎士と白の魔王)、XR (= Grani VR Office Tour / Project Sonata) などなど色んなプロジェクトに関わらせてもらって、すごく密度の濃い時間を過ごすことができました。本当に東京に出て良かった。

それから 3 年半ほどの時が経ち、小学 1 年生だった娘が 5 年生になろうとしていました。比較的長く単身赴任をしていた割に僕と娘はメチャクチャ仲良し *1 なのですが、これから思春期を迎える娘とこれまで同様の関係でいられる時間はもう短いかもしれない *2 と思い、家族との時間を大切に過ごすために地元に帰る決心をしました。結果としてこれは本当に良い判断だったと思えていて、精神面で QoL の高い生活ができていると実感しています。仕事しつつ夏休みの娘の面倒を見ることだってできちゃう!

f:id:xin9le:20200101181204p:plain

東京の会社に勤め、東京ベースの給与でリモートワークしながら福井で生活する。福井のようなド田舎民としては理想的で先進的なライフスタイルだと思ってますし、実際同級生などと話をすると「そんな働き方もあるんだね」と驚かれます。すべて弊社に感謝です。

会社のみんなは四六時中、それこそ飲み会の場でもずっと IT や技術について語っているくらいの IT オタばっかりで心地よいです。雑談なのかミーティングなのかもはや分からないんですが、そういう空気感がとても好きです。良い選択をした。

ソーシャルゲーム

2017 年 4 月末のリリースから 2 年半、1 日も欠かさずずっとプレイし続けていた黒騎士と白の魔王を完全に辞めました。海外に行っていてもちゃんとログインし続け、時差やネットワーク環境をものともせず毎日ギルドバトルに参加するくらいガチでやってました。自社サービスをユーザー目線で理解するために始めたのですが、正直どっぷりと浸かり過ぎました。Grani を離れてからすぐ辞めてもよかったんですが、数十万課金したという勿体なさもあって辞め時を失ったというのもあります。ちゃんと長くプレイしたことで運営目線 / ユーザー目線の両方をしっかり得ることができ、経験として非常によかったです。今振り返ってみると、課金して懐を痛めた分の元は十分に取れているなと素直に思えています。

そして辞めた今はとても清々しい。ハマり症が発動すると人生ブッ壊れる可能性もあって本当にヤバいのが身に染みたので、今後ソシャゲはやらないでしょうし、長く続けることもないでしょう*3。あと色々な理由によりネカマしてたので、それも正直相当しんどかった...(とは

タピオカドリンク

2019 年の最初のうちだけですが、タピオカにハマりまくりました。Uber Eats を使って THE ALLEY のタピオカ (大) をひとりで 3 杯とか 4 杯とか注文して、家から一歩も出ないでタピオカだけで 1 日生活するとかやってました。正直狂ってました。そのせいか若干太った気がします。

f:id:xin9le:20200101180818p:plain

ついムシャクシャしてやった。今は反省している。

仕事 / プログラミング

プライベートだけではアレなので (?) 仕事面でも 1 文字だけで表してみると、「離」でしょうか。

  • 福井でリモートワークを開始
  • 守破離の「離」に近づけた気がする

リモートワーク

すでに何度かキーワードとして出していますが、僕にとってリモートワークは大きな転機となりました。弊社は会社に出社するか / 自宅作業をするかは裁量の範囲内ですが、それは社員に働き方の裁量を与えることが仕事のパフォーマンスを最大化することに繋がると社長および役員が考えてくださっているからです。自宅で仕事をする方が効率が良いときもあれば、オフィスに行った方が効率が良いときもあります。定時出社なんてものに縛られないのは極めて効率が良いでしょう。なので東京にいる社員のみんなも適宜リモートワークをしています。僕だけが福井を拠点にしているのですが、それも「20 km とかが 500 km くらいになっただけで一緒でしょ」と言ってくれたことが本当に嬉しかったし、大きかったです。

ただ、それでもやっぱり「何かあったらオフィスに行けばいい」と簡単にできない距離ではあります。概ね毎月 1 週間くらい東京オフィスに顔を出すようにしていますが、それでもどうしても Slack 上だけでは把握しきれない空気感もあれば、コミュニケーション量も大きく違うので知らないことも出てきてしまいます。これを最大限カバーするために、僕は担当外のプロジェクトの Slack チャンネルにもほとんど参加していて、誰がどんな仕事をしているのかを積極的に情報収集するようにしています。リモートワークをしている上で常に心がけているのは、「すぐに返事をすること」と「いっぱい書き込むこと」です。幽霊社員だと他メンバーに感じさせないこと、ちゃんと見ているよというアピール、相手を待たせない姿勢が大切です。

それと東京オフィスにカメラを置いてくれたのも本当に良かった点でした。スマホからオフィスの様子を見たり、会話を聞くことも、喋りかけることもできます。これによって東京オフィスでの会話を環境音として聞きながら仕事ができるようになったので、寂しさや孤独感が一切なくなりました。

守破離

僕のプログラミングの師匠は Microsoft MVP (C#) の小島さん (@Fujiwo) で、新卒で入社した会社のプログラミング研修の担当でした。半年にも及ぶ (超厳しい) 研修を受けて育ったのは良い思い出で、それが今の礎になっています。そのとき習ったもののひとつが守破離です。

  • 守 : 師の訓えや支援のもとに作業を遂行できる (= 0.5 ~ 1 人前)
  • 破 : 師の訓えを分析し、改善 / 改良できる (= 1.5 人前)
  • 離 : 師を離れ、新たな知識 / 技術を開発できる (= 創造者)

僕のプログラミングスタイルや考え方は小島さんから教わったことが今でも色濃く出ていて、他にもまだまだありますが、例えば以下もそのひとつです。

そして昨年、メチャクチャ速い .NET の Enum ユーティリティ FastEnum を作りました。ひとつの成果を残すことができたと思っていますし、師匠にも十分褒めて貰えるものだと自負しています。当然すべてを超えることはできませんが、ひとつ自分の中で守破離の「離」に達した気がしました。

プロダクト開発

受託のお仕事ということもあり大っぴらに「コレ」と言い切るのは憚られるのでぼかしますが、アンテナを張っている IT 業界の人なら絶対知っているであろう新進気鋭の某プラットフォームのバックエンドを作ったり、美容に気を遣う女性ならほぼ知っているであろう某ブランドの EC の運営をしていたりしました。認知度の高い大物案件を少人数でやるというのは達成感ややりがいがあって善きですね。

コミュニティ活動 / オープンソース開発

2019 年は結構頑張ったんじゃないかなーと思える年でした。仕事がバカみたいに忙しかったのによくこれだけやれたなぁと...w

オープンソース開発

仕事で自分が使いたいと思えるものを中心に手を付けて実装 / 検証していきました。ちょっとオーバーエンジニアリングかなと思うところもあるのですが、自己満足と学習込みで結果として良かったかな。

登壇

登壇やお手伝いで 7 つだから 2 ヵ月に 1 度以上のペース。これは正直仕事に差し支えてるのでは...(白目

de:code は初登壇なのに大トリ (= 2 日目最終コマ) で緊張しましたが、ありがたいことに満点に近いハイスコアをいただけました。本当に感謝!北陸を中心に数年やってきたエンターテイメント重視のコンテンツですが、他のセッションとは一線を画しているのは間違いないかと思います。また機会に恵まれれば、大きなイベントの余興でもなんでもやってみたいと考えています。

Visual Studio Users Community Japan では C# / .NET における高速化 Tips 集 をやりました。スライドが結構評判が良かったようで PV が 12000 を超えていました!お役に立てているのであれば嬉しい限りです。

2020 年の目標

プライベート

基本的に現状維持できるように頑張ります。思春期を迎える娘とどう接するかは僕の大きな課題ですが、基本的には寛大に、要所要所を見極めてビシッと手短に締めるという方針は変えずにやっていければと思っています。あと老後に向けた預貯金と資産運用を頑張る。年金問題 (?) に耐えられるように将来設計しなければ...(強い意志

仕事

仕事の面では「改」をキーワードとして、より安全に、より柔軟性高く顧客の要望に応えられるよう地道な改善 / 改良を進めて行こうと思っています。猪突猛進ではなく、将来を見据えて腰を据えて戦って行けるような土台作りをして行ければと。

僕は拠点がひとり離れたリモートワーカーなので、チーム的には顧客と対話したりする PM 的立ち位置ではありません。お客様は東京だけにいるわけではないですが、やはり東京が多いのでこうなっています。その分僕は当然コードをガリガリ書くことでチームに貢献しなければなりませんし、今年も 誰よりも Git に Commit しまくることで結果にコミットできればと思っています。

*1:嫁の子育ての賜物

*2:一般的に父親は敬遠されると言われているので

*3:ソシャゲ自体がダメとは言ってない

WCF で認証が必要な Proxy を利用する [.NET Framework / .NET Core / .NET Standard 完全対応]

gRPC という超クールなものが大人気な昨今、WCF なんてひと昔もふた昔も前のもの...と鼻で笑っているあなた!古き良き (?) WCF はまだまだ現役の世界線もあるのですよ!

背景 / 前話

ということで僕は仕事で EC サービスの開発/運営をやっているのですが、とある WCF を採用している外部 API と連携する必要が業務上の必須要件としてあるのです。そして、その外部 API はアクセス元を IP 制限しています。僕たちが運用している EC サービスは Azure Web Apps にホストされているので、IP を固定するためにプロキシサーバーを経由するようにしています。そしてこのプロキシサーバーはいわゆる「ユーザー名 / パスワード」での認証が必要になっています。誰でも自由に踏み台にできてしまったら困りますからね。

...と、ここまではかなりオーソドックスなアプローチかと思うのですが、認証があることによって WCF はメチャクチャ面倒が増えます。そういった部分に対応していくための方法をご紹介します。

Case.1 : 認証なし [.NET Core / .NET Framework]

認証がない場合は非常にシンプルです。Binding インスタンスにこれだけ設定すれば OK です。内部的に WebProxy インスタンスを生成して利用してくれます。

var binding = new BasicHttpsBinding();
binding.ProxyAddress = new Uri($"http://{host}:{port}");
binding.UseDefaultWebProxy = false;

Case.2 : 認証あり - グローバル対応 [.NET Framework]

Case.1 の書き方は簡単なのですが認証情報を設定する方法がありません。ここで一気に躓くのですが、WCF が内部で WebRequest 型を利用して通信しているのを利用すれば以下のように書くことができます。

// WebRequest による通信をプロキシ経由にしてしまおう!(雑
var proxy = new WebProxy(host, port);
proxy.Credentials = new NetworkCredential("UserName", "Password");
WebRequest.DefaultWebProxy = proxy;

var binding = new BasicHttpsBinding();
binding.UseDefaultWebProxy = false;  // システム既定のプロキシを使うかどうかの設定なので false

これはこれで OK なのですが、WebRequest の既定値を変更しているので大半がプロキシを経由することになってしまうでしょう。それが許されるなら良いのですが、そんな雑過ぎる暴挙は本来良くない!

しかもこれは .NET Framework でのみ有効で .NET Core では動きません。「なんでやねん!」と思うかもしれないのですが、.NET Core 版の WCFWebRequest ベースから HttpClient ベースに実装が書き換わっているからです。一筋縄では行かなさそうな嫌な空気感が徐々に出てきましたね。

Case.3 : 認証あり - 個別対応 [.NET Framework]

Case.2 だと WebRequest を利用した他の通信にまでグローバルに影響を与えてしまうのがイヤなので、なんとかして WCF の通信だけにプロキシを適用したいものです。と、ここで意地になって .NET Framework の実装を Reference Source で読み漁ってみると...?なんと裏口入学的に Proxy を差す口があるんですねー。

// こんな独自の Binding 型を用意する
public class WebProxyBinding : BasicHttpsBinding
{
    private WebProxy Proxy { get; }

    public WebProxyBinding(WebProxy proxy)
    {
        this.Proxy = proxy;
        this.UseDefaultWebProxy = false;
    }

    public override BindingElementCollection CreateBindingElements()
    {
        // リフレクションを利用して無理やり internal な 'Proxy' プロパティに WebProxy を差し込む
        // 'ProxyAddress' プロパティなどよりも優先される隠しプロパティ
        var elements = base.CreateBindingElements();
        var element = elements.Find<HttpsTransportBindingElement>();
        var flags = BindingFlags.Instance | BindingFlags.NonPublic;
        var propertyInfo = element.GetType().GetProperty("Proxy", flags);
        if (propertyInfo != null)
            propertyInfo.SetValue(element, this.Proxy);

        return elements;
    }
}
// BasicHttpsBinding の代わりに独自型を利用
var binding = new WebProxyBinding();

internal なプロパティに無理やり差し込むという完全な力業で神回避!最初から Proxy プロパティを公開していてくれれば何も困ることはなかったのに...、と思わなくはないのですが、何か意図があったんでしょうね。ちなみに CreateBindingElements メソッドは WCF のライブラリ側から相当回数コールされます。リフレクションを使っている手前、かなり実行パフォーマンスが低下することが予想されるのでご注意ください。

また、このアプローチは .NET Framework だけで有効です。.NET Core 版の WCF 実装は Proxy プロパティが丸ごと消え去っているので、リフレクションですら手出しができません。上記の実装例だと .NET Core では propertyInfonull で返ってきます。なので、過信してそのまま .NET Framework を .NET Core に移植するとハマります。

Case.4 : 認証あり [.NET Core]

.NET Core で Case.2 も Case.3 も利用できないとなると、.NET Core で WCF + 認証 Proxy を利用している方は本気で頭を抱えると思います。僕もそうでした。とは言え業務。そう易々と諦めることはできません。なんとかしなければと意地になって GitHub にある WCF の .NET Core 実装を読みまくりました。その結果、隠し機能のような差し込み口を発見しました。

// こんな感じの型を用意
public class WebProxyBehavior : IEndpointBehavior
{
    private WebProxy Proxy { get; }

    public WebProxyBehavior(WebProxy proxy)
        => this.Proxy = proxy;

    #region IEndpointBehavior implementations
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
        // このデリゲート型 (固定) を突っ込むとコールバックしてくれる!(知らんがな
        // ちょっとでもパフォーマンスを求めるならデリゲートをキャッシュすると善き
        Func<HttpClientHandler, HttpMessageHandler> callback = handler =>
        {
            // ここで設定すると内部で通信する HttpClient に渡される
            handler.Proxy = this.Proxy;
            return handler;
        };
        bindingParameters.Add(callback);
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    { }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    { }

    public void Validate(ServiceEndpoint endpoint)
    { }
    #endregion
}
// プロキシを作って
var proxy = new WebProxy(host, port);
proxy.Credentials = new NetworkCredential("UserName", "Password");
var behavior = new WebProxyBehavior(proxy);

// 差し込む
var binding = new BasicHttpsBinding();
var endpoint = "https://example.com/foo.svc";
var client = new SampleClient(binding, endpoint);
client.Endpoint.EndpointBehaviors.Add(behavior); 

IEndpointBehavior を実装した型で Func<HttpClientHandler, HttpMessageHandler> デリゲートを BindingParameterCollection に突っ込むという隠し機能!見つけたときはメチャメチャ歓喜しましたw ちなみにこのあたりに実装があります。

この拡張ポイントは WCF が通信をするときに内部で利用する HttpClientHttpMessageHandler をカスタマイズするためにあります。今回は Proxy を設定するために利用していますが、通信にかかった時間を測定したりログを出力するなど、通信の前後をフックしていろんなことができるようになっています。全然まともにドキュメント化されていないのが残念ですが、.NET Framework 時代よりも汎用性の高い拡張ポイントを提供していると思います。

ちなみに、この拡張ポイントは .NET Core で新たに追加されたものなので .NET Framework では動作しません。具体的には bindingParameters.Add(callback) でコールバックを登録していてもコールバックされません。無視されます。

Case.5 : 認証あり [.NET Standard]

最後に .NET Standard で WCF の通信をラップするようなライブラリを作る場合です。ここまで読んできてお分かりかと思いますが、 Case.3 と Case.4 を組み合わせれば OK です。お互いの実装がちょうどよく無視されるので、特に干渉することなく動作します。めでたし。

FastEnum の初期化コストと利用指針

先日、FastEnum に関して非常に良い質問を受けました。短期間だけ起動してすぐにアプリ/インスタンスが死んでいくバッチ処理などにおいては、逆に初期化コストが大きくなってしまうのでないか?というものです。

確かに実行速度はメチャクチャ速いのですが、初期化コストがどの程度かは把握していませんでした。というのも、FastEnum は Web サービスやクライアントアプリなどの比較的長く利用されるものに対する応答速度を極限まで向上させることを目指しているので、初期化コストは warm up などで無視するものとして割り切っていました。ですが、確かに初期化コストを無視できないようなシチュエーションでの利用にどの程度耐えられるのかは気になります。

計測結果

ということで計測してみました。結果は以下のような感じです。呼び出し回数は、FastEnum の初期化の間に System.Enum の各種メソッドを何度呼び出せるのかを示しています。

処理 時間 [ns] 呼び出し回数 アロケーション [B]
FastEnum.Init 22,075.16 1.00 8,067
Enum.GetValues 1,337.97 16.50 352
Enum.GetNames 45.57 484.42 128
Enum.GetName 58.82 375.30 24
Enum.IsDefined 119.85 184.19 24
Enum.TryParse 139.91 157.78 24
Enum.ToString 33.95 650.23 24

f:id:xin9le:20191030000118p:plain

どうやら、一度ひとつの列挙型を初期化するのは 17 回ほど Enum.GetValues() を呼び出すのと同等なようです。Enum.ToString() であれば 650 回ほど。こう見ると結構遅い...気がする!初期化するときの実装には言うほど気を配っていないのが明るみに出た感じががが...。

と言っても、これらの数字は Web サービスのリクエスト数などからすればすぐにペイできます。初期化コストを気にするよりも一度のリクエストを高速に捌く方がずっと大切なので、言うほどではないでしょう。逆に短命なアプリやバッチ処理の場合は利用しない方が良いでしょう。ケースバイケースで使い分けるのが良いかと思います。

けど、やっぱりもうちょっと初期化コストを抑えるように頑張っても良いかもしれない...

ASP.NET Core 3.0 で cshtml の Edit & Continue を有効化する

ASP.NET Core でフロントエンド開発をするときは「デバッグ実行しながら cshtml の編集をしてブラウザを Reload!」というのが王道中の王道なのではないかと思います。所謂 cshtml に対する Edit & Continue です。少なくとも僕は今までずっとそうしてきました。Web Essentials というヤツの中に BrowserSync なるものがありますが、僕はそういう類の拡張機能は入れたくない派なので使ってないのです。

という、基本中の基本である開発中の行動が ASP.NET Core 3.0 になったら突然できなくなって大変お困り!どうやら ASP.NET Core 3.0 で仕様変更があり、実行時コンパイルを廃止したみたいです。一度コンパイルしたものをずっと使うのはパフォーマンス上とても良いのでありがたい話ですし、ASP.NET Core を Roslyn に依存しない形にするという点では正しい分離な気はします。が、そうは言っても開発中はすこぶる困る!

Edit & Continue を有効にする

Razor の実行時コンパイルの機能は、ASP.NET Core 3.0 から Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation というライブラリとして分離されています。これを NuGet からインストールしましょう。インストールできたら Startup.cs.AddRazorRuntimeCompilation() を追加します。たったこれだけです :)

services
    .AddMvc()
    .AddRazorRuntimeCompilation();  // 追加

この機能は基本的に開発環境だけに適用されれば良いはずなので、以下のようにするのも効果的かと思います。

var mvcBuilder = services.AddMvc();    
if (this.Environment.IsDevelopment())
    mvcBuilder.AddRazorRuntimeCompilation();