xin9le.net

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

2021 年の振り返りと 2022 年の抱負

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

ということで年末年始。大事なひと区切りなので、今年も忘れないように振り返りと抱負を書き残しておこうと思います。2021 年は一瞬で過ぎ去ったというか、働き過ぎのせいでそれ以外の記憶がかなり薄い。

お仕事

個人事業主として開業

2021/8 に個人事業主になりました。会社員であることをやめたわけではなく、副業としてです。もちろん事前の計画なんて全くなく、行き当たりばったりというか、出たとこ勝負的な勢いだけでの開業でした。なぜ開業することになったかというと、仲の良い友人の @_y_minami からお仕事を依頼されたからでした。当初はだいぶ渋っていたというか、夏は特に本業でデスマってる勢いだったので副業の時間だけで力添えできるとは全く思っていなかったからです。なのですが、当時の本業のタスクが精神的にあまりにも辛く、何か別のことに目を向けたい (= 本業から少しでも目を背ける時間が欲しい) と癒し (?) を求めて受けることにしました。

こうなると本業とは別の収入が発生してしまうということになり、これまで会社員としてしか生きてきたことがない僕にとっては未経験なことが多く発生しました。

  • 開業届の提出
  • 契約書
  • 経費の扱い
  • 確定申告 (← 近々体験することに

正直なところ、こう言った手続きや雑務は非常に苦手でなのでやりたくはなかったのですが、何事も経験だろうと思って踏み切った次第です。ただやってみると (当然) 新しい知識を得るもので、非常に良い経験になっています。ビジネスや税制の仕組み、あと簿記が以前よりも分かった気がします。とにかく freee さんの体験が凄まじくよく、心の底からビギナーの味方という感じで助かっています。

副業で何してるの?

fingger というコメント連動型ゲーム配信サービスの開発のお手伝いと技術顧問をしています。と言うとなんか凄そうな響きの肩書ですが、CTO である @_y_minami の社外相談役と言うと適当でしょうか。それと爆裂コード書きマンです。もう普通にコード書いてます。なんならまだ携わって 4 か月程度ですが、GitHub 上ではすでに No.1 Contributor になっていますw

f:id:xin9le:20220101022353p:plain

やること / やれることが多過ぎて、気が付いたら年が明けてました。仕事が納まらないまま仕事始めになりました...(

C# 10 / .NET 6 全開でコード書きまくっているので、もしご興味がある方は一緒にお仕事しましょう!ご連絡は @_y_minami まで!

本業はどうだった?

まず、2021 年の目標として昨年掲げていた「担当 EC プロジェクトの .NET 5 移植」ですが、これはやりきって 5 月に本番環境にデプロイすることができました。めでたしめでたし。驚くほど安定稼働するようになって、アラートに悩まされることは完全になくなりました。セール等のアクセスのバーストも平気で乗り越えます。今は .NET 6 への移行まで終わっていて大変平和です。

また、それとは別に 2021 年の第一四半期は新しい EC サービスのローンチに向けた開発をしていました。びっくりするほどスムーズなプロジェクトで、前半は本当に良いスタートを切れたなぁという感じでした。そのとき Paidy 決済の実装が必要になったので、.NET 向けの SDK を作ったりしました。もし C# / .NET で Paidy 決済の開発をされる方がいらっしゃったら、是非使ってみてくださいませ。

夏頃からは大規模リファクタリングにかかりっきりでした。新機能を実装するのにリファクタリングをしないとダメなのに「納期だけ決まっている」という散々な有様で、自分が作った部分じゃないだけにヘイトの溜まりっぷりとイライラがピークでした。ほんの数ページのリファクタリングだけで 3 か月近くかかっていて、その期間に副業に癒しを求めたといった感じでした。こんな精神的に擦り減る仕事の仕方は二度としたくないですね、さすがに。

あとは LINE 認証のライブラリを作ったりもしました。LINE Profile+ や BotPrompt に対応した .NET SDK がなかったといことで必要に迫られて作ったものではありますが、かなりシンプルで綺麗に仕上がっていると思います。LINE 認証が必要な方がいらっしゃったら是非。

コミュニティ活動

YouTube 配信

2021 年も COVID-19 の影響で人と直接会う機会がほぼなく、岩永さんとの C# YouTube 配信 が主な活動でした。最新 / 最先端の C# について隔週くらいで話ができる機会があることは、C# ヲタにとって最高の環境です。いつも一緒してくださっている岩永さんかずき先生に感謝!

C# Japan Discord

引き続きゆるーくサーバー管理者を続けています。現時点で 882 名の方にご参加いただいております。本当に感謝!参加してみたいという方は、下記リンクからどうぞ!

GitHub

のいえ先生が作った CloudStructures を数年 (勝手に) 保守をしていたのですが、なんとリポジトリが僕のところに移管されました。Cysharp 配下に入れないでこっちに来るとか、そういうこともあるんですねw

地味ぃに改修を続けていまして、Azure Redis Cache のメンテナンスイベントを受けられるようにしてみたり、いくつか新しくコマンドを追加したりしています。

プライベート

ゲーム

数年遅れと言われても仕方ないくらいの時代遅れ感がありますが、ようやく NINTENDO Switch を買いました。我が家で唯一の据え置き型ゲーム機です。NINTENDO 64 がやりたかったので...。64 版のスマブラが Switch Online で配信されないかなーと心待ちにしています。

YouTube

この 1 年も毎晩のように YouTube を観ていました。特に将棋解説と高校数学 / 高校物理あたりに興味が出て、以下のチャンネルには大変お世話になりました。

高校生の頃は (当然?) 勉強が好きではなかったわけですが、なぜ今になって知識欲が出るのか不思議。理由があるとすれば、娘に教えられるように先回り (?) して復習してるという感じでしょうか。

2022 年の目標

新卒からこれまでのプログラマのキャリアの間はずっと本業だけに明け暮れていたのですが、昨年から副業もはじめたということで上手に双方のバランスをとれるようにしていきたいと思っております。単純に分野が違うというだけでも面白いですし、片方の知識がもう片方に生きることも当然あるはずなので、そういう相乗効果が生まれることを楽しみにしています。

あとは健康には真剣に目を向けたい。最近腰痛がかなりひどく、湿布と飲み薬に頼り切りというアカンコレ状態...。健康が理由でプログラマ人生が短くならないようにしていかなければ。

SDK Style の csproj で ASP.NET (.NET Framework) を動かす

世の中は .NET 6 RC1 がリリースされ、.NET 6 (LTS) の時代がもうすぐそこまで来ています。が、現実はそんなに甘くない!.NET Framework 4.8 + ASP.NET MVC 5 で頑張っている人もいるんです!ただ、一度でも .NET Core 時代の SDK Style の .csproj (新形式) という甘い蜜を吸ってしまうと .NET Framework の .csproj (旧形式) はかなり厳しく感じます。むしろその一点だけでも .NET Framework を敬遠するレベル。特にどの辺りが嫌いかと言うと、

  • NuGet Package の管理が煩雑で、どの dll と依存関係にあるのかが全然分からない
  • .cs ファイルひとつ追加するだけでも .csproj にタグが 1 行増える
  • 多人数開発している際に .csproj がコンフリクトすることがちょくちょくある

などです。SDK Style の .csproj はこういった部分が解決されていて大変スッキリします。昨今は特に Web 開発をすることが多いので ASP.NET + SDK Style .csproj の組み合わせで開発できるようにしておくと何かと平和そう。ということで、今回はそんなアナタ (= 僕自身) のために SDK Style 版の ASP.NET プロジェクトの作り方を書き残します。

書き換え手順

備忘録ということで Step-by-step で作業メモを残します。でき上がりのサンプルプロジェクトは GitHub に Push しておいたので、そちらも併せてご参照ください。

今回は ASP.NET MVC 5 のみを取り扱いますが、一部読み替えれば WebForms / Web API / WebPages / SignalR などでも使えるのではないかと思います。

Step.1 : ASP.NET MVC 5 のプロジェクトを作成

まず、Visual Studio で .NET Framework の ASP.NET Web アプリケーションのプロジェクトを作成します。Wizard をポチポチっとするだけなので簡単です。

f:id:xin9le:20210921170350p:plain

f:id:xin9le:20210921170532p:plain

f:id:xin9le:20210921170905p:plain

親の顔より見た構造のプロジェクトが出来上がりました。.csproj を見てみると、複雑でイヤーな感じの旧形式の XML が見えます。ここから SDK Style の .csproj に書き換えていきましょう。

f:id:xin9le:20210921171401p:plain

Step.2 : .csproj を SDK Style に書き換える

.csproj をテキストエディタで開き、以下のように書き換えましょう。あのクソ長い XML がたったこれだけになります!

<Project Sdk="MSBuild.SDK.SystemWeb/4.0.50">

    <PropertyGroup>
        <TargetFramework>net48</TargetFramework>
    </PropertyGroup>

    <ItemGroup>
        <!-- 必須 -->
        <PackageReference Include="Microsoft.AspNet.Mvc" Version="5.2.7" />
        <PackageReference Include="Microsoft.AspNet.Web.Optimization" Version="1.1.3" />
        <!-- 依存関係パッケージの最新化 -->
        <PackageReference Include="Antlr" Version="3.5.0.2" />
        <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
        <PackageReference Include="WebGrease" Version="1.6.0" />
    </ItemGroup>

    <ItemGroup>
        <Reference Include="Microsoft.CSharp" />
        <Reference Include="System.Web" />
    </ItemGroup>

</Project>

特に重要なのが 1 行目の <Project Sdk="MSBuild.SDK.SystemWeb/4.0.50"> の部分で、System.Web を利用した IIS 向けアプリケーションビルドをしてくれるようになります。細かいことは気にせず、これを使いましょう。

Step.3 : 不要なファイルを削除

SDK Style にすることで不要になる以下のファイルを削除しましょう。

  • \Properties\AssemblyInfo.cs
  • packages.config

f:id:xin9le:20210921182459p:plain

Step.4 : Assembly Binding のバージョン調整

参照しているアセンブリと Web.config に指定されている Assembly Binding 等のバージョン不一致を調整します。これは直接的には SDK Style 化には関係がないことですが、せっかくなので NuGet Package を最新化しておきましょう。つまりオマケです。

<dependentAssembly>
    <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" />
    <bindingRedirect oldVersion="0.0.0.0-13.0.0.0" newVersion="13.0.0.0" />
</dependentAssembly>

Step.5 : DotNetCompilerPlatform のバージョン調整

最後に Microsoft.CodeDom.Providers.DotNetCompilerPlatform のバージョンを揃えます。今回だと 3.6.0 の NuGet Package を参照することになるので、該当箇所を 3.6.0.0 にしましょう。これをしないと実行時にエラーになってしまうので、とっても大事!

<system.codedom>
    <compilers>
        <compiler language="c#;cs;csharp" extension=".cs" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.CSharpCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=3.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:1659;1699;1701" />
        <compiler language="vb;vbs;visualbasic;vbscript" extension=".vb" type="Microsoft.CodeDom.Providers.DotNetCompilerPlatform.VBCodeProvider, Microsoft.CodeDom.Providers.DotNetCompilerPlatform, Version=3.6.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" warningLevel="4" compilerOptions="/langversion:default /nowarn:41008 /define:_MYTYPE=\&quot;Web\&quot; /optionInfer+" />
    </compilers>
</system.codedom>

できあがり!

以上の 5 step が済んだら以下のようなプロジェクト構成になっているかと思います。特にアセンブリの依存関係部分がスッキリしたのではないでしょうか。

f:id:xin9le:20210921225908p:plain

では最後にデバッグ実行して動作確認をしてみましょう。IIS Express が立ち上がり、これまた親の顔より見た ASP.NET MVC 5 テンプレートの画面が立ち上がるはずです。

f:id:xin9le:20210921183752p:plain

注意点

ここまで来て「最高じゃないか!」と思いたいところですが、実は盛大な落とし穴があります。dotnet publish や Visual Studio の 発行 が .NET Framework の .csproj と同じようには挙動しません...!なんなら全然ダメダメで頭を抱えるレベル。たとえば

  • dotnet publish で bin フォルダの中身のみ出力される
  • web.config が Debug / Release で合成されない

などなど、Production 環境に乗せるにはかなり致命的なものが多いです。ビルド環境を整えようと思ったら華麗なる (?) CI 芸が必要になりそうというか、その部分さえクリアできれば使い物になりそうな予感はします。まさに「あともう一歩」というところでしょうか。

LINE Profile+ に対応した LINE ログイン Provider ライブラリを作りました

業務で LINE ログインを実装することになり、合わせて LINE Profile+ から情報を取得する必要が出ました。ということで LINE Profile+ をサポートした OAuth2 ライブラリを作りました

弊社メンバーで Microsoft MVP for Azure の吉野くんが以前 .NET Core 2.x 世代向けに LINE ログインのライブラリを作っていたので、そこに相乗りさせてもらいました。

Getting Started

このライブラリは ASP.NET Core における外部プロバイダー認証の実装に準拠しています。なので、以下のような感じで始められます。

dotnet add package LineAuthentication
services
    .AddAuthentication()
    .AddLine(options =>
    {
        options.ClientId = Configuration["Authentication:Line:ChannelId"];
        options.ClientSecret = Configuration["Authentication:Line:ChannelSecret"];
    });

LINE Profile+ の情報取得

LineAuthentication-AspNetCore を利用すれば、OAuth の認可スコープを指定することでユーザーの LINE Profile+ から以下の情報を取得できるようになります。会員登録などで利用したいですね。

  • 姓 / 名
  • 姓 / 名 (カナ)
  • 性別
  • 生年月日
  • 住所
  • 電話番号
  • E-mail アドレス

ちょこっと注意が必要なのは、LINE Profile+ にアクセスするためには LINE 側への利用申請が必要な点です。個人情報を扱うのでこればっかりは致し方ない感じはあります。詳細は LINE Developers のドキュメントをご参照ください。

こんな感じで利用します。なんてことはなく、標準に準拠してるので他の外部ログインを実装したことがある方にとってはメッチャ簡単です。

services
    .AddAuthentication()
    .AddLine(options =>
    {
        options.ClientId = Configuration["Authentication:Line:ChannelId"];
        options.ClientSecret = Configuration["Authentication:Line:ChannelSecret"];

        // 認可スコープを追加
        options.Scope.Add("real_name");
        options.Scope.Add("gender");
        options.Scope.Add("birthdate");
        options.Scope.Add("address");
        options.Scope.Add("phone");
        options.Scope.Add("email");

        // JSON ペイロードとの Claim 情報のマッピング
        options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");

        // JSON ペイロードに丸ッとアクセス
        options.Events.OnCreatingTicket = context =>
        {
            // context.User に JsonElement 形式で入ってます
            var json = context.User.GetRawText();
            return Task.CompletedTask;
        };
    });

なぜ作ったか

ASP.NET Core で OAuth2 による認証 / 認可を実装しようと思ったら、大半の実装は AspNet.Security.OAuth.Providers にあります。この中にも LINE Provider のライブラリは当然あるのですが、

  • LINE Profile+ に対応していない
  • Pull-Request を出してもスピーディ NuGet 化されない
  • .NET 5 のみのサポート

という部分が気掛かりでした。特に最初の 2 点が今回ビジネス的に大変困るということで、別ライブラリとして実装することにしたという感じです。

実際をのところは AspNet.Security.OAuth.Line で LINE Profile+ に対する拡張ポイントが全くないのかと言うとそうではないんですが、ライブラリ規模が非常に小さいのもあって「作り直しするのとほぼ変わらないなぁ」という肌感だったというのが正直なところです。

.NET 5 未満でもモジュール初期化子を利用する

.NET 5 じゃなくても C# 9.0 をできる限り使いたい!そんなあなたのために「.NET 5 未満でも」シリーズ第 2 弾。第 1 弾はこちら

今回はタイトルにある通りモジュール初期化子についてですが、それ自体の用途や詳細挙動は岩永さんのサイトに譲ります。

.NET 5 未満でモジュール初期化子を有効化

モジュール初期化子は ModuleInitializerAttribute を以下の条件を満たすメソッドに付与することで利用できます。

  • 引数なし
  • 戻り値なし
  • static メソッド
  • public / internal のどちらかのアクセシビリティ
  • 非 Generics

逆に言うと ModuleInitializerAttribute をプロジェクト内で用意してあげればコンパイラは解釈できることになります。ということで、準備しましょう。プロジェクト内に閉じるだけであれば internal 型でも問題ありません。

#if !NET5_0_OR_GREATER
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices
{
    [AttributeUsage(AttributeTargets.Method, Inherited = false)]
    internal sealed class ModuleInitializerAttribute : Attribute
    { }
}
#endif

あとは C# 9.0 を明示的に有効化すれば OK です。第 1 弾で紹介した init の有効化の方法と全く同じですね。積極的に (?) コンパイラをハックしていきましょう!

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <!-- これで .NET 5 以外のターゲットに対しても init が有効になる -->
        <TargetFrameworks>netstandard2.0;netstandard2.1;net461;net5</TargetFrameworks>
        <LangVersion>9.0</LangVersion>
    </PropertyGroup>
</Project>

.NET 5 未満でも C# 9.0 の init アクセサを利用する

init アクセサは大変良いです。C# 9.0 で追加された初期化のタイミングでのみプロパティに値を設定できる set アクセサです。アクセシビリティは狭ければ狭いほどコードは安全になるので、僕は set を見たらとりあえず init に置き換える勢い!

.NET 5 未満で init を利用する

init アクセサは内部的にはただの set アクセサです。set アクセサに対して IsExternalInit という型と共に modreq という謎の (?) 修飾を行うことでコンパイラが init な挙動と解釈してくれます。逆に言うと IsExternalInit という型があり、それを解釈できるコンパイラがいれば利用できるとも言えます。

ということで .NET 5 以外のプロジェクトに対して IsExternalInit 型を準備します。.NET 5 で用意されている型は public ですが、プロジェクト内だけに閉じる場合は internal な型で大丈夫です。

#if !NET5_0_OR_GREATER
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace System.Runtime.CompilerServices
{
    internal sealed class IsExternalInit
    { }
}
#endif

あとは C# 9.0 を明示的に有効化すれば OK です。

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <!-- これで .NET 5 以外のターゲットに対しても init が有効になる -->
        <TargetFrameworks>netstandard2.0;netstandard2.1;net461;net5</TargetFrameworks>
        <LangVersion>9.0</LangVersion>
    </PropertyGroup>
</Project>

readonly struct の制約緩和

個人的にこれの何が嬉しいかと言うと、C# 7.2 から導入された readonly struct の条件緩和があります。init でコンパイラがアクセシビリティをより細かく制御できるようになったおかげで、以下のように書けるようになりました。

// C# 8.0 までは「getter only + コンストラクタ」しか認められなかった
public readonly struct Person
{
    public string Name { get; }
    public Person(string name)
        => this.Name = name;
}
// C# 9.0 からは init でも大丈夫
public readonly struct Person
{
    public string Name { get; init; }
}

C# 9.0 が利用できるコンパイラさえあれば (= 最新の Visual Studio を利用してさえいれば)、Target Framework が .NET 5 でなくても init を利用できるのは大きなメリットです。