ぷろじぇくと、みすじら。 2022-05-21T09:11:37.901Z http://www.misuzilla.org/ Mayuki Sawatari Hexo Azure Container Apps (Preview) でカスタムドメインに必要な証明書を Cloudflare で作る http://www.misuzilla.org/Blog/2022/05/21/ContainerAppsWithCloudflareOriginCerts 2022-05-21T09:00:00.000Z 2022-05-21T09:11:37.901Z tl;dr
  • Azure Container Apps は現時点ではカスタムドメインの設定に証明書が必須
  • Cloudflare オリジン証明書というオリジンと Cloudflare の間の通信で使用する証明書を発行できる仕組みがある
  • オリジン証明書を Container Apps に登録して、Cloudflare をフロントに置くことでドメインの証明書管理を Cloudflare にまかせる

Azure Container Apps にカスタムドメインを登録時、証明書が必要

Azure の Azure Container Apps (Preview) ではカスタムドメインを設定して、外部からのアクセスを受け付けるようにできます。

詳しくはしばやんさんのブログを参照していただきたいのですが、現時点ではカスタムドメインの設定時に HTTPS 向けの証明書のアップロードが必要です。

つまりドメインに対する証明書を何らかの方法で作成/用意する必要があり、しばやんさんの記事では Acmebot を用意して Let’s Encrypt で証明書を作成、更新していく方法が紹介されています。

今回触っていた環境は個人で適当に立てているサイトなので動かすものを増やしたくないなと思っていたのですが、元々 Cloudflare を前に置いていたので Cloudflare が自動で発行、更新する証明書を使うようにすれば丸投げできそうな気がしてきました。が、しかし Container Apps は HTTPS 必須なのかカスタムドメインの登録時にどうにしても証明書が必要です。

オリジン証明書を作る

Container Apps のカスタムドメイン設定に必要な証明書をどうするか、というところで Cloudflare にはオリジン証明書を作るという機能があるのでこれを使います。

オリジン証明書は Cloudflare とオリジン、この場合だと Container Apps の通信でのみ使用する目的の証明書で今回の用途にはぴったりです。デフォルトでは15年という長い期限で作成され、個人のサイトならそっちが先に朽ちるか Managed Certificates がくると思うのでよさそうです(今回はある程度放っておきたいのが目的)。

オリジン証明書は Cloudflare の管理画面の SSL/TLS → オリジンサーバー → オリジン証明書 で発行できます。発行すると証明書と秘密鍵がでてきますので PEM 形式で手元に保存します。特に秘密鍵はページを閉じると確認できなくなるのでしっかり保存しておきます。

オリジン証明書を pfx 形式 (PKCS#12) に変換する

次に発行した証明書と秘密鍵を Container Apps に登録します。ところが登録時にパスワードを求められたりしてそのままでは取り込めないので OpenSSL で pfx 形式 (PKCS#12) に変換します。

cat certificate.pem private.pem > origin.pemopenssl pkcs12 -export -in origin.pem -out origin.pfx

オリジン証明書を Container Apps に登録する

pfx 形式の証明書を作成したら Container Apps に登録します。Azure Portal の コンテナー アプリ環境 → (コンテナーアプリ) → 証明書 で “証明書の追加” で追加を行えます。

注意点として現時点では “証明書名” に大文字アルファベットを含む文字列を指定すると “この証明書名は既に使用されています。別の証明書名をお試しください。” という謎のエラーが出るので小文字で入力してください。

カスタムドメインの設定

証明書が作成出来たら後はカスタムドメインの設定を行い、証明書として先ほど登録したものを選択すれば完了です。

まとめ

Container Apps が Managed Certificates にはやく対応してほしいですね。

]]>
<h2 id="tl-dr"><a href="#tl-dr" class="headerlink" title="tl;dr"></a>tl;dr</h2><ul> <li>Azure Container Apps は現時点ではカスタムドメインの設定に証明書が必須</li> <
Source Generator の Visual Studio 2019 v16.9 での新 API http://www.misuzilla.org/Blog/2021/05/02/SourceGeneratorNewApiIn16.9 2021-05-02T13:50:00.000Z 2022-05-21T09:11:37.901Z Source Generator を触っていて気づいたのですが Visual Studio 2019 version 16.9 の段階でいくつか API が追加されていました。

追加されたものは次のようなあるといいよねといった API が増えている感じです。

  • GeneratorInitializationContext.RegisterForPostInitialization メソッド
  • ISyntaxContextReceiver インターフェース

Visual Studio 2019 であれば 16.9 以降、NuGet パッケージであれば Microsoft.CodeAnalysis.CSharp 3.9.0 を参照すれば使用できます。

GeneratorInitializationContext.RegisterForPostInitialization メソッド

GeneratorInitializationContext.RegisterForPostInitialization メソッドは Source Generator が読み込まれて初期化された後に呼び出されるコールバックを登録できます。

これは Source Generator で必要となる属性用のコードを追加したいパターンに役立ちます。例えば次のようなコードがサンプルにあります

[Generator]public class AutoNotifyGenerator : ISourceGenerator{    private const string attributeText = @"using System;namespace AutoNotify{[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)][System.Diagnostics.Conditional(""AutoNotifyGenerator_DEBUG"")]sealed class AutoNotifyAttribute : Attribute{    public AutoNotifyAttribute()    {    }    public string PropertyName { get; set; }}}";    public void Initialize(GeneratorInitializationContext context)    {        // Register the attribute source        context.RegisterForPostInitialization((i) => i.AddSource("AutoNotifyAttribute", attributeText));        // Register a syntax receiver that will be created for each generation pass        context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());    }...}

今までソースで指定したい属性をどうするかという問題があった(一度生成させてその属性を使うのか、手で追加するのかとか)のがこれで解決できそうです。

ISyntaxContextReceiver インターフェース

コンパイラーがソースコードを処理する際に Source Generator を呼び出す ISyntaxReceiver インターフェースがあります。

例えばクラス定義に対するコードジェネレートを行いたいときには自分でシンタックスツリーを走査して集めるのではなく、ツリーの走査はコンパイラーに任せて、 ClassDeclarationSyntax を集めておいて最後に処理するといった使い方です。

ISyntaxReceiver インターフェースは OnVisitSyntaxNode メソッドのみをもち引数として SyntaxNode を受ける形でしたが、 ISyntaxContextReceiver では GeneratorSyntaxContext を受け取る形になります。

このコンテキストオブジェクトにはセマンティックモデルが含まれているので、Receiver で必要なものをかき集めるときに、コードの解析された情報にアクセスできるようになります。

例えば属性はセマンティックモデルを通してシンボルを取得して、GetAttributes で取得できるので、Source Generator 用の属性がついていることを Receiver が集める段階で確認できます。こちらもサンプルコードが例としてわかりやすいです。

注意

16.9 で追加された API なので古い Visual Studio や .NET SDK では動かない可能性があります(試していない)。

Rider 2021.1.2 ではビルドは問題ないのですが Rider 側が対応していないようでエディター上では正しく動作しなかったりしたので、2021年5月頭時点では使うにはちょっと早いかもしれません。

]]>
<p>Source Generator を触っていて気づいたのですが Visual Studio 2019 version 16.9 の段階でいくつか API が追加されていました。</p> <ul> <li><a href="https://github.com/dotnet/
Blazor のコンポーネントのステートについて http://www.misuzilla.org/Blog/2020/12/25/BlazorComponentLifecycle 2020-12-24T15:00:00.000Z 2022-05-21T09:11:37.901Z これは Blazor Advent Calendar 2020 の25日目のエントリーです。

Blazor はお手軽に Single Page Application を作れるのですがコンポーネントのライフサイクル、ステート関連について知らないと若干不思議な仕組みで動いているように見えます。

例えばプロジェクトテンプレートの Counter は @onclick="IncrementCount" でプライベート変数 currentCount をインクリメントするとページのカウントがアップしますが、これは初見ではなかなか不思議な挙動です。Blazor は currentCount の変更をどうやって知ったのか?としばらく不思議に思っていました。そういったこともライフサイクルを知ることで理解できます。

基本的には ASP.NET Core Blazor ライフサイクル というドキュメントに書いてあるのですがこれはそれを補完する目的のエントリーです。

基本的な流れ

Blazor コンポーネントはコンポーネントのステート(状態)が変更されるたびに再レンダリングを行うという流れが基本となります。これ自体は他のフレームワークと大きく変わるところではないと思います。

  • 初期化
  • レンダリング
  • ステート変更
  • 再レンダリング
  • ステート変更
  • 再レンダリング

これらの多くのライフサイクルに関連するものは Microsoft.AspNetCore.Components.ComponentBase クラス に実装されています。

初期化/初回レンダリング

コンポーネントの初回のレンダリング、つまり初めてページが表示されたときの処理の流れは次のようになっています。

  • コンストラクター
  • SetParametersAsync
    • OnInitialized
    • OnInitializedAsync
    • OnParametersSet
    • OnParametersSetAsync
  • BuildRenderTree
  • (ここに子コンポーネントの一式が入る)
  • OnAfterRender
  • OnAfterRenderAsync

初めに SetParametersAsync が呼び出され、初期化とパラメータのセットを行います。この OnInitializedAsync (初期化), OnParameterSetAsync (パラメータセット) では非同期処理を行うことができますが、その場合は Task の完了を待たずにレンダリングが行われて完了次第再レンダリングが行われます。

つまり非同期初期化の場合、コンポーネントの HTML をレンダリングが行われるタイミングではまだ値がそろっていない場合がある点には注意が必要です。

private NanikaObject _nanika;protected override async Task OnInitializedAsync(){    await Task.Delay(1000);    _nanika = new NanikaObject();    await base.OnInitializedAsync();}...@* OnInitializedAsync が終わるまで _nanika は null *@<h1>@_nanika.Description</h1>

BuildRenderTree メソッドは Blazor 上のドキュメントツリー構造(仮想DOM的なもの)を構築するものです。通常は Razor Component (.razor) ファイルから自動生成され、手でツリーを作るようなカスタムコンポーネントを作らない限りは触ることはありません。中身が気になる場合には obj ディレクトリの中を覗いてみると生成物を確認できます。

なお Server-side Blazor に関してはレンダリングモードが ServerPrerendered の場合、 SetParametersAsync + BuildRenderTree が2回呼び出されます。1回目はプリレンダリングのための実行です。またプリレンダリングはサーバーサイドで完成系を返す都合、SetParametersAsync の完了を待ってからのレンダリングとなります。

OnInitialized と OnParametersSet の違い

OnInitialized{Async} と OnParametersSet{Async} の違いは、OnInitialized がコンポーネントの初期化であるのに対して、OnParametersSet はプロパティの変更です。

つまり [Parameter] として受けている値が変わったときに呼び出されるもので React で言うなら props の変更のような感じでしょうか。

例えば次のようなコンポーネントを用意して…

<p>@Count</p>@code {    [Parameter]    public int Count { get; set; }    protected override Task OnParametersSetAsync()    {        Console.WriteLine($"Count={Count}");        return base.OnParametersSetAsync();    }}

Counter.razor にぶら下げてみます。

<Nantoka Count="currentCount" />

これでカウンターを更新すると OnParametersSetAsync が呼び出されることを確認できます。

ステート(状態)と再レンダリング

そもそも Blazor におけるステートとは何かステートという特別な入れ物があるわけでもなく、あるのは何らかの何らかステートが変更されたということを通知する仕組みです。

データそのものはプライベート変数であったり、Parameter であったりするかもしれませんが Blazor としては「何か状態が変わったことを知る」ということが重要になります。

Blazor はステートの変更通知を受けると、コンポーネントの再レンダリングを行い、表示を更新します。

ステート変更通知は何によって引き起こされるのか

ステートの変更通知はどのようなタイミングで発生するのかですが、おおまかに下記の3パターンで発生します。

  • StateHasChanged メソッドを明示的に呼び出したとき
  • イベント発火時 (@bind@onclick とか)
  • Parameter が変わったとき

3パターンと書きましたが2つ目と3つ目は暗黙的に StateHasChanged を呼び出しているというのが実際のところです。

StateHasChanged を呼び出したとき

StateHasChanged はステートが変更されたことを通知するメソッドです。このメソッドを呼び出すと再レンダリング候補としてマークされます。何が起こるのかは後ほど少し説明します。

多くのケースでは StateHasChanged を明示的に呼び出す必要はありませんが、ロジック側から表示を更新したいケースでは呼び出しを必要とします。

例えばサーバーからのデータ受信やタイマー処理といったユーザー操作によるイベントではないが表示を更新するようなパターンです。この場合は自らステートが変更されたので再レンダリングしてほしいという意思を伝えるために StateHasChanged を呼び出す必要があります。

イベント発火時 (@bind や @onclick とか)

冒頭でプロジェクトテンプレートの Counter でクリックしてプライベート変数を更新するだけでページの表示が変わって不思議、と書きましたがそのような挙動になる理由はここにあります。

Blazor の ComponentBase ではイベントハンドラーの実行時に StateHasChanged を呼び出しています。

https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Components/Components/src/ComponentBase.cs#L313

Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, object? arg){    var task = callback.InvokeAsync(arg);    var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&        task.Status != TaskStatus.Canceled;    // After each event, we synchronously re-render (unless !ShouldRender())    // This just saves the developer the trouble of putting "StateHasChanged();"    // at the end of every event callback.    StateHasChanged();    return shouldAwaitTask ?        CallStateHasChangedOnAsyncCompletion(task) :        Task.CompletedTask;}

これにより「クリックイベントが発生する」→「プライベート変数を更新する」→「StateHasChanged を呼び出す」→「再レンダリング」→「値が表示に反映される」という流れになっているというわけです。

変数を監視していたり、プロキシになっていたりするわけではなくイベントが発生したら StateHasChanged (=ステート変更通知)が呼び出されているだけという単純な仕組みです。

Parameter が変わったとき

コンポーネントの Parameter に渡される値が変化した場合もステートの変更がありと認識されます。

これも ComponentBase.SetParametersAsync の中で StateHasChanged を呼びだしているので、値の変更があると SetParametersAsync が呼び出されることで結果的にステートの変更があった扱いになります。

https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Components/Components/src/ComponentBase.cs#L277

private Task CallOnParametersSetAsync(){    OnParametersSet();    var task = OnParametersSetAsync();    // If no async work is to be performed, i.e. the task has already ran to completion    // or was canceled by the time we got to inspect it, avoid going async and re-invoking    // StateHasChanged at the culmination of the async work.    var shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &&        task.Status != TaskStatus.Canceled;    // We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and    // the synchronous part of OnParametersSetAsync has run.    StateHasChanged();    return shouldAwaitTask ?        CallStateHasChangedOnAsyncCompletion(task) :        Task.CompletedTask;}

ステートが変更されると (StateHasChanged が呼ばれると) 何が起こるのか

StateHasChanged メソッドを呼ぶと次のような流れで処理が実行されます。

  • ShouldRender
  • BuildRenderTree
  • ステートの変更が子コンポーネントのParameterも変更した場合
    • SetParametersAsync
      • OnParameterSet
      • OnParameterSetAsync
      • ShouldRender
    • BuildRenderTree
  • OnAfterRender (firstRender = false)
  • OnAfterRenderAsync (firstRender = false)

初回と似ていますが異なるのは初期化周りのメソッド(OnInitialized)が呼び出されないことと ShouldRender が呼ばれることです。

ShouldRender メソッドはレンダリングする必要があるかどうかを返すことができるものです。このメソッドのデフォルト実装は常に true を返しますが、カスタムコンポーネントは再レンダリングを抑える目的でオーバーライドできます。

StateHasChanged の詳細

StateHasChanged の実装は ComponentBase クラスにあり、protected として公開されています。

https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Components/Components/src/ComponentBase.cs#L100

このメソッド自体は比較的シンプルでやっていることは次のようになっています。

  • 未レンダリング状態または ShouldRender が true かどうかチェック
  • RenderHandle (のキュー) に RenderFragment を登録する
    • RenderFragment は BuildRenderTree を呼び出す

先ほどの流れで ShouldRender の後に BuildRenderTree が呼び出されると書いていましたが、実際は一度レンダラーのキューに詰めてから BuildRenderTree が呼び出されます。

まとめ

Blazor のライフサイクルやレンダリングでは StateHasChanged が重要な役割となります。

しかしイベントハンドラーのような暗黙的に呼び出されているケースもあり、Blazor の仕組みをあまり知らずに使っていても画面が更新されるので思わぬところでハマる可能性もあります。そういった罠を踏む前にある程度流れを抑えておくことをお勧めします。

]]>
<p>これは <a href="https://qiita.com/advent-calendar/2020/blazor" target="_blank" rel="noopener">Blazor Advent Calendar 2020</a> の25日目のエントリーです。
Blazor Web Assembly を publish すると AssemblyResolutionException でエラーとなる http://www.misuzilla.org/Blog/2020/08/24/AssemblyResolutionExceptionWhenPublishBlazorWebAsssembly 2020-08-24T12:55:00.000Z 2022-05-21T09:11:37.901Z 現象

Visual Studio からの発行や dotnet publish -c Release などを実行したとき、IL Linker 実行中に以下のような AssemblyResolutionException がスローされます。

  Fatal error in Mono IL LinkerC:\Users\Tomoyo\.nuget\packages\microsoft.aspnetcore.components.webassembly.build\3.2.1\targets\Blazor.MonoRuntime.targets(326,5): error : Unhandled exception. Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly: 'SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535' [C:\Users\Tomoyo\Source\Repos\BlazorApp6\BlazorApp6\BlazorApp6.csproj]   ---> Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly: 'SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535'     at Mono.Cecil.BaseAssemblyResolver.Resolve(AssemblyNameReference name, ReaderParameters parameters)     at Mono.Linker.AssemblyResolver.Resolve(AssemblyNameReference name, ReaderParameters parameters)     at Mono.Linker.LinkContext.Resolve(IMetadataScope scope)     at Mono.Linker.LinkContext.Resolve(IMetadataScope scope)     at Mono.Linker.LinkContext.ResolveReferences(AssemblyDefinition assembly)     at Mono.Linker.Steps.LoadReferencesStep.ProcessReferences(AssemblyDefinition assembly)     at Mono.Linker.Steps.LoadReferencesStep.ProcessAssembly(AssemblyDefinition assembly)     at Mono.Linker.Steps.BaseStep.Process(LinkContext context)     at Mono.Linker.Pipeline.ProcessStep(LinkContext context, IStep step)     at Mono.Linker.Pipeline.Process(LinkContext context)     at Mono.Linker.Driver.Run(ILogger customLogger)     at Mono.Linker.Driver.Execute(String[] args, ILogger customLogger)     at Mono.Linker.Driver.Main(String[] args)

原因

IL Linker は依存しているアセンブリ参照をすべて検索して、不要な IL を削っていくというビルドステップです。

この例外(エラー)は依存先のアセンブリを探しているときにアセンブリが見つからなかった場合に発生するもので、通常 NuGet でパッケージ参照されているものに対しては発生しません。

しかし依存パッケージの作り次第では例外が発生する場合があります。例えば Microsoft.CodeAnalysis.Workspaces.Common のようなパッケージを参照すると発生します。

これは Microsoft.CodeAnalysis.Workspaces.CommonSQLitePCLRaw.bundle_green パッケージを PrivateAssets="all" として参照しているため、実際のパッケージにはパッケージ参照として SQLitePCLRaw.bundle_green が含まれないことでアプリケーションから参照したときにパッケージが解決されないのでアセンブリが見つからないということが発生します。

つまりパッケージの依存としては扱わないもののアセンブリを参照はあるという状況で発生します。必須ではないパッケージなどの場合にはこういった構成になります。

解決方法

解決方法としては次の2つの方法があります。

1. 必要なパッケージをプロジェクトで参照する

見つからないといわれているアセンブリが含まれているパッケージをプロジェクトから参照します。

2. IL Linker を無効にする

SQLitePCLRaw.bundle_green のようなものは参照してもエラーとなるのでそういった場合には IL Linker 自体を無効にします。

https://docs.microsoft.com/ja-jp/aspnet/core/blazor/host-and-deploy/configure-linker?view=aspnetcore-3.1

<PropertyGroup>  <BlazorWebAssemblyEnableLinking>false</BlazorWebAssemblyEnableLinking></PropertyGroup>
]]>
<h2 id="現象"><a href="#現象" class="headerlink" title="現象"></a>現象</h2><p>Visual Studio からの発行や <code>dotnet publish -c Release</code> などを実行したとき、
ARM64 Windows 版 Visual Studio Code をビルドする http://www.misuzilla.org/Blog/2020/04/23/BuildVsCodeForArm64 2020-04-23T00:50:00.000Z 2022-05-21T09:11:37.901Z Surface Pro X も発売され早数か月、今や多くの方が ARM64 版 Windows をご利用中かと思いますが(要出典)、Visual Studio Code の ARM64 版はまだリリースされていない状況です。もちろん x86 版をエミュレーションで利用できますが、Electron アプリはパフォーマンス的にかなり不利ですので ARM64 ネイティブなものが欲しいところです。

Visual Studio Code のリポジトリで ARM64 対応がないかなと眺めていたところ ARM64 ビルドをやっていく PR が作られているのを発見し、マージされるのを watch していたのですが先日ついにマージされました。

Add gulp targets, fix build for Windows on Arm. by richard-townsend-arm · Pull Request #85326 · microsoft/vscode

というわけで自分で VSCode リポジトリからビルドしてみたところ、手順が意外とわかりにくかったのでまとめておきます。

ビルド準備 (ツール)

大体は vscode リポジトリの今トリビュートガイドのビルド手順に従って環境を用意します。

  • Windows 10 (x64)
  • Git for Windows
  • Node.js (x64, 10.x 以上 12.x 以下)
  • Yarn
  • Python 2.7
  • Visual Studio 2017 ビルドツール (C/C++ コンパイラー)
    • Visual C++ compilers and libraries for ARM64
  • Visual C++ 2010 Redistributable (x86)

まず、クロスコンパイルする形になるので x64 の Windows 環境が必要になります。もしかするとコンパイラーなどは動くかもしれませんが死ぬほど遅いでしょうし、Node.js もメモリーの都合なのか x64 版を用意するように書かれているので素直に x64 環境でビルドするのがよいでしょう(AzureやAWSで適当にVM立ててビルドするとか)。

Python とコンパイラーは npm install -g windows-build-tools でインストールできます。VSCode のガイドには --vs2015 を指定して Visual Studio 2015 で…のようなことが書いてありますがそのまま Visual Studio 2017 でもビルドできます。

コンパイラーは windows-build-tools ではなく Visual Studio 2017 Build Tools を入れることでも大丈夫です。Visual Studio 2019 の場合でも 2017 のコンパイラーが入っていればビルドできそうな気がします。

さらに ARM64 版ビルドを作る場合には Visual Studio Installer から個別のコンポーネントとして Visual C++ compilers and libraries for ARM64 というものを入れておく必要があります。

Visual C++ 2010 Redistributable (x86) も必要です。これはビルド途中で使われるリソース書き換えツールの rcedit を動かすのに必要です。

ビルド準備 (Node)

ツールがそろったらコードを clone して、yarn でモジュールをインストールします。

git clone https://github.com/microsoft/vscode.git

と、yarn でモジュールをインストールする前にビルド対象のアーキテクチャを環境変数で設定しておきます。

cd vscodeset npm_config_arch=arm64set npm_config_target_arch=arm64

設定したら yarn を実行します。

yarn install

すると多分途中で %USERPROFILE%\AppData\Local\node-gyp\Cache\12.4.0\arm64\node.lib がないというようなエラーになるかと思います。

node-gyp で使われる lib がないということなので次の場所からダウンロードしてきて放り込みます。

https://unofficial-builds.nodejs.org/download/release/v12.15.0/win-arm64/

本当はバージョンを合わせるべきな気がしますが、必ずしも同じバージョンのバイナリがあるわけではないのでそれっぽいバージョンを放り込みます。

ファイルを置いたらもう一度 yarn install を実行すると最後まで通るはずです。

ビルド準備 (VSCode)

ここまできたらあとはビルドするだけですが、その前に少し VSCode のビルド設定を変更しておきます。

まず前提として VSCode リポジトリは Microsoft からリリースされている Visual Studio Code そのものではないのです。

VSCode リポジトリは Code - OSS というオープンソースな Visual Studio Code 部分です。一方 Microsoft がバイナリでリリースしている Visual Studio Code は Code - OSS にブランディングや Marketplace のエンドポイント設定、テレメトリーの有効化などのカスタマイズを行って、プロプラエタリなライセンスでリリースしているものとなっています。Chromium と Google Chrome とかも似たような感じかもしれません。

つまり Code - OSS そのままだと Marketplace を使えないので、API エンドポイントを設定してあげます。そのあたりは VSCode リポジトリを元にコミュニティーバイナリビルドを作っている VSCodium を参考にして product.json を編集します。

diff --git a/product.json b/product.jsonindex 075a1d0ada..093f517644 100644--- a/product.json+++ b/product.json@@ -20,6 +20,9 @@        "licenseFileName": "LICENSE.txt",        "reportIssueUrl": "https://github.com/Microsoft/vscode/issues/new",        "urlProtocol": "code-oss",+       "quality": "stable",+       "extensionAllowedBadgeProviders": ["api.bintray.com", "api.travis-ci.com", "api.travis-ci.org", "app.fossa.io", "badge.fury.io", "badge.waffle.io", "badgen.net", "badges.frapsoft.com", "badges.gitter.im", "badges.greenkeeper.io", "cdn.travis-ci.com", "cdn.travis-ci.org", "ci.appveyor.com", "circleci.com", "cla.opensource.microsoft.com", "codacy.com", "codeclimate.com", "codecov.io", "coveralls.io", "david-dm.org", "deepscan.io", "dev.azure.com", "flat.badgen.net", "gemnasium.com", "githost.io", "gitlab.com", "godoc.org", "goreportcard.com", "img.shields.io", "isitmaintained.com", "marketplace.visualstudio.com", "nodesecurity.io", "opencollective.com", "snyk.io", "travis-ci.com", "travis-ci.org", "visualstudio.com", "vsmarketplacebadge.apphb.com", "www.bithound.io", "www.versioneye.com"],+       "extensionsGallery": {"serviceUrl": "https://marketplace.visualstudio.com/_apis/public/gallery", "cacheUrl": "https://vscode.blob.core.windows.net/gallery/index", "itemUrl": "https://marketplace.visualstudio.com/items"},        "extensionAllowedProposedApi": [                "ms-vscode.references-view"        ],

ビルド

準備ができたらビルドを実行してコンパイルして配布物一式を作成します。

set NODE_ENV=productionyarn gulp vscode-win32-arm64yarn gulp vscode-win32-arm64-archive

vscode-win32-arm64-archive を実行すると .build\win32-arm64\archive\VSCode-win32-arm64.zip という ZIP ファイルが出来上がります。

というわけでこの一式を ARM64 環境へもっていって展開すれば Visual Studio Code 的なものを使えます(インストーラーは2020年4月現在ビルドできません)。

ARM64 版の制約

  • インストーラーがない
  • 対応していない拡張がある (ネイティブバイナリを抱えているものなど)

Microsoft Visual Studio Code との違い

ビルドの準備の途中でも書きましたが、手元でビルドしたものは Microsoft からリリースされるものとは異なります。

  • アイコンが違う
  • 名前が違う (スキームやレジストリ、設定ファイルのディレクトリ名など)
  • ライセンスが違う

名前が違うこともあり設定は Visual Studio Code とは別の場所に保存されるものになります(つまり公式リリースが出た場合でも設定は別になります)。

ライセンスが違うのが実はちょっと罠なので注意が必要です。というのも Microsoft がリリースしている VSCode 拡張のライセンスは Microsoft 公式から配布されている Microsoft Visual Studio Code とともに使うことが許可されているものがあります(例えば Remote とか)ので注意してください。

]]>
<p>Surface Pro X も発売され早数か月、今や多くの方が ARM64 版 Windows をご利用中かと思いますが(要出典)、Visual Studio Code の ARM64 版はまだリリースされていない状況です。もちろん x86 版をエミュレーションで利用できま
.NET Core でスタックトレースからメソッド呼び出しを隠す http://www.misuzilla.org/Blog/2019/12/03/HideMethodCallFromStackTrace 2019-12-03T09:30:00.000Z 2022-05-21T09:11:37.901Z このエントリーはC# その2 Advent Calendar 2019のエントリーです。

突然ですがスタックトレースから何らか自分の書いたメソッドを隠したいなと思ったことはないでしょうか?普段はそんなことを考える必要はないのですがアプリの下回りやミドルウェア的なものを作っているとたまにそういった場面に遭遇します。

例えば ASP.NET Core MVC の ActionFilter のようなフィルターチェーン的なものを作るといったパターンがあるとします。まず次のような感じのフィルター定義があって…

interface IFilter{    void Invoke(object context, Action<object> next);}class AFilter : IFilter{    public void Invoke(object context, Action<object> next)    {        Console.WriteLine("A Begin");        next(context);        Console.WriteLine("A End");    }}class BFilter : IFilter{    public void Invoke(object context, Action<object> next)    {        Console.WriteLine("B Begin");        next(context);        Console.WriteLine("B End");    }}

そのフィルターをつなげて呼び出すというような仕組みです。

// フィルターの一覧を作る (後ろに行くほうが内側 = 外側が AFilter, 内側が BFilter)var filters = new IFilter[] { new AFilter(), new BFilter() };// フィルターに渡す次のアクションの変数(その際最後になるアクションを入れておく)Action<object> next = (context) =>{    // 一番深いところでスタックトレースを取得する    Console.WriteLine(Environment.StackTrace);};// フィルターチェーンを作る// 内側から外に向かってつないでいくforeach (var filter in filters.Reverse()){    // next をラムダにキャプチャーする必要がある    var next_ = next;    next = (context) => filter.Invoke(context, next_);}// 呼び出すnext(null);

このコードの実行結果はおおよそこんな感じになります。

A BeginB Begin   at System.Environment.get_StackTrace()   at Program.<>c.<Main>b__4_0(Object context) in C:\Program.cs:line 9   at Program.BFilter.Invoke(Object context, Action`1 next) in C:\Program.cs:line 44   at Program.<>c__DisplayClass4_1.<Main>b__1(Object context) in C:\Program.cs:line 17   at Program.AFilter.Invoke(Object context, Action`1 next) in C:\Program.cs:line 35   at Program.<>c__DisplayClass4_1.<Main>b__1(Object context) in C:\Program.cs:line 17   at Program.Main() in C:\Program.cs:line 21   (略)B EndA End

結果を見ていただくとわかるのですがスタックトレースに Program.<>c.<Main>b__4_0(Object context) といった呼び出しが現れていますが、単にキャプチャーして渡すだけのメソッドなのでユーザーにはほぼ意味のないものです。とはいえコード上でラムダを挟んでいるので出てくるのは当然です。

そこで何とかしてこのような些末な呼び出しをスタックトレースから隠すためのハックを今回ご紹介します。

案1. StackTraceHiddenAttribute を使う

.NET Core 2.0 には StackTraceHiddenAttribute という属性が追加されていて、その属性のついたメソッドはスタックトレースから除外されるようになっています。

ということはこれを使えば解決しそうですが残念ながらこの属性は internal です。効果を考えたらそんなカジュアルに使われても困るので内部向けに用意したというところでしょうか。

とはいえそれでも動的アセンブリ生成なら無理やり属性を引っ張り出してくっつけることができるはず…!イメージとしては次のようなヘルパークラスの InvokeNext の部分を動的に作ってうまいことするような感じです。

/// <summary>/// 次のフィルター呼び出しと、フィルターのメソッドを保持するクラス。/// 以前のラムダのキャプチャーと同等。/// </summary>public class InvokeHelper{    /// <summary>フィルターのメソッド</summary>    public Action<object, Action<object>> Invoke;    /// <summary>次のフィルターの呼び出しとなるデリゲート</summary>    public Action<object> Next;    public InvokeHelper(Action<object, Action<object>> invoke, Action<object> next)    {        Invoke = invoke;        Next = next;    }        [StackTraceHidden]    public void InvokeNext(object context)    {        Invoke(context, Next);    }}

ということでILをペコペコ書きます。

そしてフィルターチェーンを作るところをヘルパー経由に書き換え。

foreach (var filter in filters.Reverse()){    next = new InvokeHelper<object, Action<object>>(filter.Invoke, next).GetDelegate();}

そして実行すると…。

A BeginB Begin   at System.Environment.get_StackTrace()   at Program.<>c.<Main>b__4_0(Object context) in C:\Program.cs:line 9   at Program.BFilter.Invoke(Object context, Action`1 next) in C:\Program.cs:line 44   at Program.AFilter.Invoke(Object context, Action`1 next) in C:\Program.cs:line 35   at Program.Main() in C:\Program.cs:line 21   (略)B EndA End

ばっちり BFitlerAFitler の間にあった呼び出し行が消えました!めでたしめでたし。

案2. 動的メソッド生成

案1でめでたしめでたしとなるかと思いきや、実は案1を試している間に気づいたのですが StackTraceHiddenAttribute をつけなくても消えます。案1の下記の二行を削除してみても結果は変わりません。

var attrStacktraceHiddenAttribute = Type.GetType("System.Diagnostics.StackTraceHiddenAttribute");method.SetCustomAttribute(new CustomAttributeBuilder(attrStacktraceHiddenAttribute.GetConstructor(Array.Empty<Type>()), Array.Empty<object>()));

どうも CoreCLR の中も少し調べてみたのですがよくわからず、動的生成されたメソッドがスタックトレース的に何らかの特別扱いされることがあるようです。

というわけで動的にメソッドを定義できれば理屈は謎ですが消えますし、アセンブリを生成しなくとも DynamicMethod で事足ります。

案3. [MethodImpl(MethodImplOptions.AggressiveInlining)] を使う

StackTraceHiddenAttribute のあたりのコードを見ていて気が付いたのですが .NET Core 3.0 以降では [MethodImpl(MethodImplOptions.AggressiveInlining)] がついているかどうかもチェックしています

これは通常JITでインライン化されるとスタックトレースから消えてしまうけれども、Tiered Compilation の Tier 0 ではインライン化されないので表示されてしまったりして一貫性がないので属性がついているものは丸ごと非表示にしてしまおうということのようです。

ということで裏技っぽいですが [MethodImpl(MethodImplOptions.AggressiveInlining)] をつければ非表示になります。内容的にもインライン化されたところで困るものでもないですしよさそうです。

ヘルパークラスのイメージとしてはこんな感じです。

public class InvokeHelper{    /// <summary>フィルターのメソッド</summary>    public Action<object, Action<object>> Invoke;    /// <summary>次のフィルターの呼び出しとなるデリゲート</summary>    public Action<object> Next;    public InvokeHelper(Action<object, Action<object>> invoke, Action<object> next)    {        Invoke = invoke;        Next = next;    }        [MethodImpl(MethodImplOptions.AggressiveInlining)]    public void InvokeNext(object context)    {        Invoke(context, Next);    }}

まとめ

ということでこれをまとめると .NET Core 3.0 以前では動的メソッド定義、それ以外では AggressiveInlining をつけておくというのがよさそうです。シンプルですし、もし挙動が変わってスタックトレースが出るようになっても AggressiveInlining がついているだけなのでプログラムの挙動には影響を与えないのもよいですね。

このハックは実際にMagicOnionのフィルター周りに入れていてスタックトレースの見やすさを向上させています。

ハックなし

Before

ハックあり

After

スタックトレースは開発において重要な情報なのでノイズは少なければ少ないほうが望ましいですし、アプリケーションの基盤に近い部分ではそういった開発体験を考慮しておくのは重要だと思います。今回のようなハックも機会があればお役立てください。

なお、あくまでハックなのでいつまで効果があるかといった点は保証できませんのであらかじめご了承ください。

]]>
<p>このエントリーは<a href="https://qiita.com/advent-calendar/2019/c-sharp-2" target="_blank" rel="noopener">C# その2 Advent Calendar 2019</a>のエントリーです
マンモス展 http://www.misuzilla.org/Blog/2019/11/04/MammothTen 2019-11-04T14:30:00.000Z 2022-05-21T09:11:37.901Z DSC03921.jpg

DSC03927.jpg

DSC03938.jpg

4万年以上前の仔ウマが丸ごと出てくるのすごい。

音声ガイドを東山奈央さんがナビゲートされていたので折角なので借りてみました。ジュニアガイドとはいえTips的な話もあり、比較的ライトな雰囲気はこれはこれで良かったです。

]]>
<p><a data-flickr-embed="true" href="https://www.flickr.com/photos/mayuki/49012305348/in/dateposted/" title="DSC03921.jpg" target="_blank" r
HttpClient の StringContent は charset を付ける http://www.misuzilla.org/Blog/2019/11/04/HttpClientStringContentCharsetParameter 2019-11-04T14:00:00.000Z 2022-05-21T09:11:37.901Z tl;dr
  • .NET の HttpClientStringContent は自動で Content-Typecharset=<encoding> を付ける
  • 不要な場合は StringContent.Headers.ContentType を手で設定しなおす必要がある

はじまり

HttpClient でとある API (POST) を呼び出すとなぜか動かない… curl で同じ内容を投げると動くのに…という相談を受けて、そんな不思議なことが?と思って調べてみたのが始まりでした。

.NET の HttpClient, StringContent は自動で charset を付ける

curl で動いているなら .NET の HttpClient が投げるものが何か違うのではと思って、リクエストやリクエストを作っているところを確認してみました。

https://github.com/dotnet/corefx/blob/dbf74fac15dad697ae7e7c4d88327c0478f465ef/src/System.Net.Http/src/System/Net/Http/StringContent.cs#L30-L34

// Initialize the 'Content-Type' header with information provided by parameters.MediaTypeHeaderValue headerValue = new MediaTypeHeaderValue((mediaType == null) ? DefaultMediaType : mediaType);headerValue.CharSet = (encoding == null) ? HttpContent.DefaultStringEncoding.WebName : encoding.WebName;Headers.ContentType = headerValue;

StringContent クラスのコンストラクターのコードではメディアタイプとともに CharSet プロパティを設定したものをヘッダーの ContentType プロパティに設定しています。この CharSet プロパティは Content-Type の charset 指定になります。

つまり StringContent クラスを使って投げるリクエストを作るとデフォルトで application/json; charset=utf-8 のような Content-Type で送られるということになります。

ContentType を再設定する

charset パラメータが必ずついてしまうということが分かったので、付けずにリクエストを送信したいという場合にはこれを変える必要があります。

といってもやることは簡単で後から手動で ContentType プロパティを再設定してあげれば大丈夫です。

content.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue("application/json");
]]>
<h2 id="tl-dr"><a href="#tl-dr" class="headerlink" title="tl;dr"></a>tl;dr</h2><ul> <li>.NET の <code>HttpClient</code> の <code>StringContent
Real World .NET Core on Kubernetes という話をしました http://www.misuzilla.org/Blog/2019/10/21/RealWorld.NETCoreOnKubernetes 2019-10-21T03:50:00.000Z 2022-05-21T09:11:37.901Z 10月18日に AWS .NET Developer User Group 勉強会 #2 にて Real World .NET Core on Kubernetes というセッションで話させていただきました。このエントリーはそのフォローアップです。

Lifebear のサーバーアプリケーション (.NET Core) を Amazon EKS / Kubernetes 環境を構築してリリースするまでにメモしていたポイントまとめた形です。

「EKS/AKS/GKE で Kubernetes 立てて、.NET Core アプリをデプロイしてみた(使ってみた)」より一歩進んだ話が欲しいと思っていたのでそれがテーマでした。

.NET Core のアプリケーションを Kubernetes の上で動かしたという国内の実例というのは少なくて、自分で構築する際にも不安になったので、今後 Kubernetes でシステムを構築しようという人の参考になってほしいなと。…と思って書いたらちょっと盛りすぎてしまったので、Kuberentes 寄りの話と .NET Core 寄りの話を分ける方がよかったかなというのが反省点ですね。

知見いろいろ

ネタ帳にはあったものの時間の関係上話せなかったスライドに書いた以外のこともこの際なので書いておきます(主に Kubernetes)。

Docker build は BuildKit 使おう

BuildKit でビルドすると高速かつキャッシュできるので有効にするのがおすすめです。特に NuGet パッケージのリストアに効果があります。

環境変数で DOCKER_BUILDKIT=1 を指定して Dockerfile でおまじないを追加するだけです。

# syntax = docker/dockerfile:experimental...RUN --mount=type=cache,target=/root/.nuget/ \    dotnet restore "WebApplication1.csproj" -c Release -o /app

Kuberentes のバージョンアップは大変

Kubernetes のクラスターのバージョンは1マイナーバージョンアップごとに上げることになります。これはバージョンジャンプが大変なので日々追いかけておくつもりが必要ということです。

kubectl や kubelet のバージョンポリシーもあり、例えば kubelet は api-server に対して前2マイナーバージョン、kubectl はプラスマイナス1マイナーバージョンといった制約がありますのでどのみち大ジャンプが難しいということです。

ノードにはアプリケーション以外のものも動いている

通常ノードを落とす時には kubectl drain するのが普通です。ある時 kubectl drain しなくても kubectl cordon してアプリを再デプロイして Pod を別なところに移せばそのままノードを落とせるのではと思い、やってみると CoreDNS 等のサービスがいなくなって死にました(もちろん対象のノードにいなければ無傷)。それはそう…。

Amazon EKS 用のノードは AMI は作らない

基本カスタマイズは UserData で起動時に何とかします。AMI作っても絶対管理できないですし、ノードはコンテナーを動かすためだけの代物なので。

/etc/eks/bootstrap.sh というものに kubelet のパラメータなどを渡せるのでそこでやりましょう。

デプロイ時にサービスから外れるのを待つ

Pod のシャットダウンと Service から外れるタイミングが異なるため、先に Pod がシャットダウンすると接続しようとしてしまうという話。preStop フックで数秒待って外れてからシャットダウンへ進むようにします。

apiVersion: apps/v1kind: Deploymentmetadata:  name: myappspec:  template:    spec:      containers:      - name: myapp        lifecycle:          # シャットダウン前に少し待たないと Service から削除されるより先に Pod が終了してしまうことがある          # https://qiita.com/superbrothers/items/3ac78daba3560ea406b2          preStop:            exec:              command: ["sh", "-c", "sleep 5"]

Amazon EKS では Private Endpoint を使う

Kubernetes API のエンドポイントをインターネット側からアクセスできないようにする設定が追加されたので特にプロダクション環境などでは有効にするのがおすすめです。

ただし、当然外部サービスから Kubernetes API を呼び出す難易度が上がるのでその点は考慮する必要があります(その制約で Azure Pipelines の Environments を使えなかった)。

VPC のプライベートアドレスが枯渇

Amazon EKS では Pod に対してプライベートIPを割り当てるのですが、普通の EC2 インスタンスの気持ちで小さめのサブネットにしていると Pod がぽこぽこ増えた時に困ったことになります。

アドレス空間はある程度余裕を持った形にしておくのがよいです。

OOMKilled や CrashLoopBackOff を監視する

Kubernetes 上で発生したエラーイベントを監視しておきましょう。Datadog では Kubernetes のイベントも監視できるのでひっかけて通知するようにしていると異変に気付きやすくなります。

CrashLoopBackOff は発生していても、以前の Pod にはアクセスできるので他のきっかけがないと気づきにくくなります。

コンテナーの中で docker を動かす

Docker イメージを CI でビルドするのに CI のエージェントが Docker で動いているとそのままでは Docker 動かない問題にあたります。そこで俗にいう Docker in Docker (dind) です。

イメージをビルドするだけなので、ほかにも img とか Rootless Docker とか kaniko とか検討できるものはあるのですが無理しない方針で無難に Docker in Docker にしました。

privileged: true にしないといけないのでそこは微妙なのですが、今ならもしかすると Rootless でうまいこと privileged: true しなくてもいけるかもしれません。

Kubernetes の API を試すときは Pod に入って curl するのがオススメ

Kubernetes の API にアクセスする場合 ServiceAccount の設定をミスっていたりするとうまくアクセスできなくなったりするのですが、これをアプリデプロイして試すのは大変なので kubectl exec podname -it /bin/bash で入って curl で叩くのがおすすめです。

Pod が偏る

通常、Pod をデプロイするときには Kubernetes がリソースの具合を見て適度にバランスしてスケジューリングします。一方でデプロイ後に再スケジュールはしてくれません。

例えば、2つあるうちの1つのノードが死んだ場合はそのノードで動いていた分の Pod が生きているノードで起動されなおします。そして、その後ノードが復活してきたとしても Pod たちは元のノードに帰っていったりはしません。偏ったままです。

これを解決する手っ取り早い方法としては再デプロイかレプリカ数を上げたり下げたり、要するに Pod の再作成です。

他には Kubernetes の拡張として Descheduler という再スケジュールしなおすものが開発されているのでこちらの利用を検討してもいいかもしれません(が、鋭意開発中っぽい雰囲気があります)。

まとめ

振り返ってみると大変なのはアプリそのものよりは中小規模の Kubernetes クラスターを構築、運用するノウハウというところが大きい気がしました。

繰り返しにはなりますが、.NET Core を Kubernetes で実際に動かしてリリースまでもっていっているという事例の参考になって、事例がもっと出てきてほしいですね。

というわけでライフベアではほとんど好き勝手構築させていただいたので大変感謝しています。もし .NET Core (C#) + Kubernetes な環境ににご興味のある方はご一報いただくか、直接コンタクトしていただくと良いかと思います。

そして Cysharp では C# (サーバー、クライアント) に関するご相談を受け付けておりますのでお困りのことがあればぜひお問い合わせください

]]>
<p>10月18日に <a href="https://jaws-dotnet.connpass.com/event/146583/" target="_blank" rel="noopener">AWS .NET Developer User Group 勉強会 #2</a>
C# で null 許容型あれこれ http://www.misuzilla.org/Blog/2019/09/25/NullableReferenceTypes 2019-09-25T03:30:00.000Z 2022-05-21T09:11:37.901Z .NET Core 3.0 のリリースとともに C# 8.0 がきて、null 許容型 (nullable reference types) を使えるようになったので使ってみたメモです。

未初期化だけど後で初期化するパターン

コンストラクターではまだ初期化しないのだけど、いずれフレームワークから初期化するので API を利用する側はほぼ null を扱わない、けど内部の初期状態は null なのを許してほしいというケースはまあよくあります。Kotlin の lateinitTypeScript の definite assignment assertion みたいなやつですね。

C# ではズバリそれっぽいものはないので default! を初期値として代入しておけばよいです。

lateinit とかもそうですが適当に使うと治安が最高に悪くなるので気を付けたほうがいいやつです。

ジェネリクス

class 制約のないジェネリクスの場合 ? をつけて null 許容型であると宣言できません。そのため null を扱うかもしれない、というケースを宣言するには属性を使用することになります。

逆を言えば where T: classwhere T: unmanaged のような制約をつけておけば T? と null 許容型を素直に書くことができるので書けるときは書いてしまうのが手です。

AllowNull 属性と DisallowNull 属性 (事前条件)

例えば次のようなメソッドの引数で参照型のときは null 渡したいケースがあります。

そのままでは非許容なので null が来てもよいということをコンパイラーに伝える必要があるので、 AllowNull 属性を使用します。

逆に null が入ってきてほしくないというのを明示することもできます。例えば先ほどのコードをもう一度。

このような場合は DisallowNull 属性を使用すると null を許さないということを明示できます。

DisallowNull 属性は .NET Core のコードであれば IEqualityComparer<T>.GetHashCode などに見られます。

MaybeNull 属性と NotNull 属性 (事後条件)

次は値型ならデフォルト値、参照型なら null が返るようなメソッドを扱うケースです。GetValueOrDefaultDefaultIfEmpty のようなものでよくありますね。

このようなケースをカバーするには MaybeNull 属性を使うと戻り値が null になる可能性があることを表せます。なお、戻り値に対しての属性なので return: です。

逆に絶対 null を返さないということがわかっている場合には NotNull 属性で宣言できます。例えば次のようなケースです。

プロパティ

AllowNull 属性と DisallowNull 属性 (事前条件)

プロパティの setter で null を許可したかったり拒否したかったりというケースがあるかもしれません。例えば setter で null はセットできるけれども null は返ってこないような API です(というのはあまり想像しづらいですが…)。

ちなみにコンパイラーが中途半端に賢いので Value = null; の後に Value を使用しようとすると怒られます(が、実際は null じゃないはずなのでなんか変な気がします)。

逆は例によって DisallowNull です。null が返るかもしれないけれど null をセットすることは許さないようなケースです。Microsoft のドキュメントのサンプルではこんな感じです

初期化時は null が返るけど、ユーザーがセットすることは許さないみたいな感じですかね。

その他のケース

NotNullWhen 属性 と MaybeNullWhen 属性 (事後条件)

TryGetValueTryParse のような、成功時には out 引数で値を返すがそれ以外では null を返し、成否は戻り値にするというケースがあります。例えば次のようなコードがあるとします。

この MyDictionary を使う場合は次のようなコードになるわけですが、その際 null の扱いも上手く処理されてほしいわけです。

そこで戻り値によって nullable かどうかを伝える NotNullWhen という属性が用意されています。この属性を付けると「nullable な型が場合によってはその後 non-nullable 確定できるかも」という情報をコンパイラーに伝えられます。

他の使い道としては String.IsNullOrEmpty みたいなもので使えます(使われています)。 IsNullOrEmpty も後のフロー解析に影響を与えてほしい側面を持っているのでピッタリですね。

逆の意味を持つのが MaybeNullWhen 属性で non-nullable につけた MaybeNullWhen(false)NotNullWhen(true) と同じになります。

これらの属性の使い分けですが

  • 引数に渡す/出てくる値 (out, ref) が前提として null 非許容型 (T) かもしれないなら MaybeNullWhen 属性
  • 引数に渡す/出てくる値 (out, ref) が前提として null 許容型 (T?) かもしれないなら NotNullWhen 属性

例えば TryGetValue のようなもので MaybeNullWhen 属性を使った場合、前提として non-nullable になると out var で宣言したローカル変数が non-nullable になり、if のようなフロー解析から外れたときに non-nullable (ただし null が入っている可能性がある) という状況になります。

同様に IsNullOrEmpty のようなもので MaybeNullWhen 属性を使った場合、引数は前提として non-nullable なので String? な値を引数に渡すたびに ! を付けてあげないといけないことになります。

…と、ここまで読むと MaybeNullWhen 属性を使わなくてもほとんどのケースでは NotNullWhen 属性で事足りるのではと思うのですが、制約なしジェネリクスの型パラメータに ? を付けて null 許容型とすることはできないのでその場合には MaybeNullWhen を使う必要があります。

NotNullIfNotNull 属性 (事後条件)

入力が null ではなかったら絶対に null ではないということがわかっていて、コンパイラーに伝えたいというケースに使えるのが NotNullIfNotNull 属性です。何を言っているのかみたいな名前…。

具体的なケースであれば例えばエスケープ処理のようなもので、文字列を受け取るけれども null が来たら null を返すが、それ以外は絶対値が返るようなメソッドです(その仕様がいいかどうかはさておき)。

まとめ

属性を書き始めると書き味が悪くなるのと IntelliSense などでシグネチャヒントを見ただけではわからないという問題が起きやすくなるので、新規に書き起こすものは極力属性を書かなくて済むような API にするほうが望ましいように感じました。

例えば NotNullIfNotNull 属性のようなものなどはそもそも引数で null を受けない、TryGetValue などは戻り値と out を使わないで null 許容型をそのまま返すといった形にできます。

属性は既存のコードを壊さず null 許容型との相互運用のためにアノテーションをつけていくものというぐらいの認識がいいかもしれません。まあジェネリクスが厳しいのですが…。

]]>
<p>.NET Core 3.0 のリリースとともに C# 8.0 がきて、null 許容型 (nullable reference types) を使えるようになったので使ってみたメモです。</p> <h2 id="未初期化だけど後で初期化するパターン"><a href="#未
Google (GSuite) を IdP として Azure Active Directory (Office 365) にサインインする http://www.misuzilla.org/Blog/2019/07/26/FederatingGSuiteWithAzureActiveDirectory 2019-07-25T15:00:00.000Z 2022-05-21T09:11:37.901Z Azure Active Directory(以下Azure AD)とGoogleのアカウント連携について調べるとAzure AD (Office 365)をIdPつまりユーザー情報のソースとしてGoogleにサインイン (SSO) する、ユーザープロビジョニングを行うといった設定についてのドキュメントが見つかります。

一方で逆のパターン、つまりGoogle (GSuite, Cloud Identity)をIdPとしてAzure ADやOffice 365にサインインしたり、ユーザー情報を同期するパターンについての説明は少ないので折角ですしメモもかねて残しておきます。

ちなみにAzure AD B2Bの機能としてGoogleをアプリとして登録することでAzure ADにゲストとして登録する機能というのもあるのですがそれとは別です。

公式ドキュメント(Office 365 クラウド アプリケーション - G Suite 管理者 ヘルプ)ushiyasanさんのブログエントリー(GoogleアカウントでOffice365(Azure AD)にSSOログイン)を参考にして大体設定しています。というわけで基本は公式のドキュメントの手順に沿って設定していきます。

Google の IdP 設定を取得する

公式ドキュメントの手順1で、ここはドキュメントそのままです。まずは Google の管理コンソールから設定のための情報を取得します。

特権管理者で管理コンソールにログインして セキュリティシングルサインオン (SSO) の設定 を開いて、各種情報のメモと証明書のダウンロードを…と思ったのですが実はSAMLアプリケーション (Office 365)の追加の手順の途中でも表示されるのでそっちからでもよいです。

特権管理者で アプリSAML アプリサービスやアプリをドメインに追加 をクリックして、SAMLアプリを追加する画面を表示します。SAML アプリケーションで SSO を有効にする という画面が出るのでOffice 365 で検索します。Microsoft Office 365 というアプリが見つかるのでそれをクリックして進めます。

ステップ2 Google IdP 情報 という画面が表示されるとGoogleをIdPとして利用するために必要な情報が表示されますので下記の3つの情報を記録しておきます。

  • SSO の URL
  • エンティティの ID
  • 証明書 (ダウンロード)

Azure AD をフェデレーテッドモードに変更する

公式ドキュメントの手順 2で、ここからはAzure AD側の設定を行います。先のブログエントリーでも書かれているのですが、手順 2についての手順はPowerShellで設定してぐらいのざっくりとしたことしか書かれていません。

やることは次の二点です。

  • 既存ユーザーの ImmutableId を設定する
    • 「ユーザーの普遍の ID を設定します」と書かれている…
  • Azure ADの認証モードを Managed から Federated に変更して、SAML認証を行う設定をする

PowerShell モジュールをインストールし、接続する

Azure ADの設定にはPowerShellで接続できる必要があるのでその準備です。

まずはモジュールをインポートします。

Import-Module -Name MSOnline

次にサービスに接続します。管理権限のあるユーザーで接続してください。

PS> Connect-MsolServicePS> Get-MsolDomainName                         Status   Authentication----                         ------   --------------example.com                  Verified Managedexamplecom.onmicrosoft.com   Verified Managed

既存ユーザーの ImmutableId を設定する

GoogleはAzure AD側のユーザーと突き合わせるためにAzure ADのユーザープロパティ ImmutableId を使用します。

ImmutableId に使用する値は特に理由がなければメールアドレス (例: `user@example.com`) にします。他でもできるはずですが、Google側が受け入れるのがメールアドレスか姓名ぐらいしかないようです。

Azure AD単体で使っている状態ではユーザーに対して設定されていないので既存のユーザーに関しては何らかの方法で設定してあげます。まあ何らかの方法というか Set-MsolUser コマンドレットですね。

Set-MsolUser -UserPrincipalName alice@example.com -ImmutableId alice@example.com
Get-MsolUser | ?{ $_.UserPrincipalName.EndsWith("example.com") } | %{ Set-MsolUser -UserPrincipalName $_.UserPrincipalName -ImmutableId $_.UserPrincipalName }

後々ユーザープロビジョニングでGoogle側からやってきたユーザーに関しては自動で設定されます。

Azure ADの認証モードを Federated に変更し、SAML認証の設定をする

Azure ADの認証モードを Federated に変更し、SAML関連の設定をおこなうことで認証をAzure AD以外の場所で行うようにします。

設定には Set-MsolDomainAuthentication コマンドレットを使用して、先ほどのIdP情報を指定します。

# "SSO の URL" と書かれていた項目$ssoUrl = "https://accounts.google.com/o/saml2/idp?idpid=<IdPId>"# "エンティティ ID" と書かれていた項目$entity = "https://accounts.google.com/o/saml2?idpid=<IdPId>"# 対象のドメイン名$domain = "example.com"# 証明書$cert = "ダウンロードした証明書の -----BEGIN CERTIFICATE----- から -----END CERTIFICATE----- までの「間」を改行をなしで一行で"Set-MsolDomainAuthentication -Authentication Federated  -DomainName $domain -ActiveLogOnUri $ssoUrl -PassiveLogOnUri $ssoUrl -IssuerUri $entity -LogOffUri $ssoUrl -SigningCertificate $cert -PreferredAuthenticationProtocol SAMLP
PS> Get-MsolDomainName                         Status    Authentication----                         ------    --------------example.com                  Federated Managedexamplecom.onmicrosoft.com   Verified  Managed

下記のようなエラーが発生した場合には指定したドメインがAzure ADのプライマリドメインとなっていると思うので onmicrosoft.com や他のドメインにプライマリを一度切り替える必要があります。

Set-MsolDomainAuthentication : You cannot remove this domain as the default domain without replacing it with anotherdefault domain. Use the the Set-MsolDomain cmdlet to set another domain as the default domain before you delete thisdomain.

Google (GSuite) の SAML アプリケーション設定

再びGoogleの管理コンソールに戻ってステップを進めます。

Microsoft Office 365 の基本情報 はそのままで「次へ」で進めます。

サービス プロバイダの詳細 は「署名付き応答」にチェックを入れて「次へ」で進めます。

属性のマッピング は「IDPEmail」を「基本情報」「メインのメールアドレス」を選択して「次へ」で進めます。

これでアプリの基本設定は完了ですがまだ有効になっていないので動きません。

Microsoft Office 365 アプリを有効化する

SAMLアプリを追加しただけでは有効になっていないのでそれを有効化する必要があります。

アプリSAML アプリ でアプリの一覧から Microsoft Office 365 を選択して、右上の「サービスを編集」をクリックします。

「サービスのステータス」で「オン (すべてのユーザー)」を選択します。

サインインをしてみる

ここまでの設定でAzure ADにGoogle経由でサインインできるようになっているはずでしょう。多分。

Cookieが残っていると挙動が怪しいのでプライベート ブラウジングやIn Private ウィンドウなどでテストするのをオススメします。

トラブルシューティング: Google側で 400 エラーが発生する

  1. That’s an error.
    Error parsing the request, No SAML message present in request That’s all we know.

Azure ADから一度Googleにリダイレクト後、上記のようなエラーメッセージが出て進まない場合には Set-MsolDomainAuthentication コマンドレットで設定時に -PreferredAuthenticationProtocol SAMLP が指定されてなく、SAMLではなくWSで認証をかけている可能性があります。

トラブルシューティング: 無限サインインループ

無限サインインループになった場合はAzure AD側に ImmutableId が設定されていない可能性や設定が間違っている可能性があるのでそちらを確認してください。

ユーザー プロビジョニングを設定する

サインインできるようになった後はGoogle側からAzure AD側へユーザー情報を同期するためにユーザープロビジョニングの設定を行います。ユーザープロビジョニングによってGoogle側にユーザーを追加するとAzure ADにユーザーを自動で作成するといったことが可能になります。

ユーザープロビジョニングの設定は アプリSAML アプリ でアプリの一覧から Microsoft Office 365 を選択し、ユーザー プロビジョニング を開きます。

「ユーザー プロビジョニングを設定」をクリックするとダイアログが開くので「承認」をクリックします。

Azure AD側でアクセス許可の確認が表示されるので「組織の代理として同意する」にチェックして「承諾」でGoogleの管理コンソールに戻ります。

最後に属性のマッピング設定画面が表示されるので onPremisesImmutableId基本情報 > ユーザー名 にマッピング設定して「次へ」で完了です。

後はしばらく待つとGoogle側のユーザー情報がAzure AD側へと反映され、ユーザーが作成されたりします。

おわりに

というわけでGoogle (GSuite)をユーザー情報のソースとしてAzure ADを利用できるようになることでOffice 365やAzureに関連したサービス(AzureやAzure DevOps等)などのサインインを一元化できるのでよいのではないでしょうか。

すでにGSuiteを使用している環境ではAzure ADをIdPにするのが怖かったり抵抗がある、めんどくさいということもあるのでこの構成もオススメです。

]]>
<p>Azure Active Directory(以下Azure AD)とGoogleのアカウント連携について調べるとAzure AD (Office 365)をIdPつまりユーザー情報のソースとしてGoogleにサインイン (SSO) する、ユーザープロビジョニングを行うとい
App ServiceやVPSからAzure Kubernetes ServiceとNetlifyへ移行した http://www.misuzilla.org/Blog/2018/11/15/MoveToAKS 2018-11-15T14:35:00.000Z 2022-05-21T09:11:37.901Z 今までこのブログを含むサイトといくつかのサイトがApp Serviceの上で動いていて、TiarraやTIG等IRC系のものはVPSやVMで動いていたのですが、まるごとAKSとNetlifyに移行しました。

  • AKS
    • Tiarra x 3 (IRCnet, Freenode, TIG)
      • fluentd
    • stunnel x 3
    • TweetIrcGateway
    • platformstatus.io
      • cert-manager + ingress-nginx (HTTPS)
  • Netlify
    • このブログ (というかサイト)

今まで .NET なアプリがあったりしたのでWindowsなApp Serviceでしたが、.NET Core化して全部Dockerに乗せた結果Windowsで動いているものがなくなってしまった…かなしい。

ちなみに最初はGKEを考えたのですが、Load Balancerが月2,000円ぐらいかかるのでAKSにしました。まあk8sに乗せてればどこかに行くのも簡単でしょうし(慢心)。

しかしYAMLこねこね結構めんどくさかったので、改めてApp Serviceのお手軽さはいいですねと感じたのでした。

]]>
<p>今までこのブログを含むサイトといくつかのサイトがApp Serviceの上で動いていて、TiarraやTIG等IRC系のものはVPSやVMで動いていたのですが、まるごとAKSとNetlifyに移行しました。</p> <ul> <li>AKS<ul> <li>Tiarra x
MoreLocale 2で日本語設定にしても再起動すると戻る問題 http://www.misuzilla.org/Blog/2018/09/17/RevertLanguageWhenRebooted 2018-09-17T13:35:00.000Z 2022-05-21T09:11:37.901Z Androidの端末を使う際に何らかの事情(とは)でMoreLocale 2というアプリを使用して言語設定を日本語に設定することがあります。

ところが設定しても再起動やユーザースイッチすると言語が英語に戻されてしまうという問題が発生しました。この現象自体は割とよくあるようです。

とはいえ不便なので試行錯誤したところ手元の環境ではMoreLocale 2で日本語に設定した後、第二言語設定にEnglishを追加したところ戻らなくなりました。もしこの現象で困っている人は試してみるともしかしたらうまくいくかもしれません。というメモです。

]]>
<p>Androidの端末を使う際に何らかの事情(とは)でMoreLocale 2というアプリを使用して言語設定を日本語に設定することがあります。</p> <p>ところが設定しても再起動やユーザースイッチすると言語が英語に戻されてしまうという問題が発生しました。この現象自体は割と
KumoDictionary: クラウドのNoSQLをIDictionary<TKey, TValue>として使う http://www.misuzilla.org/Blog/2018/07/15/IntroducingKumoDictionary 2018-07-14T15:00:00.000Z 2022-05-21T09:11:37.901Z KumoDictionary というライブラリーを公開しました。

このライブラリは何かというと IDictionary<TKey, TValue> を操作すると、裏側ではネットワークの先にあるNoSQL的なもの、例えばAzure Storage TableやAmazon DynamoDBなどへ透過的に記録、読み取りを行うものです。

作った動機

Azure FunctionsやAWS LambdaのようなServerlessアプリ、もしかしたら単なるCLIで小さいツールといったものを作るということはよくありますが、その際にちょっとしたデータを保存したいということも同時によくあります。

例えばFunctionsでWebhookを作ったとして、特定のパラメータに対して最後にアクセスされた時刻を記録する…などなど。

じゃあデータ保存しましょうとなると、ファイルは永続化できる書き込み先はないし、Redisやデータベース的なデータアクセスはサーバーを用意するのもオーバーキル。そうなるとお手軽なのはNoSQL系かとなるのですが、いざSDKを入れて使ってみるとテーブルの設計と呼び出しがめんどくさいものです。

欲しいのは雑に Dictionary<TKey, TValue> が永続化されてくれるような程度でいいのですがーという気持ちからスタートしています。

IDictionary ということはブロッキングだし筋があまりよくないのではという疑問はごもっともですが、クラウド内であればレイテンシーも小さいことやそもそも低負荷のアプリであることをこのライブラリは期待しています。

パフォーマンスを真面目に気にする段階になったときは、Asyncメソッドをご利用いただくか、そもそも普通にSDKでアクセスしてください。

使い方

使い方はとりあえず Azure Storage Table であれば簡単です。

まずポータルやCLIでストレージアカウントを作成しておきます。

次に KumoDictionary.AzureStorageTable NuGetパッケージをインストールします。

そしてプログラムの頭で一度だけデフォルトのバックエンドの設定を行います。

using KumoDictionary;using KumoDictionary.Provider;// Set backend provider for Microsoft Azure Storage Tablevar tableName = "MyTestTable";var connectionString = "DefaultEndpointsProtocol=https;AccountName=...";KumoDictionaryAzureStorageTableProvider.UseAsDefault(connectionString, tableName);

あとは KumoDictionary<TValue> クラスを使うだけです。ちなみにこれは KumoDictionary<string, TValue> の別名です。

// KumoDictionary を作るときにディクショナリーの名前を付けてあげるvar dict = new KumoDictionary<MyClass>("dictionaryName1");// インデクサで値をセットdict["key1"] = new MyClass { ValueA = 1234 };dict["key2"] = new MyClass { ValueA = 5678 };// インデクサで値を取得Console.WriteLine(dict["key1"].ValueA); // => 1234

つまり最初のProviderの設定以外は普通のDictionaryっぽく扱えるのです(もちろん一部機能は実装されていなかったりはしますが)。

DynamoDB の場合

DynamoDBの場合にはテーブルの作成があらかじめ必要ですが、逆を言えばそれぐらいです。

裏側

裏側は単純にMessagePackでシリアライズして適当にストアに保存しているだけです。

ただ、値がもしデータストア側がネイティブで対応している型の場合にはそのままそれを使います。例えばAzure Storage TableやDynamoDBはStringを直接扱えるのでそのまま突っ込むことで管理画面や別なツールからもフレンドリーになるのです。

またキーの方も独自のクラスを使えますが、クラスの構造(プロパティやフィールドの名前、型、順番、数)が変更されると壊れるのであまりお勧めはできません。

雰囲気的にはRedis向けのCloudStructuresに近いですし、Redisに値を詰めたい or 詰めるのでよいのであればCloudStructuresを使うのをお勧めします。

まとめ

これでちょっとしたものを作るときにデータや設定の保存の手助けとなれば幸いです。

]]>
<p><a href="https://github.com/mayuki/KumoDictionary" target="_blank" rel="noopener">KumoDictionary</a> というライブラリーを公開しました。</p> <ul> <li><a hr
MySQLへのクエリをApplication Insights(.NET)のDependencyに出したい http://www.misuzilla.org/Blog/2018/06/06/MySqlCommandsDependency 2018-06-06T06:20:00.000Z 2022-05-21T09:11:37.901Z

Application InsightsにはDependencyテレメトリーというリクエスト中に発行された外部リソースへのアクセスなどを記録する仕組みがあり、.NET向けの一式を入れておけばHTTPリクエストやSQL Serverへの問い合わせが自動で記録されます。

一方、標準で対応してないものをDependencyに出すには自前で何らかの方法で記録してあげる必要があります。MySQL Connector/Netもその例にもれず自動では記録されません。SQL Serverへの問い合わせが記録されるのはSqlClient (SQL Serverクライアント)のイベントを記録しているからであって、それ以外のデータベースドライバーでは記録されないのです。

というわけで、当然MySQLへの問い合わせでもDependencyに表示されてほしくなります。

Dependencyとしての記録

まずはそもそもApplication Insights上でDependencyとして記録するにはどうすればいいのかというところからです。

ドキュメントを見るとDependencyはテレメトリーの一種で、DependencyTelemetry というレコードを記録すればよいということになっています。

記録するには DependencyTelemetry を生成する方法と、TrackDependencyメソッドで記録する方法があり、今回は DependencyTelemetry を使った記録方法で実装してみます。後者は細かいことはできないもののメソッド呼び出し一発とお手軽です。

実際の手順としては次のようになります。

  • TelemetryClient クラスのインスタンスを作る
  • TelemetryClient.StartOperation<T> メソッドを DependencyTelemetry 型を指定して呼び出す
  • 帰ってきた IOperationHolder のTelemetryのプロパティを設定する
  • 計測する処理を実行
  • TelemetryClient.StopOperation メソッドを呼ぶ または IOperationHolder.Dispose メソッドを呼ぶ

これをコードにするとこうなります。

// TelemetryClientはスレッドセーフなので使いまわせるvar telemetryClient = new TelemetryClient();using (var operation = telemetryClient.StartOperation<DependencyTelemetry>("DependencyName")){    var telemetry = operation.Telemetry;    telemetry.Type = "ResourceType"; // Dependencyの種類(Http, SQLなど)    telemetry.Target = "リクエスト送信先"; // エンドポイントのホスト名とか    telemetry.Data = "何か生データー"; // SQLとか    // 何か時間のかかる処理...    await Task.Delay(1000 * 3);}

難しいことはないですね。これでApplication Insightsに記録する方法はなんとなく理解できました。

MySQLの呼び出しを記録する

Dependencyとして記録する方法がわかったので次はMySQLへの問い合わせを記録する方法です。

今回はMySQLドライバーはMySQL公式のConnector/Netを利用していて、単純にSQLのクエリを記録したいと考えていますが、それにはそのクエリのタイミングをつかむ必要があります。そこで少し調べてみると、Connector/NetにはInterceptorというExecute系メソッドに割り込んでSQLのロギングなどが行える仕組みがあったのでそれを利用します。

InterceptorにはあらかじめExecute系メソッドに割り込むためのベースクラスである CommandInterceptorBase クラスがあるので、このクラスを継承して各種メソッドをオーバーライドします。そしてここではオーバーライドしたメソッドでDependencyの記録を行えば良さそうというわけです。

実際に実装した例はこんな感じになります。DependencyのTargetやNameといった値の設定はSQL Serverでの記録と同じような形にしました。

using System;using System.Collections.Generic;using System.Data;using System.Text;using Microsoft.ApplicationInsights;using Microsoft.ApplicationInsights.DataContracts;using MySql.Data.MySqlClient;namespace WebApplication2.Diagnostics{    public class ApplicationInsightsBaseCommandInterceptor : BaseCommandInterceptor    {        private TelemetryClient _telemetryClient = new TelemetryClient();        private string _name;        public override bool ExecuteNonQuery(string sql, ref int returnValue)        {            using (var operation = _telemetryClient.StartOperation<DependencyTelemetry>(_name))            {                var telemetry = operation.Telemetry;                telemetry.Type = "SQL";                telemetry.Data = sql;                telemetry.Target = _name;                return base.ExecuteNonQuery(sql, ref returnValue);            }        }        public override bool ExecuteReader(string sql, CommandBehavior behavior, ref MySqlDataReader returnValue)        {            using (var operation = _telemetryClient.StartOperation<DependencyTelemetry>(_name))            {                var telemetry = operation.Telemetry;                telemetry.Type = "SQL";                telemetry.Data = sql;                telemetry.Target = _name;                return base.ExecuteReader(sql, behavior, ref returnValue);            }        }        public override bool ExecuteScalar(string sql, ref object returnValue)        {            using (var operation = _telemetryClient.StartOperation<DependencyTelemetry>(_name))            {                var telemetry = operation.Telemetry;                telemetry.Type = "SQL";                telemetry.Data = sql;                telemetry.Target = _name;                return base.ExecuteScalar(sql, ref returnValue);            }        }        public override void Init(MySqlConnection connection)        {            _name = String.Format("{0} | {1}", connection.DataSource, connection.Database);            base.Init(connection);        }    }}

Interceptorを実装したら最後にアプリケーションの設定でデータベース接続文字列に commandinterceptors パラメータを追加して読み込ませます。パラメータの値は<CommandInterceptorClass>,<Assembly>というフォーマットで、次のようなものになります。

commandinterceptors=WebApplication2.Diagnostics.ApplicationInsightsBaseCommandInterceptor,WebApplication2

そしてこれを有効にした状態でアプリケーションを実行するとApplication Insightsに記録されます。もちろんAzureに接続していない場合でもVisual Studioで確認できます。


この例では問い合わせが1ms以下なので全く面白くなくて残念ですが、ともあれこれで取れるようになったので無いよりは全然よさそうです。

]]>
<p><img src="/Shared/Blog/img/2018/06/06/Untitled-2.png" alt=""></p> <p>Application InsightsにはDependencyテレメトリーというリクエスト中に発行された外部リソースへのアクセスなどを
AuthenticationHandlerで処理する方法をアクションごとに変更する http://www.misuzilla.org/Blog/2018/05/26/SwitchStrategyInAuthenticationHandler 2018-05-26T06:50:00.000Z 2022-05-21T09:11:37.901Z ASP.NET Core MVCでは認証は基本的にフィルターではなく、認証ハンドラーを認証スキーム名で登録しておき、使用したい認証スキームをAuthorize属性などで指定するという形になっています(デフォルトという指定もできます)。

今回やりたいこととしては「認証機構は基本的に一つで、あるアクションの時だけ認証中に追加で特殊な処理を行いたい」というパターンです。例えばコントローラーに共通のAuthorizeを付けているが、一部特殊なアクションでは追加の処理をしたいというケースです。

難しいところ

そもそも認証ハンドラーはASP.NET Coreの一部であってMVCとは切り離されたものなので、操作可能なものは主にHttpContextとなります。つまりASP.NET Core MVCが認識しているアクションであるとかコントローラーであるとかを取り出すことは難しいため、アクションに付けた属性を引いてくるといったことはできないという悩みがあります。

うまくいかないパターン

そこで最初に考えたのは、二つの認証スキームとして登録してそれぞれで利用する認証スキームを変更するという方法です。

まず次のようなオプションを持つAuthenticationHandlerを用意します。

public class CustomAuthenticationSchemeOptions : AuthenticationSchemeOptions{    public bool EnableNanika { get; set; }}public class CustomAuthenticationHandler : AuthenticationHandler<CustomAuthenticationSchemeOptions>{    public CustomAuthenticationHandler(IOptionsMonitor<CustomAuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)        : base(options, logger, encoder, clock)    {    }    protected override Task<AuthenticateResult> HandleAuthenticateAsync()    {        if (Options.EnableNanika)        {            // 一部で必要な特殊な処理...        }        return Task.FromResult(AuthenticateResult.Fail("Unauthorized"));    }    protected override Task HandleChallengeAsync(AuthenticationProperties properties)    {        Response.Headers["WWW-Authenticate"] = "Nanika";        return base.HandleChallengeAsync(properties);    }}

このハンドラーをStartupで二つの認証スキームとして登録します。

services.AddAuthentication()    .AddScheme<CustomAuthenticationSchemeOptions, CustomAuthenticationHandler>("Custom1", options =>    {        options.EnableNanika = false;    })    .AddScheme<CustomAuthenticationSchemeOptions, CustomAuthenticationHandler>("Custom2", options =>    {        options.EnableNanika = true;    });

コントローラーにAuthorize属性を付け、その際にコントローラーとアクションで認証スキームを別々に設定します。

[Route("api/[controller]")][Authorize(AuthenticationSchemes = "Custom1")]public class ValuesController : Controller{    // GET api/values    [HttpGet]    [Authorize(AuthenticationSchemes = "Custom2")]    public IEnumerable<string> Get()    {        return new string[] { "value1", "value2" };    }}

これでどうかと思ったのですが、この指定で実際に動かすとコントローラーの指定の方が勝つことになります。当然ですがコントローラーにAuthorizeを付けずにアクションごと個別につけると期待通りに動作します。

うまくいかないパターン(2)

認証はフィルターとしても実装できるので(Authorizeも実体はMVCのフィルターで、中で認証ハンドラーを呼び出している)、それをかけてみるのはどうかという風になります。

public class CustomAuthorize : Attribute, IAsyncAuthorizationFilter{    public bool EnableNanika { get; set; }    public Task OnAuthorizationAsync(AuthorizationFilterContext context)    {        // ここで認証処理をあれこれやる        return Task.CompletedTask;    }}

この場合にはコントローラーとアクション両方に指定すると二つのフィルターが呼ばれることになる(=二回処理が走る)ので都合が悪い感じになります。それに加えてAuthorize属性と同じようなことを書いてあげる必要があり面倒です。

解決策: フィルターを組み合わせるパターン

認証はフィルターとして実装できるという点を利用して、認証自体は行わずリクエストのデータにフラグを立てておき、認証ハンドラーで取り出して何とかする方法があります。その方法であれば 自前のフィルター(ASP.NET Core MVC) → 自前の認証ハンドラー (ASP.NET Core) という形にできます。

まずアクションにつけるフィルターを作ります。

public class UseCustomAuthenticationWithNanikaAttribute : Attribute, IAsyncAuthorizationFilter, IOrderedFilter{    public int Order => int.MinValue;    public Task OnAuthorizationAsync(AuthorizationFilterContext context)    {        context.HttpContext.Features.Set<IUseCustomAuthenticationWithNanikaFeature>(new UseCustomAuthenticationWithNanikaFeature        {            Enable = true        });        return Task.CompletedTask;    }}public interface IUseCustomAuthenticationWithNanikaFeature{    bool Enable { get; set; }}public class UseCustomAuthenticationWithNanikaFeature : IUseCustomAuthenticationWithNanikaFeature{    public bool Enable { get; set; }}

このフィルターはOnAuthorizationAsyncでHttpContextのFeatures(リクエスト単位の機能を入れておけるもの)にフラグ情報を持つFeatureを保存します。IOrderedFilterを実装しているのはAuthorize属性より先に来てほしいためです。

次に認証ハンドラーを作ります。このハンドラーの中でFeaturesから先ほどのフィルターでつけていたFeatureを引っ張り出します。

public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>{    public CustomAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)        : base(options, logger, encoder, clock)    {    }    protected override Task<AuthenticateResult> HandleAuthenticateAsync()    {        var enableNanika = Context.Features.Get<IUseCustomAuthenticationWithNanikaFeature>()?.Enable ?? false;        if (enableNanika)        {            // 一部で必要な特殊な処理...        }        return Task.FromResult(AuthenticateResult.Fail("Unauthorized"));    }    protected override Task HandleChallengeAsync(AuthenticationProperties properties)    {        Response.Headers["WWW-Authenticate"] = "Nanika";        return base.HandleChallengeAsync(properties);    }}

Startupで認証ハンドラーを登録します。この際、AddAuthenticationでデフォルト認証スキームを登録しないように注意します。デフォルトの認証スキームとなるハンドラーは順番が特別なことになるので意図した挙動になりません。

services.AddAuthentication()    .AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>("Custom", options => { });

そしてコントローラーにはAuthorize属性を、特別な処理をしたいアクションにはUseCustomAuthenticationWithNanika属性でフィルターを付けます。

[Route("api/[controller]")][Authorize(AuthenticationSchemes = "Custom")]public class ValuesController : Controller{    // GET api/values    [HttpGet]    [UseCustomAuthenticationWithNanika]    public IEnumerable<string> Get()    {        return new string[] { "value1", "value2" };    }    // GET api/values/5    [HttpGet("{id}")]    public string Get(int id)    {        return "value";    }}

これでアクションに属性を指定するだけで認証時の挙動を少し変更するということが可能になります。

]]>
<p>ASP.NET Core MVCでは認証は基本的にフィルターではなく、認証ハンドラーを認証スキーム名で登録しておき、使用したい認証スキームをAuthorize属性などで指定するという形になっています(デフォルトという指定もできます)。</p> <p>今回やりたいこととしては
U-NEXT TV (第二世代)はAndroidおもちゃではない http://www.misuzilla.org/Blog/2018/04/08/UNextTvIsNotAndroidTv 2018-04-07T15:15:00.000Z 2022-05-21T09:11:37.901Z U-NEXT、9,800円のHDR対応Android TV「U-NEXT TV」。カラオケやYouTubeも - AV Watchという記事を見て「先着100名で、新規にU-NEXT登録した人を対象にU-NEXT TVをプレゼントするキャンペーン」とあったので登録してみたところ無事届きました。というわけでAndroidとして遊べないかなというところに関してのお話です。

製品仕様はキャンペーンサイトに書いてあり、Android 7.0であることが明記されています。

U-NEXT TV (2nd Gen)

端末自体はHuaweiです。この手のセットトップボックスではHuawei製はよくあるようです(たしかauやdocomoのものもそうだったはず)。

OS

さて、Androidに関してですがよく見ると公式サイトの製品仕様にはAndroid 7.0とはあるもののAndroid TVとは書いていないのでもしかしたら…と思っていたのですがセットアップし始めるとやはりAndroid TVではなく単なるAndroidです。

そもそもセットアップステップがAndroid TV風でなくU-NEXTのログインが必須であったり、オンスクリーンキーボードもAndroid TVのそれではない、ホーム画面は独自というあたりでお察しです。

製品仕様によればアプリも利用できることになっているのですがAndroidがベースになっているだけでGoogleサービスが入っていないのでGoogle Playは使えません。「U-NEXTサービス、HDMI-CEC対応、Player for YouTube、Androidなど」とあるので使えるのかと思いきや。

YouTubeを観れそうな雰囲気ですがGoogle Play入ってないのにどうやって?と思ったらHuaweiの独自アプリでした。oh…。

現状U-NEXT用のアプリストアがあるわけでもないのでプレインストールのアプリのみ利用できます。なお設定のアプリ管理からアプリをアンインストールできるのですが、それらはインストールする口がないので消したら最後です(これはひどい。

参考までにほぼ同型機のdocomoのドコモテレビターミナルは説明書を見てもわかるのですが正真正銘のAndroid TVでロゴも表記されています。

adbを使う

とはいえAndroidなのでUSBデバッグとかできればなんかいろいろできるかもということで探すとちゃんとUSBデバッグはあります。

「アプリ」→「端末設定」→「情報」→「開発者オプション」→「USBデバッグ」で有効になるので有効にして、USBケーブルをつなぐと許可するかどうかのダイアログが表示されます。ちなみにUSBケーブルはType-Aなので注意が必要です。

接続後はいつものようにadbでコマンドを実行できshellにも入れます。そこで早速apkをインストールしてみようとすると次のようなエラーが発生しました。

adb -s 0123456789 install "app-release.apk"adb: failed to install app-release.apk: Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Verify signature failed]

どうも署名チェックのようなものがあり、そこで引っかかっているようです。

提供元不明のアプリのインストールを許可する必要があるのか?と思ったので設定画面を開いてセキュリティから許可してみましたがダメです。Webを検索してもそれらしい情報はないので端末固有でロックをかけていそうです。うーん。

なお、設定画面は am コマンドを使えば開けます。

HWM380:/ $ am start -a android.intent.action.MAIN -n com.android.settings/.Settings

ちなみにバージョン情報はこんな感じです。

もう少し探ってみたところ /system/etc/security/apk_sign_white_list.xml というファイルがあり、署名のホワイトリストのようなものだったのでユーザーが任意にインストールするのは無理そうですね(HuaweiやU-NEXTだけが含まれている)。

一応 pm list packageの結果、パッケージの一覧はこんな感じでした。

まとめ

残念ながらAndroid端末としてはいじりがいがないのでそこを期待してはダメそうです。とはいえ特定サービス向けの装置なのでそこはしょうがないかなーという気もします。

U-NEXTのSTBとしてはきびきび動いたりするので普通に使う分には割とよいのではないかとは思いますが、実際お金を出す場合半額の4,980円でもどうかなー…って感じがしますね。リテラシがなくてつなげば使えるみたいなのを求めていない限り、正直縛りのないAndroid TVであるAirStick 4Kの方がよさそう。

そうそう、ログイン必須なので退会すると完全に使い物にならなくなります。

]]>
<p><a href="https://av.watch.impress.co.jp/docs/news/1113860.html" target="_blank" rel="noopener">U-NEXT、9,800円のHDR対応Android TV「U-NEXT TV」。カ
App Centerでユーザー定義環境変数を取得できない http://www.misuzilla.org/Blog/2018/02/15/AppCenterEnvironmentVariable 2018-02-15T01:00:00.000Z 2022-05-21T09:11:37.897Z Visual Studio App Centerではビルド時の環境変数を定義でき、ビルドスクリプトやGradleのようなビルドシステムから値を取得できるのですが、定義しても環境変数が取れないという現象が発生しました。

tl;dr

SECRET のようなApp Centerが許可しないキーワードを含んだ環境変数を定義している場合、そのままの名前で露出しないようになっているようです。PASSWORD のような単語は通るので SECRET のみの制限かもしれません(ドキュメントに特に書いてない)。

ユーザー定義の環境変数は USER-DEFINED_ というプレフィックスがついた環境変数も定義されるのでそちらから取得する方法もありますが、ハイフンを含んでいるため参照も定義も一筋縄ではいかないのでお勧めできません。

問題と調査

API_KEYNUGET_USERNAME のような環境変数を定義してみたところ特に問題なく取得できたので、もしかして変数名に問題があるのでは?と思いいくつかのパターンを試してみました。すると以下のパターンで違いが出ることがわかりました。

  • SECRET_HAUHAU: hauhau1
  • SEECRET_HAUHAU: hauhau2
  • SECRE__HAUHAU: hauhau3

この定義を設定した上でビルドし、pre-buildスクリプトで環境変数をすべて出力すると…

##[section]Starting: Pre Build Script==============================================================================Task         : Shell ScriptDescription  : Run a shell script using bashVersion      : 2.1.3Author       : Microsoft CorporationHelp         : [More Information](https://go.microsoft.com/fwlink/?LinkID=613738)==============================================================================[command]/bin/bash /Users/vsts/agent/2.127.0/work/1/s/app/appcenter-pre-build.sh...snip...SECRE__HAUHAU=hauhau3...snip...USER-DEFINED_SEECRET_HAUHAU=hauhau2...snip...USER-DEFINED_SECRE__HAUHAU=hauhau3...snip...USER-DEFINED_SECRET_HAUHAU=hauhau1...snip...SEECRET_HAUHAU=hauhau2...snip...

…となり、SECRET_HAUHAU という変数のみそのまま露出しないようになっています。この結果からApp Centerは SECRET を含む環境変数をそのまま変数として露出しないようにしているようです。セキュリティのためでしょうか。

というわけで特別必要がなければ SECRET という名前を含めないでおくというのがハマらないポイントです。

]]>
<p>Visual Studio App Centerではビルド時の環境変数を定義でき、ビルドスクリプトやGradleのようなビルドシステムから値を取得できるのですが、定義しても環境変数が取れないという現象が発生しました。</p> <ul> <li><a href="https:
App Centerでビルドスクリプトが動かない http://www.misuzilla.org/Blog/2018/02/13/AppCenterBuildScripts 2018-02-12T15:55:00.000Z 2022-05-21T09:11:37.897Z Visual Studio App Centerでビルドする際、特定の名前でシェルスクリプトを置いておくことでビルドの前後でコマンドを実行できます。

2018年2月時点では Build scripts | Microsoft Doc にある通り、以下のスクリプトを認識します(for UWPなら.ps1)。

  • appcenter-post-clone.sh: git clone後
  • appcenter-pre-build.sh: パッケージ等復元後、ビルド前
  • appcenter-post-build.sh: ビルド後

ここまでは書いてある通りなのですが、実際リポジトリにビルドスクリプトを含めても一向に実行されないという問題が発生しました。設定画面を見ると Build scripts: ✔ Post-clone と表示され認識はされているようでした。

新しいブランチを作ってビルド設定を追加して試すと問題なく動作したので悩んだのですが、実は Build scripts: ✔ Post-clone となった後に Save もしくは Save & Build で設定を保存する必要があるようです。

逆を言うと、ビルドスクリプトを削除した後も保存しなおさないとビルドがエラーになります。

新しいブランチを作った時に動いたのは、すでにビルドスクリプトが含まれていて、ビルド定義を作る際に Build scripts: ✔ Post-clone となった状態だったからということでした。

]]>
<p>Visual Studio App Centerでビルドする際、特定の名前でシェルスクリプトを置いておくことでビルドの前後でコマンドを実行できます。</p> <p>2018年2月時点では <a href="https://docs.microsoft.com/en-us/a
TiarraのIRCログをBigQueryに流し込む http://www.misuzilla.org/Blog/2018/01/18/TiarraToBigQuery 2018-01-18T00:40:00.000Z 2022-05-21T09:11:37.897Z

ふとTiarraでとったIRCのログをBigQueryに流し込むようにしたら手元にログを長期間残す必要もないし、検索もできるし便利そうだなと思ったので設定してみました。Twitterのログもあって若干流れますがBigQuery的には誤差の範囲レベル(1日1MB程度)なのでStreaming Insertして、たまーに検索しても無料枠で余裕で収まりそうです。

主な流れはTiarraで吐いたテキストログをfluentdのin_tailで読んで、fluent-plugin-bigqueryでBigQueryに流し込むという何の変哲もない形です。

Docker

ですでに手元のTiarraはDocker(docker-compose)で起動しているのでfluentdもDockerで起動しています。fluent-plugin-bigqueryはfluentdの公式イメージに追加する必要があるので適当に追加します。

FROM fluent/fluentdRUN apk --no-cache --update add ruby-bigdecimal ruby-dev build-base && \    fluent-gem install fluent-plugin-bigquery

ruby-bigdecimalが必要ということはREADMEにあるのですが、ruby-devとbuild-baseも必要で追加していないとfluent-plugin-bigqueryをインストール中にstrptimeのビルドで失敗します。

BigQuery Schema (schema.json)

次にBigQueryのスキーマを用意します。今回は次のようなものになっています。

[    { "name": "Time", "type": "TIMESTAMP" },    { "name": "User", "type": "STRING" },    { "name": "Server", "type": "STRING" },    { "name": "Room", "type": "STRING" },    { "name": "Text", "type": "STRING" }]

テーブル自体はこのスキーマを使ってfluent-plugin-bigqueryに自動生成させるので、BigQuery側にはプロジェクトとデータセットだけ作っておきます。このスキーマファイルは次のfluent.confと同じところにおいてください(最終的にdockerでマウントされるところ)。

fluentd (fluent.conf)

最後にfluentdの設定です。

source(入力)はssig33 - Tiarra のログを fluentd で流すを参考にin_tailを設定します。手元ではTIGのログが流れてくるのとサーバー名を分解するために少し変更しています。ログはローテートされるので %Y.%m.%d.txt で。

出力はfluent-plugin-bigqueryの設定を auto_create_table で設定します。これ自体は何のことはない設定なのですが table を年で分けるようにしようとしたところでハマりました。

table にはプレースホルダ文字列を使えることになっているのですが、文字列置換が有効になる条件としてまず buffer time/timekey を設定しているというものがあります。そこで timekey を設定すると %Y ではなく %d 精度のプレースホルダを持つ設定を要求します(参考: output.rb)。しかし今回は%Yでいいのに…困った…と考えた末に fetch_schema_table という使っていない設定項目に log_%Y%m%d 入れて回避しました。

とはいえ真面目にBigQueryを使うレベルなら日別にすると思うので(扱いづらいし…)罠にははまらないとは思います。日付パーティションにしてもいいかもとか頭をよぎりますが量も大してないので最悪あとで何とかすればいいでしょう(適当。

<source>    @type tail    path /fluentd/log/tig/tig-twitter/%Y.%m.%d.txt,/fluentd/log/ircnet/*/%Y.%m.%d.txt,/fluentd/log/freenode/*/%Y.%m.%d.txt    tag tiarra        format /^(?<time>[^ ]+)\ (<(?<room>[^ ]*)@(?<server>[^ ]*):(?<user>[^ ]*)>|\((?<room>[^ ]*)@(?<server>[^ ]*):(?<user>[^ ]*)\)|>(?<room>[^ ]*)@(?<server>[^ ]*):(?<user>[^ ]*)<|-(?<user>[^ ]*)-|>(?<user>[^ ]*)@[^ ]*<)\ (?<text>.*?)(?: \u0003\d+\([^)]+\).*)?$/    time_format %H:%M:%S</source><match tiarra>    @type bigquery    method insert    auth_method json_key    json_key /fluentd/etc/serviceaccount_key.json    project <YourProjectName>    dataset <YourDataSetName>    table Log_%Y    auto_create_table true    schema_path /fluentd/etc/schema.json    # table でplaceholderを使うために必要    # buffer time/timekeyの設定が必要でそのためにplaceholderの%dをもつ設定が必要なので使わない項目をダミーとして使う…    fetch_schema_table log_%Y%m%d    <buffer time>        timekey 1d    </buffer>    <inject>        time_key time        time_type string        time_format %s    </inject></match>

あとはこれでfluent/fluentd-docker-imageを参考にDockerでマウントしつつ起動すればログがBigQueryに良しなに送られます。便利。

]]>
<p><img src="/Shared/Blog/img/2018/01/18/Screen_01.png" alt=""></p> <p>ふとTiarraでとったIRCのログをBigQueryに流し込むようにしたら手元にログを長期間残す必要もないし、検索もできるし便利そうだな
This XML file does not appear to have any style information associated with it. The document tree is shown below.
<feed xmlns="http://www.w3.org/2005/Atom">
<title>ぷろじぇくと、みすじら。</title>
<link href="/Feed.xml" rel="self"/>
<link href="http://www.misuzilla.org/"/>
<updated>2022-05-21T09:11:37.901Z</updated>
<id>http://www.misuzilla.org/</id>
<author>
<name>Mayuki Sawatari</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>Azure Container Apps (Preview) でカスタムドメインに必要な証明書を Cloudflare で作る</title>
<link href="http://www.misuzilla.org/Blog/2022/05/21/ContainerAppsWithCloudflareOriginCerts"/>
<id>http://www.misuzilla.org/Blog/2022/05/21/ContainerAppsWithCloudflareOriginCerts</id>
<published>2022-05-21T09:00:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <h2 id="tl-dr"><a href="#tl-dr" class="headerlink" title="tl;dr"></a>tl;dr</h2><ul><li>Azure Container Apps は現時点ではカスタムドメインの設定に証明書が必須</li><li>Cloudflare オリジン証明書というオリジンと Cloudflare の間の通信で使用する証明書を発行できる仕組みがある</li><li>オリジン証明書を Container Apps に登録して、Cloudflare をフロントに置くことでドメインの証明書管理を Cloudflare にまかせる</li></ul><h2 id="Azure-Container-Apps-にカスタムドメインを登録時、証明書が必要"><a href="#Azure-Container-Apps-にカスタムドメインを登録時、証明書が必要" class="headerlink" title="Azure Container Apps にカスタムドメインを登録時、証明書が必要"></a>Azure Container Apps にカスタムドメインを登録時、証明書が必要</h2><p>Azure の Azure Container Apps (Preview) ではカスタムドメインを設定して、外部からのアクセスを受け付けるようにできます。</p><p>詳しくはしばやんさんのブログを参照していただきたいのですが、現時点ではカスタムドメインの設定時に HTTPS 向けの証明書のアップロードが必要です。</p><ul><li><a href="https://blog.shibayan.jp/entry/20220514/1652519813" target="_blank" rel="noopener">Azure Container Apps がカスタムドメインと証明書の追加に対応したので試した - しばやん雑記</a></li></ul><p>つまりドメインに対する証明書を何らかの方法で作成/用意する必要があり、しばやんさんの記事では Acmebot を用意して Let’s Encrypt で証明書を作成、更新していく方法が紹介されています。</p><p>今回触っていた環境は個人で適当に立てているサイトなので動かすものを増やしたくないなと思っていたのですが、元々 Cloudflare を前に置いていたので Cloudflare が自動で発行、更新する証明書を使うようにすれば丸投げできそうな気がしてきました。が、しかし Container Apps は HTTPS 必須なのかカスタムドメインの登録時にどうにしても証明書が必要です。</p><h2 id="オリジン証明書を作る"><a href="#オリジン証明書を作る" class="headerlink" title="オリジン証明書を作る"></a>オリジン証明書を作る</h2><p>Container Apps のカスタムドメイン設定に必要な証明書をどうするか、というところで Cloudflare にはオリジン証明書を作るという機能があるのでこれを使います。</p><p>オリジン証明書は Cloudflare とオリジン、この場合だと Container Apps の通信でのみ使用する目的の証明書で今回の用途にはぴったりです。デフォルトでは15年という長い期限で作成され、個人のサイトならそっちが先に朽ちるか Managed Certificates がくると思うのでよさそうです(今回はある程度放っておきたいのが目的)。</p><p>オリジン証明書は Cloudflare の管理画面の SSL/TLS → オリジンサーバー → オリジン証明書 で発行できます。発行すると証明書と秘密鍵がでてきますので PEM 形式で手元に保存します。特に秘密鍵はページを閉じると確認できなくなるのでしっかり保存しておきます。</p><h2 id="オリジン証明書を-pfx-形式-PKCS-12-に変換する"><a href="#オリジン証明書を-pfx-形式-PKCS-12-に変換する" class="headerlink" title="オリジン証明書を pfx 形式 (PKCS#12) に変換する"></a>オリジン証明書を pfx 形式 (PKCS#12) に変換する</h2><p>次に発行した証明書と秘密鍵を Container Apps に登録します。ところが登録時にパスワードを求められたりしてそのままでは取り込めないので OpenSSL で pfx 形式 (PKCS#12) に変換します。</p><pre><code class="hljs bash">cat certificate.pem private.pem &gt; origin.pemopenssl pkcs12 -<span class="hljs-built_in">export</span> -<span class="hljs-keyword">in</span> origin.pem -out origin.pfx</code></pre><h2 id="オリジン証明書を-Container-Apps-に登録する"><a href="#オリジン証明書を-Container-Apps-に登録する" class="headerlink" title="オリジン証明書を Container Apps に登録する"></a>オリジン証明書を Container Apps に登録する</h2><p>pfx 形式の証明書を作成したら Container Apps に登録します。Azure Portal の コンテナー アプリ環境 → (コンテナーアプリ) → 証明書 で “証明書の追加” で追加を行えます。</p><p>注意点として現時点では “証明書名” に大文字アルファベットを含む文字列を指定すると “この証明書名は既に使用されています。別の証明書名をお試しください。” という謎のエラーが出るので小文字で入力してください。</p><h2 id="カスタムドメインの設定"><a href="#カスタムドメインの設定" class="headerlink" title="カスタムドメインの設定"></a>カスタムドメインの設定</h2><p>証明書が作成出来たら後はカスタムドメインの設定を行い、証明書として先ほど登録したものを選択すれば完了です。</p><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>Container Apps が Managed Certificates にはやく対応してほしいですね。</p> ]]>
</content>
<summary type="html"> <h2 id="tl-dr"><a href="#tl-dr" class="headerlink" title="tl;dr"></a>tl;dr</h2><ul> <li>Azure Container Apps は現時点ではカスタムドメインの設定に証明書が必須</li> < </summary>
</entry>
<entry>
<title>Source Generator の Visual Studio 2019 v16.9 での新 API</title>
<link href="http://www.misuzilla.org/Blog/2021/05/02/SourceGeneratorNewApiIn16.9"/>
<id>http://www.misuzilla.org/Blog/2021/05/02/SourceGeneratorNewApiIn16.9</id>
<published>2021-05-02T13:50:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p>Source Generator を触っていて気づいたのですが Visual Studio 2019 version 16.9 の段階でいくつか API が追加されていました。</p><ul><li><a href="https://github.com/dotnet/roslyn/issues/49873" target="_blank" rel="noopener">dotnet/roslyn: Source Generator semantic model visitor #49873</a></li><li><a href="https://github.com/dotnet/roslyn/pull/50419" target="_blank" rel="noopener">dotnet/roslyn: Allow access to semantic model during syntax walk #50419</a></li><li><a href="https://github.com/dotnet/roslyn/pull/50026" target="_blank" rel="noopener">dotnet/roslyn: Implement PostInitialization in generator driver #50026</a></li></ul><p>追加されたものは次のようなあるといいよねといった API が増えている感じです。</p><ul><li><code>GeneratorInitializationContext.RegisterForPostInitialization</code> メソッド</li><li><code>ISyntaxContextReceiver</code> インターフェース</li></ul><p>Visual Studio 2019 であれば 16.9 以降、NuGet パッケージであれば Microsoft.CodeAnalysis.CSharp 3.9.0 を参照すれば使用できます。</p><h2 id="GeneratorInitializationContext-RegisterForPostInitialization-メソッド"><a href="#GeneratorInitializationContext-RegisterForPostInitialization-メソッド" class="headerlink" title="GeneratorInitializationContext.RegisterForPostInitialization メソッド"></a><code>GeneratorInitializationContext.RegisterForPostInitialization</code> メソッド</h2><p><code>GeneratorInitializationContext.RegisterForPostInitialization</code> メソッドは Source Generator が読み込まれて初期化された後に呼び出されるコールバックを登録できます。</p><p>これは Source Generator で必要となる属性用のコードを追加したいパターンに役立ちます。例えば<a href="https://github.com/dotnet/roslyn-sdk/blob/main/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs" target="_blank" rel="noopener">次のようなコードがサンプルにあります</a>。</p><pre><code class="hljs csharp">[<span class="hljs-meta">Generator</span>]<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">AutoNotifyGenerator</span> : <span class="hljs-title">ISourceGenerator</span>&#123; <span class="hljs-keyword">private</span> <span class="hljs-keyword">const</span> <span class="hljs-keyword">string</span> attributeText = <span class="hljs-string">@"using System;namespace AutoNotify&#123;[AttributeUsage(AttributeTargets.Field, Inherited = false, AllowMultiple = false)][System.Diagnostics.Conditional(""AutoNotifyGenerator_DEBUG"")]sealed class AutoNotifyAttribute : Attribute&#123; public AutoNotifyAttribute() &#123; &#125; public string PropertyName &#123; get; set; &#125;&#125;&#125;"</span>; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Initialize</span>(<span class="hljs-params">GeneratorInitializationContext context</span>)</span> &#123; <span class="hljs-comment">// Register the attribute source</span> context.RegisterForPostInitialization((i) =&gt; i.AddSource(<span class="hljs-string">"AutoNotifyAttribute"</span>, attributeText)); <span class="hljs-comment">// Register a syntax receiver that will be created for each generation pass</span> context.RegisterForSyntaxNotifications(() =&gt; <span class="hljs-keyword">new</span> SyntaxReceiver()); &#125;...&#125;</code></pre><p>今までソースで指定したい属性をどうするかという問題があった(一度生成させてその属性を使うのか、手で追加するのかとか)のがこれで解決できそうです。</p><h2 id="ISyntaxContextReceiver-インターフェース"><a href="#ISyntaxContextReceiver-インターフェース" class="headerlink" title="ISyntaxContextReceiver インターフェース"></a><code>ISyntaxContextReceiver</code> インターフェース</h2><p>コンパイラーがソースコードを処理する際に Source Generator を呼び出す <code>ISyntaxReceiver</code> インターフェースがあります。</p><p>例えばクラス定義に対するコードジェネレートを行いたいときには自分でシンタックスツリーを走査して集めるのではなく、ツリーの走査はコンパイラーに任せて、 ClassDeclarationSyntax を集めておいて最後に処理するといった使い方です。</p><p><code>ISyntaxReceiver</code> インターフェースは <code>OnVisitSyntaxNode</code> メソッドのみをもち引数として <code>SyntaxNode</code> を受ける形でしたが、 <code>ISyntaxContextReceiver</code> では <code>GeneratorSyntaxContext</code> を受け取る形になります。</p><p>このコンテキストオブジェクトにはセマンティックモデルが含まれているので、Receiver で必要なものをかき集めるときに、コードの解析された情報にアクセスできるようになります。</p><p>例えば属性はセマンティックモデルを通してシンボルを取得して、GetAttributes で取得できるので、Source Generator 用の属性がついていることを Receiver が集める段階で確認できます。こちらも<a href="https://github.com/dotnet/roslyn-sdk/blob/15974e41e2bcff3b172f89df6feb24b421a4bb8e/samples/CSharp/SourceGenerators/SourceGeneratorSamples/AutoNotifyGenerator.cs#L155" target="_blank" rel="noopener">サンプルコード</a>が例としてわかりやすいです。</p><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><p>16.9 で追加された API なので古い Visual Studio や .NET SDK では動かない可能性があります(試していない)。</p><p>Rider 2021.1.2 ではビルドは問題ないのですが Rider 側が対応していないようでエディター上では正しく動作しなかったりしたので、2021年5月頭時点では使うにはちょっと早いかもしれません。</p> ]]>
</content>
<summary type="html"> <p>Source Generator を触っていて気づいたのですが Visual Studio 2019 version 16.9 の段階でいくつか API が追加されていました。</p> <ul> <li><a href="https://github.com/dotnet/ </summary>
<category term="dotNET CSharp" scheme="http://www.misuzilla.org/Tags/dotNET-CSharp/"/>
</entry>
<entry>
<title>Blazor のコンポーネントのステートについて</title>
<link href="http://www.misuzilla.org/Blog/2020/12/25/BlazorComponentLifecycle"/>
<id>http://www.misuzilla.org/Blog/2020/12/25/BlazorComponentLifecycle</id>
<published>2020-12-24T15:00:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p>これは <a href="https://qiita.com/advent-calendar/2020/blazor" target="_blank" rel="noopener">Blazor Advent Calendar 2020</a> の25日目のエントリーです。</p><p>Blazor はお手軽に Single Page Application を作れるのですがコンポーネントのライフサイクル、ステート関連について知らないと若干不思議な仕組みで動いているように見えます。</p><p>例えばプロジェクトテンプレートの Counter は <code>@onclick=&quot;IncrementCount&quot;</code> でプライベート変数 <code>currentCount</code> をインクリメントするとページのカウントがアップしますが、これは初見ではなかなか不思議な挙動です。Blazor は <code>currentCount</code> の変更をどうやって知ったのか?としばらく不思議に思っていました。そういったこともライフサイクルを知ることで理解できます。</p><p>基本的には <a href="https://docs.microsoft.com/ja-jp/aspnet/core/blazor/components/lifecycle?view=aspnetcore-5.0" target="_blank" rel="noopener">ASP.NET Core Blazor ライフサイクル</a> というドキュメントに書いてあるのですがこれはそれを補完する目的のエントリーです。</p><h2 id="基本的な流れ"><a href="#基本的な流れ" class="headerlink" title="基本的な流れ"></a>基本的な流れ</h2><p>Blazor コンポーネントはコンポーネントのステート(状態)が変更されるたびに再レンダリングを行うという流れが基本となります。これ自体は他のフレームワークと大きく変わるところではないと思います。</p><ul><li>初期化</li><li>レンダリング</li><li>ステート変更</li><li>再レンダリング</li><li>ステート変更</li><li>再レンダリング</li><li>…</li></ul><p>これらの多くのライフサイクルに関連するものは <a href="https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Components/Components/src/ComponentBase.cs" target="_blank" rel="noopener">Microsoft.AspNetCore.Components.ComponentBase クラス</a> に実装されています。</p><h2 id="初期化-初回レンダリング"><a href="#初期化-初回レンダリング" class="headerlink" title="初期化/初回レンダリング"></a>初期化/初回レンダリング</h2><p>コンポーネントの初回のレンダリング、つまり初めてページが表示されたときの処理の流れは次のようになっています。</p><ul><li>コンストラクター</li><li>SetParametersAsync<ul><li>OnInitialized</li><li>OnInitializedAsync</li><li>OnParametersSet</li><li>OnParametersSetAsync</li></ul></li><li>BuildRenderTree</li><li>(ここに子コンポーネントの一式が入る)</li><li>OnAfterRender</li><li>OnAfterRenderAsync</li></ul><p>初めに SetParametersAsync が呼び出され、初期化とパラメータのセットを行います。この OnInitializedAsync (初期化), OnParameterSetAsync (パラメータセット) では非同期処理を行うことができますが、その場合は Task の完了を待たずにレンダリングが行われて完了次第再レンダリングが行われます。</p><p>つまり非同期初期化の場合、コンポーネントの HTML をレンダリングが行われるタイミングではまだ値がそろっていない場合がある点には注意が必要です。</p><pre><code class="hljs csharp"><span class="hljs-keyword">private</span> NanikaObject _nanika;<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">async</span> Task <span class="hljs-title">OnInitializedAsync</span>(<span class="hljs-params"></span>)</span>&#123; <span class="hljs-keyword">await</span> Task.Delay(<span class="hljs-number">1000</span>); _nanika = <span class="hljs-keyword">new</span> NanikaObject(); <span class="hljs-keyword">await</span> <span class="hljs-keyword">base</span>.OnInitializedAsync();&#125;...@* OnInitializedAsync が終わるまで _nanika は <span class="hljs-literal">null</span> *@&lt;h1&gt;@_nanika.Description&lt;/h1&gt;</code></pre><p>BuildRenderTree メソッドは Blazor 上のドキュメントツリー構造(仮想DOM的なもの)を構築するものです。通常は Razor Component (.razor) ファイルから自動生成され、手でツリーを作るようなカスタムコンポーネントを作らない限りは触ることはありません。中身が気になる場合には obj ディレクトリの中を覗いてみると生成物を確認できます。</p><p>なお Server-side Blazor に関してはレンダリングモードが ServerPrerendered の場合、 SetParametersAsync + BuildRenderTree が2回呼び出されます。1回目はプリレンダリングのための実行です。またプリレンダリングはサーバーサイドで完成系を返す都合、SetParametersAsync の完了を待ってからのレンダリングとなります。</p><h3 id="OnInitialized-と-OnParametersSet-の違い"><a href="#OnInitialized-と-OnParametersSet-の違い" class="headerlink" title="OnInitialized と OnParametersSet の違い"></a>OnInitialized と OnParametersSet の違い</h3><p>OnInitialized{Async} と OnParametersSet{Async} の違いは、OnInitialized がコンポーネントの初期化であるのに対して、OnParametersSet はプロパティの変更です。</p><p>つまり <code>[Parameter]</code> として受けている値が変わったときに呼び出されるもので React で言うなら props の変更のような感じでしょうか。</p><p>例えば次のようなコンポーネントを用意して…<br><pre><code class="hljs csharp">&lt;p&gt;@Count&lt;/p&gt;@code &#123; [<span class="hljs-meta">Parameter</span>] <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Count &#123; <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; &#125; <span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">override</span> Task <span class="hljs-title">OnParametersSetAsync</span>(<span class="hljs-params"></span>)</span> &#123; Console.WriteLine(<span class="hljs-string">$"Count=<span class="hljs-subst">&#123;Count&#125;</span>"</span>); <span class="hljs-keyword">return</span> <span class="hljs-keyword">base</span>.OnParametersSetAsync(); &#125;&#125;</code></pre></p><p>Counter.razor にぶら下げてみます。</p><pre><code class="hljs html"><span class="hljs-tag">&lt;<span class="hljs-name">Nantoka</span> <span class="hljs-attr">Count</span>=<span class="hljs-string">"currentCount"</span> /&gt;</span></code></pre><p>これでカウンターを更新すると OnParametersSetAsync が呼び出されることを確認できます。</p><h2 id="ステート-状態-と再レンダリング"><a href="#ステート-状態-と再レンダリング" class="headerlink" title="ステート(状態)と再レンダリング"></a>ステート(状態)と再レンダリング</h2><p>そもそも Blazor におけるステートとは何かステートという特別な入れ物があるわけでもなく、あるのは何らかの何らかステートが変更されたということを通知する仕組みです。</p><p>データそのものはプライベート変数であったり、Parameter であったりするかもしれませんが Blazor としては「何か状態が変わったことを知る」ということが重要になります。</p><p>Blazor はステートの変更通知を受けると、コンポーネントの再レンダリングを行い、表示を更新します。</p><h2 id="ステート変更通知は何によって引き起こされるのか"><a href="#ステート変更通知は何によって引き起こされるのか" class="headerlink" title="ステート変更通知は何によって引き起こされるのか"></a>ステート変更通知は何によって引き起こされるのか</h2><p>ステートの変更通知はどのようなタイミングで発生するのかですが、おおまかに下記の3パターンで発生します。</p><ul><li>StateHasChanged メソッドを明示的に呼び出したとき</li><li>イベント発火時 (<code>@bind</code> や <code>@onclick</code> とか)</li><li>Parameter が変わったとき</li></ul><p>3パターンと書きましたが2つ目と3つ目は暗黙的に StateHasChanged を呼び出しているというのが実際のところです。</p><h3 id="StateHasChanged-を呼び出したとき"><a href="#StateHasChanged-を呼び出したとき" class="headerlink" title="StateHasChanged を呼び出したとき"></a>StateHasChanged を呼び出したとき</h3><p>StateHasChanged はステートが変更されたことを通知するメソッドです。このメソッドを呼び出すと再レンダリング候補としてマークされます。何が起こるのかは後ほど少し説明します。</p><p>多くのケースでは StateHasChanged を明示的に呼び出す必要はありませんが、ロジック側から表示を更新したいケースでは呼び出しを必要とします。</p><p>例えばサーバーからのデータ受信やタイマー処理といったユーザー操作によるイベントではないが表示を更新するようなパターンです。この場合は自らステートが変更されたので再レンダリングしてほしいという意思を伝えるために StateHasChanged を呼び出す必要があります。</p><h3 id="イベント発火時-bind-や-onclick-とか"><a href="#イベント発火時-bind-や-onclick-とか" class="headerlink" title="イベント発火時 (@bind や @onclick とか)"></a>イベント発火時 (@bind や @onclick とか)</h3><p>冒頭でプロジェクトテンプレートの Counter でクリックしてプライベート変数を更新するだけでページの表示が変わって不思議、と書きましたがそのような挙動になる理由はここにあります。</p><p>Blazor の ComponentBase ではイベントハンドラーの実行時に StateHasChanged を呼び出しています。</p><p><a href="https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Components/Components/src/ComponentBase.cs#L313" target="_blank" rel="noopener">https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Components/Components/src/ComponentBase.cs#L313</a></p><pre><code class="hljs csharp">Task IHandleEvent.HandleEventAsync(EventCallbackWorkItem callback, <span class="hljs-keyword">object</span>? arg)&#123; <span class="hljs-keyword">var</span> task = callback.InvokeAsync(arg); <span class="hljs-keyword">var</span> shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &amp;&amp; task.Status != TaskStatus.Canceled; <span class="hljs-comment">// After each event, we synchronously re-render (unless !ShouldRender())</span> <span class="hljs-comment">// This just saves the developer the trouble of putting "StateHasChanged();"</span> <span class="hljs-comment">// at the end of every event callback.</span> StateHasChanged(); <span class="hljs-keyword">return</span> shouldAwaitTask ? CallStateHasChangedOnAsyncCompletion(task) : Task.CompletedTask;&#125;</code></pre><p>これにより「クリックイベントが発生する」→「プライベート変数を更新する」→「StateHasChanged を呼び出す」→「再レンダリング」→「値が表示に反映される」という流れになっているというわけです。</p><p>変数を監視していたり、プロキシになっていたりするわけではなくイベントが発生したら StateHasChanged (=ステート変更通知)が呼び出されているだけという単純な仕組みです。</p><h3 id="Parameter-が変わったとき"><a href="#Parameter-が変わったとき" class="headerlink" title="Parameter が変わったとき"></a>Parameter が変わったとき</h3><p>コンポーネントの Parameter に渡される値が変化した場合もステートの変更がありと認識されます。</p><p>これも ComponentBase.SetParametersAsync の中で StateHasChanged を呼びだしているので、値の変更があると SetParametersAsync が呼び出されることで結果的にステートの変更があった扱いになります。</p><p><a href="https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Components/Components/src/ComponentBase.cs#L277" target="_blank" rel="noopener">https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Components/Components/src/ComponentBase.cs#L277</a></p><pre><code class="hljs csharp"><span class="hljs-function"><span class="hljs-keyword">private</span> Task <span class="hljs-title">CallOnParametersSetAsync</span>(<span class="hljs-params"></span>)</span>&#123; OnParametersSet(); <span class="hljs-keyword">var</span> task = OnParametersSetAsync(); <span class="hljs-comment">// If no async work is to be performed, i.e. the task has already ran to completion</span> <span class="hljs-comment">// or was canceled by the time we got to inspect it, avoid going async and re-invoking</span> <span class="hljs-comment">// StateHasChanged at the culmination of the async work.</span> <span class="hljs-keyword">var</span> shouldAwaitTask = task.Status != TaskStatus.RanToCompletion &amp;&amp; task.Status != TaskStatus.Canceled; <span class="hljs-comment">// We always call StateHasChanged here as we want to trigger a rerender after OnParametersSet and</span> <span class="hljs-comment">// the synchronous part of OnParametersSetAsync has run.</span> StateHasChanged(); <span class="hljs-keyword">return</span> shouldAwaitTask ? CallStateHasChangedOnAsyncCompletion(task) : Task.CompletedTask;&#125;</code></pre><h2 id="ステートが変更されると-StateHasChanged-が呼ばれると-何が起こるのか"><a href="#ステートが変更されると-StateHasChanged-が呼ばれると-何が起こるのか" class="headerlink" title="ステートが変更されると (StateHasChanged が呼ばれると) 何が起こるのか"></a>ステートが変更されると (StateHasChanged が呼ばれると) 何が起こるのか</h2><p>StateHasChanged メソッドを呼ぶと次のような流れで処理が実行されます。</p><ul><li>ShouldRender</li><li>BuildRenderTree</li><li>ステートの変更が子コンポーネントのParameterも変更した場合<ul><li>SetParametersAsync<ul><li>OnParameterSet</li><li>OnParameterSetAsync</li><li>ShouldRender</li></ul></li><li>BuildRenderTree</li></ul></li><li>OnAfterRender (firstRender = false)</li><li>OnAfterRenderAsync (firstRender = false)</li></ul><p>初回と似ていますが異なるのは初期化周りのメソッド(OnInitialized)が呼び出されないことと ShouldRender が呼ばれることです。</p><p>ShouldRender メソッドはレンダリングする必要があるかどうかを返すことができるものです。このメソッドのデフォルト実装は常に <code>true</code> を返しますが、カスタムコンポーネントは再レンダリングを抑える目的でオーバーライドできます。</p><h3 id="StateHasChanged-の詳細"><a href="#StateHasChanged-の詳細" class="headerlink" title="StateHasChanged の詳細"></a>StateHasChanged の詳細</h3><p>StateHasChanged の実装は ComponentBase クラスにあり、protected として公開されています。</p><p><a href="https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Components/Components/src/ComponentBase.cs#L100" target="_blank" rel="noopener">https://github.com/dotnet/aspnetcore/blob/v5.0.0/src/Components/Components/src/ComponentBase.cs#L100</a></p><p>このメソッド自体は比較的シンプルでやっていることは次のようになっています。</p><ul><li>未レンダリング状態または ShouldRender が true かどうかチェック</li><li>RenderHandle (のキュー) に RenderFragment を登録する<ul><li>RenderFragment は BuildRenderTree を呼び出す</li></ul></li></ul><p>先ほどの流れで ShouldRender の後に BuildRenderTree が呼び出されると書いていましたが、実際は一度レンダラーのキューに詰めてから BuildRenderTree が呼び出されます。</p><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>Blazor のライフサイクルやレンダリングでは StateHasChanged が重要な役割となります。</p><p>しかしイベントハンドラーのような暗黙的に呼び出されているケースもあり、Blazor の仕組みをあまり知らずに使っていても画面が更新されるので思わぬところでハマる可能性もあります。そういった罠を踏む前にある程度流れを抑えておくことをお勧めします。</p> ]]>
</content>
<summary type="html"> <p>これは <a href="https://qiita.com/advent-calendar/2020/blazor" target="_blank" rel="noopener">Blazor Advent Calendar 2020</a> の25日目のエントリーです。 </summary>
<category term="Blazor ASP.NET" scheme="http://www.misuzilla.org/Tags/Blazor-ASP-NET/"/>
</entry>
<entry>
<title>Blazor Web Assembly を publish すると AssemblyResolutionException でエラーとなる</title>
<link href="http://www.misuzilla.org/Blog/2020/08/24/AssemblyResolutionExceptionWhenPublishBlazorWebAsssembly"/>
<id>http://www.misuzilla.org/Blog/2020/08/24/AssemblyResolutionExceptionWhenPublishBlazorWebAsssembly</id>
<published>2020-08-24T12:55:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <h2 id="現象"><a href="#現象" class="headerlink" title="現象"></a>現象</h2><p>Visual Studio からの発行や <code>dotnet publish -c Release</code> などを実行したとき、IL Linker 実行中に以下のような <code>AssemblyResolutionException</code> がスローされます。</p><pre><code class="hljs undefined"> Fatal error in Mono IL LinkerC:\Users\Tomoyo\.nuget\packages\microsoft.aspnetcore.components.webassembly.build\3.2.1\targets\Blazor.MonoRuntime.targets(326,5): error : Unhandled exception. Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly: &apos;SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535&apos; [C:\Users\Tomoyo\Source\Repos\BlazorApp6\BlazorApp6\BlazorApp6.csproj] ---&gt; Mono.Cecil.AssemblyResolutionException: Failed to resolve assembly: &apos;SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535&apos; at Mono.Cecil.BaseAssemblyResolver.Resolve(AssemblyNameReference name, ReaderParameters parameters) at Mono.Linker.AssemblyResolver.Resolve(AssemblyNameReference name, ReaderParameters parameters) at Mono.Linker.LinkContext.Resolve(IMetadataScope scope) at Mono.Linker.LinkContext.Resolve(IMetadataScope scope) at Mono.Linker.LinkContext.ResolveReferences(AssemblyDefinition assembly) at Mono.Linker.Steps.LoadReferencesStep.ProcessReferences(AssemblyDefinition assembly) at Mono.Linker.Steps.LoadReferencesStep.ProcessAssembly(AssemblyDefinition assembly) at Mono.Linker.Steps.BaseStep.Process(LinkContext context) at Mono.Linker.Pipeline.ProcessStep(LinkContext context, IStep step) at Mono.Linker.Pipeline.Process(LinkContext context) at Mono.Linker.Driver.Run(ILogger customLogger) at Mono.Linker.Driver.Execute(String[] args, ILogger customLogger) at Mono.Linker.Driver.Main(String[] args)</code></pre><h2 id="原因"><a href="#原因" class="headerlink" title="原因"></a>原因</h2><p>IL Linker は依存しているアセンブリ参照をすべて検索して、不要な IL を削っていくというビルドステップです。</p><p>この例外(エラー)は依存先のアセンブリを探しているときにアセンブリが見つからなかった場合に発生するもので、通常 NuGet でパッケージ参照されているものに対しては発生しません。</p><p>しかし依存パッケージの作り次第では例外が発生する場合があります。例えば <code>Microsoft.CodeAnalysis.Workspaces.Common</code> のようなパッケージを参照すると発生します。</p><p>これは <code>Microsoft.CodeAnalysis.Workspaces.Common</code> が <a href="https://github.com/dotnet/roslyn/blob/c5e712b953db1f8e6376c6f2a6ca67632bf24144/src/Workspaces/Core/Portable/Microsoft.CodeAnalysis.Workspaces.csproj#L25" target="_blank" rel="noopener"><code>SQLitePCLRaw.bundle_green</code></a> パッケージを <code>PrivateAssets=&quot;all&quot;</code> として参照しているため、実際のパッケージにはパッケージ参照として <code>SQLitePCLRaw.bundle_green</code> が含まれないことでアプリケーションから参照したときにパッケージが解決されないのでアセンブリが見つからないということが発生します。</p><p>つまりパッケージの依存としては扱わないもののアセンブリを参照はあるという状況で発生します。必須ではないパッケージなどの場合にはこういった構成になります。</p><h2 id="解決方法"><a href="#解決方法" class="headerlink" title="解決方法"></a>解決方法</h2><p>解決方法としては次の2つの方法があります。</p><h3 id="1-必要なパッケージをプロジェクトで参照する"><a href="#1-必要なパッケージをプロジェクトで参照する" class="headerlink" title="1. 必要なパッケージをプロジェクトで参照する"></a>1. 必要なパッケージをプロジェクトで参照する</h3><p>見つからないといわれているアセンブリが含まれているパッケージをプロジェクトから参照します。</p><h3 id="2-IL-Linker-を無効にする"><a href="#2-IL-Linker-を無効にする" class="headerlink" title="2. IL Linker を無効にする"></a>2. IL Linker を無効にする</h3><p><code>SQLitePCLRaw.bundle_green</code> のようなものは参照してもエラーとなるのでそういった場合には IL Linker 自体を無効にします。</p><p><a href="https://docs.microsoft.com/ja-jp/aspnet/core/blazor/host-and-deploy/configure-linker?view=aspnetcore-3.1" target="_blank" rel="noopener">https://docs.microsoft.com/ja-jp/aspnet/core/blazor/host-and-deploy/configure-linker?view=aspnetcore-3.1</a></p><pre><code class="hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">PropertyGroup</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">BlazorWebAssemblyEnableLinking</span>&gt;</span>false<span class="hljs-tag">&lt;/<span class="hljs-name">BlazorWebAssemblyEnableLinking</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">PropertyGroup</span>&gt;</span></code></pre> ]]>
</content>
<summary type="html"> <h2 id="現象"><a href="#現象" class="headerlink" title="現象"></a>現象</h2><p>Visual Studio からの発行や <code>dotnet publish -c Release</code> などを実行したとき、 </summary>
<category term="CSharp Blazor" scheme="http://www.misuzilla.org/Tags/CSharp-Blazor/"/>
</entry>
<entry>
<title>ARM64 Windows 版 Visual Studio Code をビルドする</title>
<link href="http://www.misuzilla.org/Blog/2020/04/23/BuildVsCodeForArm64"/>
<id>http://www.misuzilla.org/Blog/2020/04/23/BuildVsCodeForArm64</id>
<published>2020-04-23T00:50:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p>Surface Pro X も発売され早数か月、今や多くの方が ARM64 版 Windows をご利用中かと思いますが(要出典)、Visual Studio Code の ARM64 版はまだリリースされていない状況です。もちろん x86 版をエミュレーションで利用できますが、Electron アプリはパフォーマンス的にかなり不利ですので ARM64 ネイティブなものが欲しいところです。</p><p><img src="/shared/Blog/img/2020/04/23/Screen-01.png" alt=""></p><p>Visual Studio Code のリポジトリで ARM64 対応がないかなと眺めていたところ ARM64 ビルドをやっていく PR が作られているのを発見し、マージされるのを watch していたのですが先日ついにマージされました。</p><p><a href="https://github.com/microsoft/vscode/pull/85326" target="_blank" rel="noopener">Add gulp targets, fix build for Windows on Arm. by richard-townsend-arm · Pull Request #85326 · microsoft/vscode</a></p><p>というわけで自分で VSCode リポジトリからビルドしてみたところ、手順が意外とわかりにくかったのでまとめておきます。</p><h2 id="ビルド準備-ツール"><a href="#ビルド準備-ツール" class="headerlink" title="ビルド準備 (ツール)"></a>ビルド準備 (ツール)</h2><p>大体は <a href="https://github.com/Microsoft/vscode/wiki/How-to-Contribute#prerequisites" target="_blank" rel="noopener">vscode リポジトリの今トリビュートガイドのビルド手順</a>に従って環境を用意します。</p><ul><li>Windows 10 (x64)</li><li>Git for Windows</li><li>Node.js (x64, 10.x 以上 12.x 以下)</li><li>Yarn</li><li>Python 2.7</li><li>Visual Studio 2017 ビルドツール (C/C++ コンパイラー)<ul><li>Visual C++ compilers and libraries for ARM64</li></ul></li><li>Visual C++ 2010 Redistributable (x86)</li></ul><p>まず、クロスコンパイルする形になるので x64 の Windows 環境が必要になります。もしかするとコンパイラーなどは動くかもしれませんが死ぬほど遅いでしょうし、Node.js もメモリーの都合なのか x64 版を用意するように書かれているので素直に x64 環境でビルドするのがよいでしょう(AzureやAWSで適当にVM立ててビルドするとか)。</p><p>Python とコンパイラーは <code>npm install -g windows-build-tools</code> でインストールできます。VSCode のガイドには <code>--vs2015</code> を指定して Visual Studio 2015 で…のようなことが書いてありますがそのまま Visual Studio 2017 でもビルドできます。</p><p>コンパイラーは <code>windows-build-tools</code> ではなく Visual Studio 2017 Build Tools を入れることでも大丈夫です。Visual Studio 2019 の場合でも 2017 のコンパイラーが入っていればビルドできそうな気がします。</p><p>さらに ARM64 版ビルドを作る場合には Visual Studio Installer から個別のコンポーネントとして <code>Visual C++ compilers and libraries for ARM64</code> というものを入れておく必要があります。</p><p>Visual C++ 2010 Redistributable (x86) も必要です。これはビルド途中で使われるリソース書き換えツールの rcedit を動かすのに必要です。</p><h2 id="ビルド準備-Node"><a href="#ビルド準備-Node" class="headerlink" title="ビルド準備 (Node)"></a>ビルド準備 (Node)</h2><p>ツールがそろったらコードを clone して、yarn でモジュールをインストールします。</p><pre><code class="hljs cmd">git clone https://github.com/microsoft/vscode.git</code></pre><p>と、yarn でモジュールをインストールする前にビルド対象のアーキテクチャを環境変数で設定しておきます。<br><pre><code class="hljs cmd"><span class="hljs-built_in">cd</span> vscode<span class="hljs-built_in">set</span> npm_config_arch=arm64<span class="hljs-built_in">set</span> npm_config_target_arch=arm64</code></pre></p><p>設定したら yarn を実行します。<br><pre><code class="hljs cmd">yarn install</code></pre></p><p>すると多分途中で <code>%USERPROFILE%\AppData\Local\node-gyp\Cache\12.4.0\arm64\node.lib</code> がないというようなエラーになるかと思います。</p><p>node-gyp で使われる lib がないということなので次の場所からダウンロードしてきて放り込みます。</p><p><a href="https://unofficial-builds.nodejs.org/download/release/v12.15.0/win-arm64/" target="_blank" rel="noopener">https://unofficial-builds.nodejs.org/download/release/v12.15.0/win-arm64/</a></p><p>本当はバージョンを合わせるべきな気がしますが、必ずしも同じバージョンのバイナリがあるわけではないのでそれっぽいバージョンを放り込みます。</p><p>ファイルを置いたらもう一度 <code>yarn install</code> を実行すると最後まで通るはずです。</p><h2 id="ビルド準備-VSCode"><a href="#ビルド準備-VSCode" class="headerlink" title="ビルド準備 (VSCode)"></a>ビルド準備 (VSCode)</h2><p>ここまできたらあとはビルドするだけですが、その前に少し VSCode のビルド設定を変更しておきます。</p><p>まず前提として VSCode リポジトリは Microsoft からリリースされている Visual Studio Code そのものではないのです。</p><p>VSCode リポジトリは <code>Code - OSS</code> というオープンソースな Visual Studio Code 部分です。一方 Microsoft がバイナリでリリースしている Visual Studio Code は <code>Code - OSS</code> にブランディングや Marketplace のエンドポイント設定、テレメトリーの有効化などのカスタマイズを行って、プロプラエタリなライセンスでリリースしているものとなっています。Chromium と Google Chrome とかも似たような感じかもしれません。</p><p>つまり <code>Code - OSS</code> そのままだと Marketplace を使えないので、API エンドポイントを設定してあげます。そのあたりは VSCode リポジトリを元にコミュニティーバイナリビルドを作っている <a href="https://github.com/VSCodium/vscodium" target="_blank" rel="noopener">VSCodium</a> を参考にして product.json を編集します。</p><pre><code class="hljs csharp">diff --git a/product.json b/product.jsonindex <span class="hljs-number">075</span>a1d0ada.<span class="hljs-number">.093</span>f517644 <span class="hljs-number">100644</span>--- a/product.json+++ b/product.json@@ <span class="hljs-number">-20</span>,<span class="hljs-number">6</span> +<span class="hljs-number">20</span>,<span class="hljs-number">9</span> @@ <span class="hljs-string">"licenseFileName"</span>: <span class="hljs-string">"LICENSE.txt"</span>, <span class="hljs-string">"reportIssueUrl"</span>: <span class="hljs-string">"https://github.com/Microsoft/vscode/issues/new"</span>, <span class="hljs-string">"urlProtocol"</span>: <span class="hljs-string">"code-oss"</span>,+ <span class="hljs-string">"quality"</span>: <span class="hljs-string">"stable"</span>,+ <span class="hljs-string">"extensionAllowedBadgeProviders"</span>: [<span class="hljs-string">"api.bintray.com"</span>, <span class="hljs-string">"api.travis-ci.com"</span>, <span class="hljs-string">"api.travis-ci.org"</span>, <span class="hljs-string">"app.fossa.io"</span>, <span class="hljs-string">"badge.fury.io"</span>, <span class="hljs-string">"badge.waffle.io"</span>, <span class="hljs-string">"badgen.net"</span>, <span class="hljs-string">"badges.frapsoft.com"</span>, <span class="hljs-string">"badges.gitter.im"</span>, <span class="hljs-string">"badges.greenkeeper.io"</span>, <span class="hljs-string">"cdn.travis-ci.com"</span>, <span class="hljs-string">"cdn.travis-ci.org"</span>, <span class="hljs-string">"ci.appveyor.com"</span>, <span class="hljs-string">"circleci.com"</span>, <span class="hljs-string">"cla.opensource.microsoft.com"</span>, <span class="hljs-string">"codacy.com"</span>, <span class="hljs-string">"codeclimate.com"</span>, <span class="hljs-string">"codecov.io"</span>, <span class="hljs-string">"coveralls.io"</span>, <span class="hljs-string">"david-dm.org"</span>, <span class="hljs-string">"deepscan.io"</span>, <span class="hljs-string">"dev.azure.com"</span>, <span class="hljs-string">"flat.badgen.net"</span>, <span class="hljs-string">"gemnasium.com"</span>, <span class="hljs-string">"githost.io"</span>, <span class="hljs-string">"gitlab.com"</span>, <span class="hljs-string">"godoc.org"</span>, <span class="hljs-string">"goreportcard.com"</span>, <span class="hljs-string">"img.shields.io"</span>, <span class="hljs-string">"isitmaintained.com"</span>, <span class="hljs-string">"marketplace.visualstudio.com"</span>, <span class="hljs-string">"nodesecurity.io"</span>, <span class="hljs-string">"opencollective.com"</span>, <span class="hljs-string">"snyk.io"</span>, <span class="hljs-string">"travis-ci.com"</span>, <span class="hljs-string">"travis-ci.org"</span>, <span class="hljs-string">"visualstudio.com"</span>, <span class="hljs-string">"vsmarketplacebadge.apphb.com"</span>, <span class="hljs-string">"www.bithound.io"</span>, <span class="hljs-string">"www.versioneye.com"</span>],+ <span class="hljs-string">"extensionsGallery"</span>: &#123;<span class="hljs-string">"serviceUrl"</span>: <span class="hljs-string">"https://marketplace.visualstudio.com/_apis/public/gallery"</span>, <span class="hljs-string">"cacheUrl"</span>: <span class="hljs-string">"https://vscode.blob.core.windows.net/gallery/index"</span>, <span class="hljs-string">"itemUrl"</span>: <span class="hljs-string">"https://marketplace.visualstudio.com/items"</span>&#125;, <span class="hljs-string">"extensionAllowedProposedApi"</span>: [ <span class="hljs-string">"ms-vscode.references-view"</span> ],</code></pre><h2 id="ビルド"><a href="#ビルド" class="headerlink" title="ビルド"></a>ビルド</h2><p>準備ができたらビルドを実行してコンパイルして配布物一式を作成します。</p><pre><code class="hljs cmd"><span class="hljs-built_in">set</span> NODE_ENV=productionyarn gulp vscode-win32-arm64yarn gulp vscode-win32-arm64-archive</code></pre><p><code>vscode-win32-arm64-archive</code> を実行すると <code>.build\win32-arm64\archive\VSCode-win32-arm64.zip</code> という ZIP ファイルが出来上がります。</p><p>というわけでこの一式を ARM64 環境へもっていって展開すれば Visual Studio Code 的なものを使えます(インストーラーは2020年4月現在ビルドできません)。</p><h2 id="ARM64-版の制約"><a href="#ARM64-版の制約" class="headerlink" title="ARM64 版の制約"></a>ARM64 版の制約</h2><ul><li>インストーラーがない</li><li>対応していない拡張がある (ネイティブバイナリを抱えているものなど)</li></ul><h2 id="Microsoft-Visual-Studio-Code-との違い"><a href="#Microsoft-Visual-Studio-Code-との違い" class="headerlink" title="Microsoft Visual Studio Code との違い"></a>Microsoft Visual Studio Code との違い</h2><p>ビルドの準備の途中でも書きましたが、手元でビルドしたものは Microsoft からリリースされるものとは異なります。</p><ul><li>アイコンが違う</li><li>名前が違う (スキームやレジストリ、設定ファイルのディレクトリ名など)</li><li>ライセンスが違う</li></ul><p>名前が違うこともあり設定は Visual Studio Code とは別の場所に保存されるものになります(つまり公式リリースが出た場合でも設定は別になります)。</p><p>ライセンスが違うのが実はちょっと罠なので注意が必要です。というのも Microsoft がリリースしている VSCode 拡張のライセンスは Microsoft 公式から配布されている Microsoft Visual Studio Code とともに使うことが許可されているものがあります(例えば Remote とか)ので注意してください。</p> ]]>
</content>
<summary type="html"> <p>Surface Pro X も発売され早数か月、今や多くの方が ARM64 版 Windows をご利用中かと思いますが(要出典)、Visual Studio Code の ARM64 版はまだリリースされていない状況です。もちろん x86 版をエミュレーションで利用できま </summary>
<category term="ARM64 VSCode" scheme="http://www.misuzilla.org/Tags/ARM64-VSCode/"/>
</entry>
<entry>
<title>.NET Core でスタックトレースからメソッド呼び出しを隠す</title>
<link href="http://www.misuzilla.org/Blog/2019/12/03/HideMethodCallFromStackTrace"/>
<id>http://www.misuzilla.org/Blog/2019/12/03/HideMethodCallFromStackTrace</id>
<published>2019-12-03T09:30:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p>このエントリーは<a href="https://qiita.com/advent-calendar/2019/c-sharp-2" target="_blank" rel="noopener">C# その2 Advent Calendar 2019</a>のエントリーです。</p><p>突然ですがスタックトレースから何らか自分の書いたメソッドを隠したいなと思ったことはないでしょうか?普段はそんなことを考える必要はないのですがアプリの下回りやミドルウェア的なものを作っているとたまにそういった場面に遭遇します。</p><p>例えば ASP.NET Core MVC の ActionFilter のようなフィルターチェーン的なものを作るといったパターンがあるとします。まず次のような感じのフィルター定義があって…</p><pre><code class="hljs csharp"><span class="hljs-keyword">interface</span> <span class="hljs-title">IFilter</span>&#123; <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">Invoke</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> context, Action&lt;<span class="hljs-keyword">object</span>&gt; next</span>)</span>;&#125;<span class="hljs-keyword">class</span> <span class="hljs-title">AFilter</span> : <span class="hljs-title">IFilter</span>&#123; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Invoke</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> context, Action&lt;<span class="hljs-keyword">object</span>&gt; next</span>)</span> &#123; Console.WriteLine(<span class="hljs-string">"A Begin"</span>); next(context); Console.WriteLine(<span class="hljs-string">"A End"</span>); &#125;&#125;<span class="hljs-keyword">class</span> <span class="hljs-title">BFilter</span> : <span class="hljs-title">IFilter</span>&#123; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Invoke</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> context, Action&lt;<span class="hljs-keyword">object</span>&gt; next</span>)</span> &#123; Console.WriteLine(<span class="hljs-string">"B Begin"</span>); next(context); Console.WriteLine(<span class="hljs-string">"B End"</span>); &#125;&#125;</code></pre><p>そのフィルターをつなげて呼び出すというような仕組みです。</p><pre><code class="hljs csharp"><span class="hljs-comment">// フィルターの一覧を作る (後ろに行くほうが内側 = 外側が AFilter, 内側が BFilter)</span><span class="hljs-keyword">var</span> filters = <span class="hljs-keyword">new</span> IFilter[] &#123; <span class="hljs-keyword">new</span> AFilter(), <span class="hljs-keyword">new</span> BFilter() &#125;;<span class="hljs-comment">// フィルターに渡す次のアクションの変数(その際最後になるアクションを入れておく)</span>Action&lt;<span class="hljs-keyword">object</span>&gt; next = (context) =&gt;&#123; <span class="hljs-comment">// 一番深いところでスタックトレースを取得する</span> Console.WriteLine(Environment.StackTrace);&#125;;<span class="hljs-comment">// フィルターチェーンを作る</span><span class="hljs-comment">// 内側から外に向かってつないでいく</span><span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> filter <span class="hljs-keyword">in</span> filters.Reverse())&#123; <span class="hljs-comment">// next をラムダにキャプチャーする必要がある</span> <span class="hljs-keyword">var</span> next_ = next; next = (context) =&gt; filter.Invoke(context, next_);&#125;<span class="hljs-comment">// 呼び出す</span>next(<span class="hljs-literal">null</span>);</code></pre><p>このコードの実行結果はおおよそこんな感じになります。</p><pre><code class="hljs plaintext">A BeginB Begin at System.Environment.get_StackTrace() at Program.&lt;&gt;c.&lt;Main&gt;b__4_0(Object context) in C:\Program.cs:line 9 at Program.BFilter.Invoke(Object context, Action`1 next) in C:\Program.cs:line 44 at Program.&lt;&gt;c__DisplayClass4_1.&lt;Main&gt;b__1(Object context) in C:\Program.cs:line 17 at Program.AFilter.Invoke(Object context, Action`1 next) in C:\Program.cs:line 35 at Program.&lt;&gt;c__DisplayClass4_1.&lt;Main&gt;b__1(Object context) in C:\Program.cs:line 17 at Program.Main() in C:\Program.cs:line 21 (略)B EndA End</code></pre><p>結果を見ていただくとわかるのですがスタックトレースに <code>Program.&lt;&gt;c.&lt;Main&gt;b__4_0(Object context)</code> といった呼び出しが現れていますが、単にキャプチャーして渡すだけのメソッドなのでユーザーにはほぼ意味のないものです。とはいえコード上でラムダを挟んでいるので出てくるのは当然です。</p><p>そこで何とかしてこのような些末な呼び出しをスタックトレースから隠すためのハックを今回ご紹介します。</p><h2 id="案1-StackTraceHiddenAttribute-を使う"><a href="#案1-StackTraceHiddenAttribute-を使う" class="headerlink" title="案1. StackTraceHiddenAttribute を使う"></a>案1. <code>StackTraceHiddenAttribute</code> を使う</h2><p>.NET Core 2.0 には <code>StackTraceHiddenAttribute</code> という属性が追加されていて、その属性のついたメソッドは<a href="https://github.com/dotnet/coreclr/blob/release/2.1/src/mscorlib/src/System/Diagnostics/Stacktrace.cs#L628" target="_blank" rel="noopener">スタックトレースから除外される</a>ようになっています。</p><p>ということはこれを使えば解決しそうですが残念ながらこの属性は <code>internal</code> です。効果を考えたらそんなカジュアルに使われても困るので内部向けに用意したというところでしょうか。</p><p>とはいえそれでも動的アセンブリ生成なら無理やり属性を引っ張り出してくっつけることができるはず…!イメージとしては次のようなヘルパークラスの <code>InvokeNext</code> の部分を動的に作ってうまいことするような感じです。</p><pre><code class="hljs csharp"><span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span></span><span class="hljs-comment"><span class="hljs-doctag">///</span> 次のフィルター呼び出しと、フィルターのメソッドを保持するクラス。</span><span class="hljs-comment"><span class="hljs-doctag">///</span> 以前のラムダのキャプチャーと同等。</span><span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;/summary&gt;</span></span><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">InvokeHelper</span>&#123; <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>フィルターのメソッド<span class="hljs-doctag">&lt;/summary&gt;</span></span> <span class="hljs-keyword">public</span> Action&lt;<span class="hljs-keyword">object</span>, Action&lt;<span class="hljs-keyword">object</span>&gt;&gt; Invoke; <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>次のフィルターの呼び出しとなるデリゲート<span class="hljs-doctag">&lt;/summary&gt;</span></span> <span class="hljs-keyword">public</span> Action&lt;<span class="hljs-keyword">object</span>&gt; Next; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">InvokeHelper</span>(<span class="hljs-params">Action&lt;<span class="hljs-keyword">object</span>, Action&lt;<span class="hljs-keyword">object</span>&gt;&gt; invoke, Action&lt;<span class="hljs-keyword">object</span>&gt; next</span>)</span> &#123; Invoke = invoke; Next = next; &#125; [<span class="hljs-meta">StackTraceHidden</span>] <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">InvokeNext</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> context</span>)</span> &#123; Invoke(context, Next); &#125;&#125;</code></pre><p>ということでILをペコペコ書きます。</p><script src="https://gist.github.com/mayuki/9df7c33b8365faeba2c04b8f8b5d6ac0.js?file=InvokeHelper1.cs"></script><p>そしてフィルターチェーンを作るところをヘルパー経由に書き換え。</p><pre><code class="hljs csharp"><span class="hljs-keyword">foreach</span> (<span class="hljs-keyword">var</span> filter <span class="hljs-keyword">in</span> filters.Reverse())&#123; next = <span class="hljs-keyword">new</span> InvokeHelper&lt;<span class="hljs-keyword">object</span>, Action&lt;<span class="hljs-keyword">object</span>&gt;&gt;(filter.Invoke, next).GetDelegate();&#125;</code></pre><p>そして実行すると…。</p><pre><code class="hljs plaintext">A BeginB Begin at System.Environment.get_StackTrace() at Program.&lt;&gt;c.&lt;Main&gt;b__4_0(Object context) in C:\Program.cs:line 9 at Program.BFilter.Invoke(Object context, Action`1 next) in C:\Program.cs:line 44 at Program.AFilter.Invoke(Object context, Action`1 next) in C:\Program.cs:line 35 at Program.Main() in C:\Program.cs:line 21 (略)B EndA End</code></pre><p>ばっちり <code>BFitler</code> と <code>AFitler</code> の間にあった呼び出し行が消えました!めでたしめでたし。</p><h2 id="案2-動的メソッド生成"><a href="#案2-動的メソッド生成" class="headerlink" title="案2. 動的メソッド生成"></a>案2. 動的メソッド生成</h2><p>案1でめでたしめでたしとなるかと思いきや、実は案1を試している間に気づいたのですが <code>StackTraceHiddenAttribute</code> をつけなくても消えます。案1の下記の二行を削除してみても結果は変わりません。</p><pre><code class="hljs csharp"><span class="hljs-keyword">var</span> attrStacktraceHiddenAttribute = Type.GetType(<span class="hljs-string">"System.Diagnostics.StackTraceHiddenAttribute"</span>);method.SetCustomAttribute(<span class="hljs-keyword">new</span> CustomAttributeBuilder(attrStacktraceHiddenAttribute.GetConstructor(Array.Empty&lt;Type&gt;()), Array.Empty&lt;<span class="hljs-keyword">object</span>&gt;()));</code></pre><p>どうも CoreCLR の中も少し調べてみたのですがよくわからず、動的生成されたメソッドがスタックトレース的に何らかの特別扱いされることがあるようです。</p><p>というわけで動的にメソッドを定義できれば理屈は謎ですが消えますし、アセンブリを生成しなくとも <code>DynamicMethod</code> で事足ります。</p><script src="https://gist.github.com/mayuki/9df7c33b8365faeba2c04b8f8b5d6ac0.js?file=InvokeHelper2.cs"></script><h2 id="案3-MethodImpl-MethodImplOptions-AggressiveInlining-を使う"><a href="#案3-MethodImpl-MethodImplOptions-AggressiveInlining-を使う" class="headerlink" title="案3. [MethodImpl(MethodImplOptions.AggressiveInlining)] を使う"></a>案3. <code>[MethodImpl(MethodImplOptions.AggressiveInlining)]</code> を使う</h2><p><code>StackTraceHiddenAttribute</code> のあたりのコードを見ていて気が付いたのですが <a href="https://github.com/dotnet/coreclr/blob/release/3.0/src/System.Private.CoreLib/shared/System/Diagnostics/StackTrace.cs#L343-L350" target="_blank" rel="noopener">.NET Core 3.0 以降では [MethodImpl(MethodImplOptions.AggressiveInlining)] がついているかどうかもチェックしています</a>。</p><p>これは通常JITでインライン化されるとスタックトレースから消えてしまうけれども、Tiered Compilation の Tier 0 ではインライン化されないので表示されてしまったりして一貫性がないので属性がついているものは丸ごと非表示にしてしまおうということのようです。</p><p>ということで裏技っぽいですが <code>[MethodImpl(MethodImplOptions.AggressiveInlining)]</code> をつければ非表示になります。内容的にもインライン化されたところで困るものでもないですしよさそうです。</p><p>ヘルパークラスのイメージとしてはこんな感じです。</p><pre><code class="hljs csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">InvokeHelper</span>&#123; <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>フィルターのメソッド<span class="hljs-doctag">&lt;/summary&gt;</span></span> <span class="hljs-keyword">public</span> Action&lt;<span class="hljs-keyword">object</span>, Action&lt;<span class="hljs-keyword">object</span>&gt;&gt; Invoke; <span class="hljs-comment"><span class="hljs-doctag">///</span> <span class="hljs-doctag">&lt;summary&gt;</span>次のフィルターの呼び出しとなるデリゲート<span class="hljs-doctag">&lt;/summary&gt;</span></span> <span class="hljs-keyword">public</span> Action&lt;<span class="hljs-keyword">object</span>&gt; Next; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">InvokeHelper</span>(<span class="hljs-params">Action&lt;<span class="hljs-keyword">object</span>, Action&lt;<span class="hljs-keyword">object</span>&gt;&gt; invoke, Action&lt;<span class="hljs-keyword">object</span>&gt; next</span>)</span> &#123; Invoke = invoke; Next = next; &#125; [<span class="hljs-meta">MethodImpl(MethodImplOptions.AggressiveInlining)</span>] <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">InvokeNext</span>(<span class="hljs-params"><span class="hljs-keyword">object</span> context</span>)</span> &#123; Invoke(context, Next); &#125;&#125;</code></pre><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>ということでこれをまとめると .NET Core 3.0 以前では動的メソッド定義、それ以外では <code>AggressiveInlining</code> をつけておくというのがよさそうです。シンプルですし、もし挙動が変わってスタックトレースが出るようになっても <code>AggressiveInlining</code> がついているだけなのでプログラムの挙動には影響を与えないのもよいですね。</p><script src="https://gist.github.com/mayuki/9df7c33b8365faeba2c04b8f8b5d6ac0.js?file=InvokeHelper3.cs"></script><p>このハックは実際に<a href="https://github.com/Cysharp/MagicOnion" target="_blank" rel="noopener">MagicOnion</a>のフィルター周りに入れていてスタックトレースの見やすさを向上させています。</p><h3 id="ハックなし"><a href="#ハックなし" class="headerlink" title="ハックなし"></a>ハックなし</h3><p><img src="https://user-images.githubusercontent.com/9012/68530724-6ed4e580-034e-11ea-91c2-3aea9006375d.png" alt="Before"></p><h3 id="ハックあり"><a href="#ハックあり" class="headerlink" title="ハックあり"></a>ハックあり</h3><p><img src="https://user-images.githubusercontent.com/9012/68530758-b196bd80-034e-11ea-86fb-5536d47b2180.png" alt="After"></p><p>スタックトレースは開発において重要な情報なのでノイズは少なければ少ないほうが望ましいですし、アプリケーションの基盤に近い部分ではそういった開発体験を考慮しておくのは重要だと思います。今回のようなハックも機会があればお役立てください。</p><p>なお、あくまでハックなのでいつまで効果があるかといった点は保証できませんのであらかじめご了承ください。</p> ]]>
</content>
<summary type="html"> <p>このエントリーは<a href="https://qiita.com/advent-calendar/2019/c-sharp-2" target="_blank" rel="noopener">C# その2 Advent Calendar 2019</a>のエントリーです </summary>
<category term=".NET CSharp" scheme="http://www.misuzilla.org/Tags/NET-CSharp/"/>
</entry>
<entry>
<title>マンモス展</title>
<link href="http://www.misuzilla.org/Blog/2019/11/04/MammothTen"/>
<id>http://www.misuzilla.org/Blog/2019/11/04/MammothTen</id>
<published>2019-11-04T14:30:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p><a data-flickr-embed="true" href="https://www.flickr.com/photos/mayuki/49012305348/in/dateposted/" title="DSC03921.jpg" target="_blank" rel="noopener"><img src="https://live.staticflickr.com/65535/49012305348_db8143bcfa_k.jpg" alt="DSC03921.jpg"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script></p><p><a data-flickr-embed="true" href="https://www.flickr.com/photos/mayuki/49013048542/in/dateposted/" title="DSC03927.jpg" target="_blank" rel="noopener"><img src="https://live.staticflickr.com/65535/49013048542_e7121b5da8_k.jpg" alt="DSC03927.jpg"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script></p><p><a data-flickr-embed="true" href="https://www.flickr.com/photos/mayuki/49013059077/in/dateposted/" title="DSC03938.jpg" target="_blank" rel="noopener"><img src="https://live.staticflickr.com/65535/49013059077_e9caa5d696_k.jpg" alt="DSC03938.jpg"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script></p><p>4万年以上前の仔ウマが丸ごと出てくるのすごい。</p><p><a href="https://www.mammothten.jp/guide/" target="_blank" rel="noopener">音声ガイドを東山奈央さんがナビゲートされていた</a>ので折角なので借りてみました。ジュニアガイドとはいえTips的な話もあり、比較的ライトな雰囲気はこれはこれで良かったです。</p> ]]>
</content>
<summary type="html"> <p><a data-flickr-embed="true" href="https://www.flickr.com/photos/mayuki/49012305348/in/dateposted/" title="DSC03921.jpg" target="_blank" r </summary>
</entry>
<entry>
<title>HttpClient の StringContent は charset を付ける</title>
<link href="http://www.misuzilla.org/Blog/2019/11/04/HttpClientStringContentCharsetParameter"/>
<id>http://www.misuzilla.org/Blog/2019/11/04/HttpClientStringContentCharsetParameter</id>
<published>2019-11-04T14:00:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <h2 id="tl-dr"><a href="#tl-dr" class="headerlink" title="tl;dr"></a>tl;dr</h2><ul><li>.NET の <code>HttpClient</code> の <code>StringContent</code> は自動で <code>Content-Type</code> に <code>charset=&lt;encoding&gt;</code> を付ける</li><li>不要な場合は <code>StringContent.Headers.ContentType</code> を手で設定しなおす必要がある</li></ul><h2 id="はじまり"><a href="#はじまり" class="headerlink" title="はじまり"></a>はじまり</h2><p><code>HttpClient</code> でとある API (POST) を呼び出すとなぜか動かない… <code>curl</code> で同じ内容を投げると動くのに…という相談を受けて、そんな不思議なことが?と思って調べてみたのが始まりでした。</p><h2 id="NET-の-HttpClient-StringContent-は自動で-charset-を付ける"><a href="#NET-の-HttpClient-StringContent-は自動で-charset-を付ける" class="headerlink" title=".NET の HttpClient, StringContent は自動で charset を付ける"></a>.NET の HttpClient, StringContent は自動で charset を付ける</h2><p><code>curl</code> で動いているなら .NET の HttpClient が投げるものが何か違うのではと思って、リクエストやリクエストを作っているところを確認してみました。</p><p><a href="https://github.com/dotnet/corefx/blob/dbf74fac15dad697ae7e7c4d88327c0478f465ef/src/System.Net.Http/src/System/Net/Http/StringContent.cs#L30-L34" target="_blank" rel="noopener">https://github.com/dotnet/corefx/blob/dbf74fac15dad697ae7e7c4d88327c0478f465ef/src/System.Net.Http/src/System/Net/Http/StringContent.cs#L30-L34</a></p><pre><code class="hljs csharp"><span class="hljs-comment">// Initialize the 'Content-Type' header with information provided by parameters.</span>MediaTypeHeaderValue headerValue = <span class="hljs-keyword">new</span> MediaTypeHeaderValue((mediaType == <span class="hljs-literal">null</span>) ? DefaultMediaType : mediaType);headerValue.CharSet = (encoding == <span class="hljs-literal">null</span>) ? HttpContent.DefaultStringEncoding.WebName : encoding.WebName;Headers.ContentType = headerValue;</code></pre><p><code>StringContent</code> クラスのコンストラクターのコードではメディアタイプとともに <code>CharSet</code> プロパティを設定したものをヘッダーの <code>ContentType</code> プロパティに設定しています。この <code>CharSet</code> プロパティは Content-Type の charset 指定になります。</p><p>つまり <code>StringContent</code> クラスを使って投げるリクエストを作るとデフォルトで <code>application/json; charset=utf-8</code> のような Content-Type で送られるということになります。</p><h2 id="ContentType-を再設定する"><a href="#ContentType-を再設定する" class="headerlink" title="ContentType を再設定する"></a><code>ContentType</code> を再設定する</h2><p><code>charset</code> パラメータが必ずついてしまうということが分かったので、付けずにリクエストを送信したいという場合にはこれを変える必要があります。</p><p>といってもやることは簡単で後から手動で <code>ContentType</code> プロパティを再設定してあげれば大丈夫です。</p><pre><code class="hljs csharp">content.Headers.ContentType = <span class="hljs-keyword">new</span> System.Net.Http.Headers.MediaTypeHeaderValue(<span class="hljs-string">"application/json"</span>);</code></pre> ]]>
</content>
<summary type="html"> <h2 id="tl-dr"><a href="#tl-dr" class="headerlink" title="tl;dr"></a>tl;dr</h2><ul> <li>.NET の <code>HttpClient</code> の <code>StringContent </summary>
<category term="CSharp" scheme="http://www.misuzilla.org/Tags/CSharp/"/>
</entry>
<entry>
<title>Real World .NET Core on Kubernetes という話をしました</title>
<link href="http://www.misuzilla.org/Blog/2019/10/21/RealWorld.NETCoreOnKubernetes"/>
<id>http://www.misuzilla.org/Blog/2019/10/21/RealWorld.NETCoreOnKubernetes</id>
<published>2019-10-21T03:50:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p>10月18日に <a href="https://jaws-dotnet.connpass.com/event/146583/" target="_blank" rel="noopener">AWS .NET Developer User Group 勉強会 #2</a> にて Real World .NET Core on Kubernetes というセッションで話させていただきました。このエントリーはそのフォローアップです。</p><script async class="speakerdeck-embed" data-id="49b2687be68d469798ec5ae647a99403" data-ratio="1.77777777777778" src="//speakerdeck.com/assets/embed.js"></script><p><a href="https://lifebear.com/" target="_blank" rel="noopener">Lifebear</a> のサーバーアプリケーション (.NET Core) を Amazon EKS / Kubernetes 環境を構築してリリースするまでにメモしていたポイントまとめた形です。</p><p>「EKS/AKS/GKE で Kubernetes 立てて、.NET Core アプリをデプロイしてみた(使ってみた)」より一歩進んだ話が欲しいと思っていたのでそれがテーマでした。</p><p>.NET Core のアプリケーションを Kubernetes の上で動かしたという国内の実例というのは少なくて、自分で構築する際にも不安になったので、今後 Kubernetes でシステムを構築しようという人の参考になってほしいなと。…と思って書いたらちょっと盛りすぎてしまったので、Kuberentes 寄りの話と .NET Core 寄りの話を分ける方がよかったかなというのが反省点ですね。</p><h2 id="知見いろいろ"><a href="#知見いろいろ" class="headerlink" title="知見いろいろ"></a>知見いろいろ</h2><p>ネタ帳にはあったものの時間の関係上話せなかったスライドに書いた以外のこともこの際なので書いておきます(主に Kubernetes)。</p><h3 id="Docker-build-は-BuildKit-使おう"><a href="#Docker-build-は-BuildKit-使おう" class="headerlink" title="Docker build は BuildKit 使おう"></a>Docker build は BuildKit 使おう</h3><p>BuildKit でビルドすると高速かつキャッシュできるので有効にするのがおすすめです。特に NuGet パッケージのリストアに効果があります。</p><p>環境変数で <code>DOCKER_BUILDKIT=1</code> を指定して Dockerfile でおまじないを追加するだけです。</p><pre><code class="hljs Dockerfile"><span class="hljs-comment"># syntax = docker/dockerfile:experimental</span>...<span class="hljs-keyword">RUN</span> --mount=type=cache,target=/root/.nuget/ \ dotnet restore "WebApplication1.csproj" -c Release -o /app</code></pre><ul><li><a href="https://medium.com/nttlabs/docker-v18-09-%E6%96%B0%E6%A9%9F%E8%83%BD-%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%83%93%E3%83%AB%E3%83%89-%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3-9534714c26e2" target="_blank" rel="noopener">Docker 18.09 新機能 (イメージビルド&amp;セキュリティ) - nttlabs - Medium</a></li></ul><h3 id="Kuberentes-のバージョンアップは大変"><a href="#Kuberentes-のバージョンアップは大変" class="headerlink" title="Kuberentes のバージョンアップは大変"></a>Kuberentes のバージョンアップは大変</h3><p>Kubernetes のクラスターのバージョンは1マイナーバージョンアップごとに上げることになります。これはバージョンジャンプが大変なので日々追いかけておくつもりが必要ということです。</p><p>kubectl や kubelet のバージョンポリシーもあり、例えば kubelet は api-server に対して前2マイナーバージョン、kubectl はプラスマイナス1マイナーバージョンといった制約がありますのでどのみち大ジャンプが難しいということです。</p><ul><li><a href="https://kubernetes.io/docs/setup/release/version-skew-policy/" target="_blank" rel="noopener">Kubernetes version and version skew support policy - Kubernetes</a></li></ul><h3 id="ノードにはアプリケーション以外のものも動いている"><a href="#ノードにはアプリケーション以外のものも動いている" class="headerlink" title="ノードにはアプリケーション以外のものも動いている"></a>ノードにはアプリケーション以外のものも動いている</h3><p>通常ノードを落とす時には <code>kubectl drain</code> するのが普通です。ある時 <code>kubectl drain</code> しなくても <code>kubectl cordon</code> してアプリを再デプロイして Pod を別なところに移せばそのままノードを落とせるのではと思い、やってみると CoreDNS 等のサービスがいなくなって死にました(もちろん対象のノードにいなければ無傷)。それはそう…。</p><h3 id="Amazon-EKS-用のノードは-AMI-は作らない"><a href="#Amazon-EKS-用のノードは-AMI-は作らない" class="headerlink" title="Amazon EKS 用のノードは AMI は作らない"></a>Amazon EKS 用のノードは AMI は作らない</h3><p>基本カスタマイズは UserData で起動時に何とかします。AMI作っても絶対管理できないですし、ノードはコンテナーを動かすためだけの代物なので。</p><p><code>/etc/eks/bootstrap.sh</code> というものに kubelet のパラメータなどを渡せるのでそこでやりましょう。</p><ul><li><a href="https://aws.amazon.com/blogs/opensource/improvements-eks-worker-node-provisioning/" target="_blank" rel="noopener">Improvements for Amazon EKS Worker Node Provisioning | AWS Open Source Blog</a></li></ul><h3 id="デプロイ時にサービスから外れるのを待つ"><a href="#デプロイ時にサービスから外れるのを待つ" class="headerlink" title="デプロイ時にサービスから外れるのを待つ"></a>デプロイ時にサービスから外れるのを待つ</h3><p>Pod のシャットダウンと Service から外れるタイミングが異なるため、先に Pod がシャットダウンすると接続しようとしてしまうという話。<code>preStop</code> フックで数秒待って外れてからシャットダウンへ進むようにします。</p><ul><li><a href="https://qiita.com/superbrothers/items/3ac78daba3560ea406b2" target="_blank" rel="noopener">Kubernetes: 詳解 Pods の終了 - Qiita</a></li></ul><pre><code class="hljs yaml"><span class="hljs-attr">apiVersion:</span> <span class="hljs-string">apps/v1</span><span class="hljs-attr">kind:</span> <span class="hljs-string">Deployment</span><span class="hljs-attr">metadata:</span><span class="hljs-attr"> name:</span> <span class="hljs-string">myapp</span><span class="hljs-attr">spec:</span><span class="hljs-attr"> template:</span><span class="hljs-attr"> spec:</span><span class="hljs-attr"> containers:</span><span class="hljs-attr"> - name:</span> <span class="hljs-string">myapp</span><span class="hljs-attr"> lifecycle:</span> <span class="hljs-comment"># シャットダウン前に少し待たないと Service から削除されるより先に Pod が終了してしまうことがある</span> <span class="hljs-comment"># https://qiita.com/superbrothers/items/3ac78daba3560ea406b2</span><span class="hljs-attr"> preStop:</span><span class="hljs-attr"> exec:</span><span class="hljs-attr"> command:</span> <span class="hljs-string">["sh",</span> <span class="hljs-string">"-c"</span><span class="hljs-string">,</span> <span class="hljs-string">"sleep 5"</span><span class="hljs-string">]</span></code></pre><h3 id="Amazon-EKS-では-Private-Endpoint-を使う"><a href="#Amazon-EKS-では-Private-Endpoint-を使う" class="headerlink" title="Amazon EKS では Private Endpoint を使う"></a>Amazon EKS では Private Endpoint を使う</h3><p>Kubernetes API のエンドポイントをインターネット側からアクセスできないようにする設定が追加されたので特にプロダクション環境などでは有効にするのがおすすめです。</p><ul><li><a href="https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/cluster-endpoint.html" target="_blank" rel="noopener">Amazon EKS クラスターエンドポイントのアクセスコントロール - Amazon EKS</a></li></ul><p>ただし、当然外部サービスから Kubernetes API を呼び出す難易度が上がるのでその点は考慮する必要があります(その制約で Azure Pipelines の Environments を使えなかった)。</p><h3 id="VPC-のプライベートアドレスが枯渇"><a href="#VPC-のプライベートアドレスが枯渇" class="headerlink" title="VPC のプライベートアドレスが枯渇"></a>VPC のプライベートアドレスが枯渇</h3><p>Amazon EKS では Pod に対してプライベートIPを割り当てるのですが、普通の EC2 インスタンスの気持ちで小さめのサブネットにしていると Pod がぽこぽこ増えた時に困ったことになります。</p><p>アドレス空間はある程度余裕を持った形にしておくのがよいです。</p><h3 id="OOMKilled-や-CrashLoopBackOff-を監視する"><a href="#OOMKilled-や-CrashLoopBackOff-を監視する" class="headerlink" title="OOMKilled や CrashLoopBackOff を監視する"></a>OOMKilled や CrashLoopBackOff を監視する</h3><p>Kubernetes 上で発生したエラーイベントを監視しておきましょう。Datadog では Kubernetes のイベントも監視できるのでひっかけて通知するようにしていると異変に気付きやすくなります。</p><p>CrashLoopBackOff は発生していても、以前の Pod にはアクセスできるので他のきっかけがないと気づきにくくなります。</p><h3 id="コンテナーの中で-docker-を動かす"><a href="#コンテナーの中で-docker-を動かす" class="headerlink" title="コンテナーの中で docker を動かす"></a>コンテナーの中で docker を動かす</h3><p>Docker イメージを CI でビルドするのに CI のエージェントが Docker で動いているとそのままでは Docker 動かない問題にあたります。そこで俗にいう Docker in Docker (dind) です。</p><p>イメージをビルドするだけなので、ほかにも img とか Rootless Docker とか kaniko とか検討できるものはあるのですが無理しない方針で無難に Docker in Docker にしました。</p><ul><li><a href="https://github.com/genuinetools/img" target="_blank" rel="noopener">genuinetools/img: Standalone, daemon-less, unprivileged Dockerfile and OCI compatible container image builder.</a></li></ul><p><code>privileged: true</code> にしないといけないのでそこは微妙なのですが、今ならもしかすると Rootless でうまいこと <code>privileged: true</code> しなくてもいけるかもしれません。</p><h3 id="Kubernetes-の-API-を試すときは-Pod-に入って-curl-するのがオススメ"><a href="#Kubernetes-の-API-を試すときは-Pod-に入って-curl-するのがオススメ" class="headerlink" title="Kubernetes の API を試すときは Pod に入って curl するのがオススメ"></a>Kubernetes の API を試すときは Pod に入って curl するのがオススメ</h3><p>Kubernetes の API にアクセスする場合 ServiceAccount の設定をミスっていたりするとうまくアクセスできなくなったりするのですが、これをアプリデプロイして試すのは大変なので <code>kubectl exec podname -it /bin/bash</code> で入って <code>curl</code> で叩くのがおすすめです。</p><h3 id="Pod-が偏る"><a href="#Pod-が偏る" class="headerlink" title="Pod が偏る"></a>Pod が偏る</h3><p>通常、Pod をデプロイするときには Kubernetes がリソースの具合を見て適度にバランスしてスケジューリングします。一方でデプロイ後に再スケジュールはしてくれません。</p><p>例えば、2つあるうちの1つのノードが死んだ場合はそのノードで動いていた分の Pod が生きているノードで起動されなおします。そして、その後ノードが復活してきたとしても Pod たちは元のノードに帰っていったりはしません。偏ったままです。</p><p>これを解決する手っ取り早い方法としては再デプロイかレプリカ数を上げたり下げたり、要するに Pod の再作成です。</p><p>他には Kubernetes の拡張として Descheduler という再スケジュールしなおすものが開発されているのでこちらの利用を検討してもいいかもしれません(が、鋭意開発中っぽい雰囲気があります)。</p><ul><li><a href="https://github.com/kubernetes-sigs/descheduler" target="_blank" rel="noopener">kubernetes-sigs/descheduler: Descheduler for Kubernetes</a></li></ul><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>振り返ってみると大変なのはアプリそのものよりは中小規模の Kubernetes クラスターを構築、運用するノウハウというところが大きい気がしました。</p><p>繰り返しにはなりますが、.NET Core を Kubernetes で実際に動かしてリリースまでもっていっているという事例の参考になって、事例がもっと出てきてほしいですね。</p><p>というわけで<a href="https://lifebear.com/" target="_blank" rel="noopener">ライフベア</a>ではほとんど好き勝手構築させていただいたので大変感謝しています。もし .NET Core (C#) + Kubernetes な環境ににご興味のある方はご一報いただくか、直接コンタクトしていただくと良いかと思います。</p><p>そして <a href="https://cysharp.co.jp/" target="_blank" rel="noopener">Cysharp</a> では C# (サーバー、クライアント) に関するご相談を受け付けておりますのでお困りのことがあればぜひ<a href="https://cysharp.co.jp/contact/" target="_blank" rel="noopener">お問い合わせください</a>。</p> ]]>
</content>
<summary type="html"> <p>10月18日に <a href="https://jaws-dotnet.connpass.com/event/146583/" target="_blank" rel="noopener">AWS .NET Developer User Group 勉強会 #2</a> </summary>
<category term=".NET Kubernetes" scheme="http://www.misuzilla.org/Tags/NET-Kubernetes/"/>
</entry>
<entry>
<title>C# で null 許容型あれこれ</title>
<link href="http://www.misuzilla.org/Blog/2019/09/25/NullableReferenceTypes"/>
<id>http://www.misuzilla.org/Blog/2019/09/25/NullableReferenceTypes</id>
<published>2019-09-25T03:30:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p>.NET Core 3.0 のリリースとともに C# 8.0 がきて、null 許容型 (nullable reference types) を使えるようになったので使ってみたメモです。</p><h2 id="未初期化だけど後で初期化するパターン"><a href="#未初期化だけど後で初期化するパターン" class="headerlink" title="未初期化だけど後で初期化するパターン"></a>未初期化だけど後で初期化するパターン</h2><p>コンストラクターではまだ初期化しないのだけど、いずれフレームワークから初期化するので API を利用する側はほぼ null を扱わない、けど内部の初期状態は null なのを許してほしいというケースはまあよくあります。Kotlin の <code>lateinit</code> や <a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-7.html#strict-class-initialization" target="_blank" rel="noopener">TypeScript の definite assignment assertion</a> みたいなやつですね。</p><p>C# ではズバリそれっぽいものはないので <code>default!</code> を初期値として代入しておけばよいです。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=1.cs"></script><p><code>lateinit</code> とかもそうですが適当に使うと治安が最高に悪くなるので気を付けたほうがいいやつです。</p><h2 id="ジェネリクス"><a href="#ジェネリクス" class="headerlink" title="ジェネリクス"></a>ジェネリクス</h2><p>class 制約のないジェネリクスの場合 <code>?</code> をつけて null 許容型であると宣言できません。そのため <code>null</code> を扱うかもしれない、というケースを宣言するには属性を使用することになります。</p><p>逆を言えば <code>where T: class</code> や <code>where T: unmanaged</code> のような制約をつけておけば <code>T?</code> と null 許容型を素直に書くことができるので書けるときは書いてしまうのが手です。</p><h3 id="AllowNull-属性と-DisallowNull-属性-事前条件"><a href="#AllowNull-属性と-DisallowNull-属性-事前条件" class="headerlink" title="AllowNull 属性と DisallowNull 属性 (事前条件)"></a><code>AllowNull</code> 属性と <code>DisallowNull</code> 属性 (事前条件)</h3><p>例えば次のようなメソッドの引数で参照型のときは null 渡したいケースがあります。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=2.cs"></script><p>そのままでは非許容なので null が来てもよいということをコンパイラーに伝える必要があるので、 <code>AllowNull</code> 属性を使用します。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=3.cs"></script><p>逆に null が入ってきてほしくないというのを明示することもできます。例えば先ほどのコードをもう一度。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=4.cs"></script><p>このような場合は <code>DisallowNull</code> 属性を使用すると null を許さないということを明示できます。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=5.cs"></script><p><code>DisallowNull</code> 属性は .NET Core のコードであれば <a href="https://github.com/dotnet/corefx/blob/4f558e67891b034639b8ec4f8768cf369e64ef74/src/Common/src/CoreLib/System/Collections/Generic/IEqualityComparer.cs#L15" target="_blank" rel="noopener"><code>IEqualityComparer&lt;T&gt;.GetHashCode</code></a> などに見られます。</p><h3 id="MaybeNull-属性と-NotNull-属性-事後条件"><a href="#MaybeNull-属性と-NotNull-属性-事後条件" class="headerlink" title="MaybeNull 属性と NotNull 属性 (事後条件)"></a><code>MaybeNull</code> 属性と <code>NotNull</code> 属性 (事後条件)</h3><p>次は値型ならデフォルト値、参照型なら null が返るようなメソッドを扱うケースです。<code>GetValueOrDefault</code> や <code>DefaultIfEmpty</code> のようなものでよくありますね。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=6.cs"></script><p>このようなケースをカバーするには <code>MaybeNull</code> 属性を使うと戻り値が null になる可能性があることを表せます。なお、戻り値に対しての属性なので <code>return:</code> です。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=7.cs"></script><p>逆に絶対 null を返さないということがわかっている場合には <code>NotNull</code> 属性で宣言できます。例えば次のようなケースです。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=8.cs"></script><h2 id="プロパティ"><a href="#プロパティ" class="headerlink" title="プロパティ"></a>プロパティ</h2><h3 id="AllowNull-属性と-DisallowNull-属性-事前条件-1"><a href="#AllowNull-属性と-DisallowNull-属性-事前条件-1" class="headerlink" title="AllowNull 属性と DisallowNull 属性 (事前条件)"></a><code>AllowNull</code> 属性と <code>DisallowNull</code> 属性 (事前条件)</h3><p>プロパティの setter で null を許可したかったり拒否したかったりというケースがあるかもしれません。例えば setter で null はセットできるけれども null は返ってこないような API です(というのはあまり想像しづらいですが…)。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=9.cs"></script><p>ちなみにコンパイラーが中途半端に賢いので <code>Value = null;</code> の後に <code>Value</code> を使用しようとすると怒られます(が、実際は null じゃないはずなのでなんか変な気がします)。</p><p>逆は例によって <code>DisallowNull</code> です。null が返るかもしれないけれど null をセットすることは許さないようなケースです。<a href="https://docs.microsoft.com/en-us/dotnet/csharp/nullable-attributes#specify-preconditions-allownull-and-disallownull" target="_blank" rel="noopener">Microsoft のドキュメントのサンプルではこんな感じです</a>。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=10.cs"></script><p>初期化時は null が返るけど、ユーザーがセットすることは許さないみたいな感じですかね。</p><h2 id="その他のケース"><a href="#その他のケース" class="headerlink" title="その他のケース"></a>その他のケース</h2><h3 id="NotNullWhen-属性-と-MaybeNullWhen-属性-事後条件"><a href="#NotNullWhen-属性-と-MaybeNullWhen-属性-事後条件" class="headerlink" title="NotNullWhen 属性 と MaybeNullWhen 属性 (事後条件)"></a><code>NotNullWhen</code> 属性 と <code>MaybeNullWhen</code> 属性 (事後条件)</h3><p><code>TryGetValue</code> や <code>TryParse</code> のような、成功時には <code>out</code> 引数で値を返すがそれ以外では <code>null</code> を返し、成否は戻り値にするというケースがあります。例えば次のようなコードがあるとします。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=11.cs"></script><p>この <code>MyDictionary</code> を使う場合は次のようなコードになるわけですが、その際 <code>null</code> の扱いも上手く処理されてほしいわけです。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=12.cs"></script><p>そこで戻り値によって nullable かどうかを伝える <code>NotNullWhen</code> という属性が用意されています。この属性を付けると「nullable な型が場合によってはその後 non-nullable 確定できるかも」という情報をコンパイラーに伝えられます。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=13.cs"></script><p>他の使い道としては <code>String.IsNullOrEmpty</code> みたいなもので使えます(<a href="https://github.com/dotnet/corefx/blob/90f7cc01418c059f496528beeca77c5f5549e6fc/src/Common/src/CoreLib/System/String.cs#L421" target="_blank" rel="noopener">使われています</a>)。 <code>IsNullOrEmpty</code> も後のフロー解析に影響を与えてほしい側面を持っているのでピッタリですね。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=14.cs"></script><p>逆の意味を持つのが <code>MaybeNullWhen</code> 属性で non-nullable につけた <code>MaybeNullWhen(false)</code> は <code>NotNullWhen(true)</code> と同じになります。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=15.cs"></script><p>これらの属性の使い分けですが</p><ul><li>引数に渡す/出てくる値 (<code>out</code>, <code>ref</code>) が前提として null 非許容型 (<code>T</code>) かもしれないなら <code>MaybeNullWhen</code> 属性</li><li>引数に渡す/出てくる値 (<code>out</code>, <code>ref</code>) が前提として null 許容型 (<code>T?</code>) かもしれないなら <code>NotNullWhen</code> 属性</li></ul><p>例えば <code>TryGetValue</code> のようなもので <code>MaybeNullWhen</code> 属性を使った場合、前提として non-nullable になると <code>out var</code> で宣言したローカル変数が non-nullable になり、<code>if</code> のようなフロー解析から外れたときに non-nullable (ただし null が入っている可能性がある) という状況になります。</p><p>同様に <code>IsNullOrEmpty</code> のようなもので <code>MaybeNullWhen</code> 属性を使った場合、引数は前提として non-nullable なので <code>String?</code> な値を引数に渡すたびに <code>!</code> を付けてあげないといけないことになります。</p><p>…と、ここまで読むと <code>MaybeNullWhen</code> 属性を使わなくてもほとんどのケースでは <code>NotNullWhen</code> 属性で事足りるのではと思うのですが、制約なしジェネリクスの型パラメータに <code>?</code> を付けて null 許容型とすることはできないのでその場合には <code>MaybeNullWhen</code> を使う必要があります。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=16.cs"></script><h3 id="NotNullIfNotNull-属性-事後条件"><a href="#NotNullIfNotNull-属性-事後条件" class="headerlink" title="NotNullIfNotNull 属性 (事後条件)"></a><code>NotNullIfNotNull</code> 属性 (事後条件)</h3><p>入力が null ではなかったら絶対に null ではないということがわかっていて、コンパイラーに伝えたいというケースに使えるのが <code>NotNullIfNotNull</code> 属性です。何を言っているのかみたいな名前…。</p><p>具体的なケースであれば例えばエスケープ処理のようなもので、文字列を受け取るけれども null が来たら null を返すが、それ以外は絶対値が返るようなメソッドです(その仕様がいいかどうかはさておき)。</p><script src="https://gist.github.com/mayuki/ae0f7bdc5b17248160d7a3bdbe7ddba8.js?file=17.cs"></script><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>属性を書き始めると書き味が悪くなるのと IntelliSense などでシグネチャヒントを見ただけではわからないという問題が起きやすくなるので、新規に書き起こすものは極力属性を書かなくて済むような API にするほうが望ましいように感じました。</p><p>例えば <code>NotNullIfNotNull</code> 属性のようなものなどはそもそも引数で null を受けない、<code>TryGetValue</code> などは戻り値と <code>out</code> を使わないで null 許容型をそのまま返すといった形にできます。</p><p>属性は既存のコードを壊さず null 許容型との相互運用のためにアノテーションをつけていくものというぐらいの認識がいいかもしれません。まあジェネリクスが厳しいのですが…。</p> ]]>
</content>
<summary type="html"> <p>.NET Core 3.0 のリリースとともに C# 8.0 がきて、null 許容型 (nullable reference types) を使えるようになったので使ってみたメモです。</p> <h2 id="未初期化だけど後で初期化するパターン"><a href="#未 </summary>
</entry>
<entry>
<title>Google (GSuite) を IdP として Azure Active Directory (Office 365) にサインインする</title>
<link href="http://www.misuzilla.org/Blog/2019/07/26/FederatingGSuiteWithAzureActiveDirectory"/>
<id>http://www.misuzilla.org/Blog/2019/07/26/FederatingGSuiteWithAzureActiveDirectory</id>
<published>2019-07-25T15:00:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p>Azure Active Directory(以下Azure AD)とGoogleのアカウント連携について調べるとAzure AD (Office 365)をIdPつまりユーザー情報のソースとしてGoogleにサインイン (SSO) する、ユーザープロビジョニングを行うといった設定についてのドキュメントが見つかります。</p><ul><li><a href="https://docs.microsoft.com/ja-jp/azure/active-directory/saas-apps/google-apps-tutorial" target="_blank" rel="noopener">チュートリアル:Azure Active Directory と G Suite の統合 | Microsoft Docs</a></li></ul><p>一方で逆のパターン、つまりGoogle (GSuite, Cloud Identity)をIdPとしてAzure ADやOffice 365にサインインしたり、ユーザー情報を同期するパターンについての説明は少ないので折角ですしメモもかねて残しておきます。</p><p>ちなみにAzure AD B2Bの機能としてGoogleをアプリとして登録することでAzure ADにゲストとして登録する機能というのもあるのですがそれとは別です。</p><ul><li><a href="https://docs.microsoft.com/ja-jp/azure/active-directory/b2b/google-federation" target="_blank" rel="noopener">Google を B2B の ID プロバイダーとして追加する - Azure Active Directory | Microsoft Docs</a></li></ul><p><a href="https://support.google.com/a/answer/6363817?hl=ja" target="_blank" rel="noopener">公式ドキュメント(Office 365 クラウド アプリケーション - G Suite 管理者 ヘルプ)</a>と<a href="https://ushiyasan.blogspot.com/2016/12/googleoffice365azure-adsso.html" target="_blank" rel="noopener">ushiyasanさんのブログエントリー(GoogleアカウントでOffice365(Azure AD)にSSOログイン)</a>を参考にして大体設定しています。というわけで基本は公式のドキュメントの手順に沿って設定していきます。</p><h2 id="Google-の-IdP-設定を取得する"><a href="#Google-の-IdP-設定を取得する" class="headerlink" title="Google の IdP 設定を取得する"></a>Google の IdP 設定を取得する</h2><p>公式ドキュメントの手順1で、ここはドキュメントそのままです。まずは Google の管理コンソールから設定のための情報を取得します。</p><p>特権管理者で管理コンソールにログインして <a href="https://admin.google.com/AdminHome?fral=1#SecuritySettings:flyout=sso" target="_blank" rel="noopener"><code>セキュリティ</code> → <code>シングルサインオン (SSO) の設定</code></a> を開いて、各種情報のメモと証明書のダウンロードを…と思ったのですが実はSAMLアプリケーション (Office 365)の追加の手順の途中でも表示されるのでそっちからでもよいです。</p><p>特権管理者で <code>アプリ</code>→<code>SAML アプリ</code> で <code>サービスやアプリをドメインに追加</code> をクリックして、SAMLアプリを追加する画面を表示します。<code>SAML アプリケーションで SSO を有効にする</code> という画面が出るので<code>Office 365</code> で検索します。<code>Microsoft Office 365</code> というアプリが見つかるのでそれをクリックして進めます。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-02.png" alt=""></p><p>ステップ2 <code>Google IdP 情報</code> という画面が表示されるとGoogleをIdPとして利用するために必要な情報が表示されますので下記の3つの情報を記録しておきます。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-03.png" alt=""></p><ul><li>SSO の URL</li><li>エンティティの ID</li><li>証明書 (ダウンロード)</li></ul><h2 id="Azure-AD-をフェデレーテッドモードに変更する"><a href="#Azure-AD-をフェデレーテッドモードに変更する" class="headerlink" title="Azure AD をフェデレーテッドモードに変更する"></a>Azure AD をフェデレーテッドモードに変更する</h2><p>公式ドキュメントの手順 2で、ここからはAzure AD側の設定を行います。先のブログエントリーでも書かれているのですが、手順 2についての手順はPowerShellで設定してぐらいのざっくりとしたことしか書かれていません。</p><p>やることは次の二点です。</p><ul><li>既存ユーザーの <code>ImmutableId</code> を設定する<ul><li>「ユーザーの普遍の ID を設定します」と書かれている…</li></ul></li><li>Azure ADの認証モードを <code>Managed</code> から <code>Federated</code> に変更して、SAML認証を行う設定をする</li></ul><h3 id="PowerShell-モジュールをインストールし、接続する"><a href="#PowerShell-モジュールをインストールし、接続する" class="headerlink" title="PowerShell モジュールをインストールし、接続する"></a>PowerShell モジュールをインストールし、接続する</h3><p>Azure ADの設定にはPowerShellで接続できる必要があるのでその準備です。</p><p>まずはモジュールをインポートします。</p><pre><code class="hljs powershell"><span class="hljs-built_in">Import-Module</span> -Name MSOnline</code></pre><p>次にサービスに接続します。管理権限のあるユーザーで接続してください。</p><pre><code class="hljs powershell">PS&gt; Connect-MsolServicePS&gt; Get-MsolDomainName Status Authentication---- ------ --------------example.com Verified Managedexamplecom.onmicrosoft.com Verified Managed</code></pre><h3 id="既存ユーザーの-ImmutableId-を設定する"><a href="#既存ユーザーの-ImmutableId-を設定する" class="headerlink" title="既存ユーザーの ImmutableId を設定する"></a>既存ユーザーの <code>ImmutableId</code> を設定する</h3><p>GoogleはAzure AD側のユーザーと突き合わせるためにAzure ADのユーザープロパティ <code>ImmutableId</code> を使用します。</p><p><code>ImmutableId</code> に使用する値は特に理由がなければメールアドレス (例: <a href="mailto:`user@example.com" target="_blank" rel="noopener">`user@example.com</a>`) にします。他でもできるはずですが、Google側が受け入れるのがメールアドレスか姓名ぐらいしかないようです。</p><p>Azure AD単体で使っている状態ではユーザーに対して設定されていないので既存のユーザーに関しては何らかの方法で設定してあげます。まあ何らかの方法というか <code>Set-MsolUser</code> コマンドレットですね。</p><pre><code class="hljs powershell">Set-MsolUser -UserPrincipalName alice@example.com -ImmutableId alice@example.com</code></pre><pre><code class="hljs powershell">Get-MsolUser | ?&#123; <span class="hljs-variable">$_</span>.UserPrincipalName.EndsWith(<span class="hljs-string">"example.com"</span>) &#125; | %&#123; Set-MsolUser -UserPrincipalName <span class="hljs-variable">$_</span>.UserPrincipalName -ImmutableId <span class="hljs-variable">$_</span>.UserPrincipalName &#125;</code></pre><p>後々ユーザープロビジョニングでGoogle側からやってきたユーザーに関しては自動で設定されます。</p><h3 id="Azure-ADの認証モードを-Federated-に変更し、SAML認証の設定をする"><a href="#Azure-ADの認証モードを-Federated-に変更し、SAML認証の設定をする" class="headerlink" title="Azure ADの認証モードを Federated に変更し、SAML認証の設定をする"></a>Azure ADの認証モードを <code>Federated</code> に変更し、SAML認証の設定をする</h3><p>Azure ADの認証モードを <code>Federated</code> に変更し、SAML関連の設定をおこなうことで認証をAzure AD以外の場所で行うようにします。</p><p>設定には <code>Set-MsolDomainAuthentication</code> コマンドレットを使用して、先ほどのIdP情報を指定します。</p><pre><code class="hljs powershell"><span class="hljs-comment"># "SSO の URL" と書かれていた項目</span><span class="hljs-variable">$ssoUrl</span> = <span class="hljs-string">"https://accounts.google.com/o/saml2/idp?idpid=&lt;IdPId&gt;"</span><span class="hljs-comment"># "エンティティ ID" と書かれていた項目</span><span class="hljs-variable">$entity</span> = <span class="hljs-string">"https://accounts.google.com/o/saml2?idpid=&lt;IdPId&gt;"</span><span class="hljs-comment"># 対象のドメイン名</span><span class="hljs-variable">$domain</span> = <span class="hljs-string">"example.com"</span><span class="hljs-comment"># 証明書</span><span class="hljs-variable">$cert</span> = <span class="hljs-string">"ダウンロードした証明書の -----BEGIN CERTIFICATE----- から -----END CERTIFICATE----- までの「間」を改行をなしで一行で"</span>Set-MsolDomainAuthentication -Authentication Federated -DomainName <span class="hljs-variable">$domain</span> -ActiveLogOnUri <span class="hljs-variable">$ssoUrl</span> -PassiveLogOnUri <span class="hljs-variable">$ssoUrl</span> -IssuerUri <span class="hljs-variable">$entity</span> -LogOffUri <span class="hljs-variable">$ssoUrl</span> -SigningCertificate <span class="hljs-variable">$cert</span> -PreferredAuthenticationProtocol SAMLP</code></pre><pre><code class="hljs powershell">PS&gt; Get-MsolDomainName Status Authentication---- ------ --------------example.com Federated Managedexamplecom.onmicrosoft.com Verified Managed</code></pre><p>下記のようなエラーが発生した場合には指定したドメインがAzure ADのプライマリドメインとなっていると思うので <code>onmicrosoft.com</code> や他のドメインにプライマリを一度切り替える必要があります。</p><pre><code class="hljs undefined">Set-MsolDomainAuthentication : You cannot remove this domain as the default domain without replacing it with anotherdefault domain. Use the the Set-MsolDomain cmdlet to set another domain as the default domain before you delete thisdomain.</code></pre><p><img src="/shared/Blog/img/2019/07/25/Screen-01.png" alt=""></p><h2 id="Google-GSuite-の-SAML-アプリケーション設定"><a href="#Google-GSuite-の-SAML-アプリケーション設定" class="headerlink" title="Google (GSuite) の SAML アプリケーション設定"></a>Google (GSuite) の SAML アプリケーション設定</h2><p>再びGoogleの管理コンソールに戻ってステップを進めます。</p><p><code>Microsoft Office 365 の基本情報</code> はそのままで「次へ」で進めます。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-04.png" alt=""></p><p><code>サービス プロバイダの詳細</code> は「署名付き応答」にチェックを入れて「次へ」で進めます。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-06.png" alt=""></p><p><code>属性のマッピング</code> は「IDPEmail」を「基本情報」「メインのメールアドレス」を選択して「次へ」で進めます。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-08.png" alt=""></p><p>これでアプリの基本設定は完了ですがまだ有効になっていないので動きません。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-09.png" alt=""></p><h2 id="Microsoft-Office-365-アプリを有効化する"><a href="#Microsoft-Office-365-アプリを有効化する" class="headerlink" title="Microsoft Office 365 アプリを有効化する"></a><code>Microsoft Office 365</code> アプリを有効化する</h2><p>SAMLアプリを追加しただけでは有効になっていないのでそれを有効化する必要があります。</p><p><code>アプリ</code>→<code>SAML アプリ</code> でアプリの一覧から <code>Microsoft Office 365</code> を選択して、右上の「サービスを編集」をクリックします。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-10.png" alt=""></p><p>「サービスのステータス」で「オン (すべてのユーザー)」を選択します。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-11.png" alt=""></p><h2 id="サインインをしてみる"><a href="#サインインをしてみる" class="headerlink" title="サインインをしてみる"></a>サインインをしてみる</h2><p>ここまでの設定でAzure ADにGoogle経由でサインインできるようになっているはずでしょう。多分。</p><p>Cookieが残っていると挙動が怪しいのでプライベート ブラウジングやIn Private ウィンドウなどでテストするのをオススメします。</p><h3 id="トラブルシューティング-Google側で-400-エラーが発生する"><a href="#トラブルシューティング-Google側で-400-エラーが発生する" class="headerlink" title="トラブルシューティング: Google側で 400 エラーが発生する"></a>トラブルシューティング: Google側で 400 エラーが発生する</h3><blockquote><ol start="400"><li>That’s an error.<br>Error parsing the request, No SAML message present in request That’s all we know.</li></ol></blockquote><p><img src="/shared/Blog/img/2019/07/25/Screen-12.png" alt=""></p><p>Azure ADから一度Googleにリダイレクト後、上記のようなエラーメッセージが出て進まない場合には <code>Set-MsolDomainAuthentication</code> コマンドレットで設定時に <code>-PreferredAuthenticationProtocol SAMLP</code> が指定されてなく、SAMLではなくWSで認証をかけている可能性があります。</p><h3 id="トラブルシューティング-無限サインインループ"><a href="#トラブルシューティング-無限サインインループ" class="headerlink" title="トラブルシューティング: 無限サインインループ"></a>トラブルシューティング: 無限サインインループ</h3><p>無限サインインループになった場合はAzure AD側に <code>ImmutableId</code> が設定されていない可能性や設定が間違っている可能性があるのでそちらを確認してください。</p><h2 id="ユーザー-プロビジョニングを設定する"><a href="#ユーザー-プロビジョニングを設定する" class="headerlink" title="ユーザー プロビジョニングを設定する"></a>ユーザー プロビジョニングを設定する</h2><p>サインインできるようになった後はGoogle側からAzure AD側へユーザー情報を同期するためにユーザープロビジョニングの設定を行います。ユーザープロビジョニングによってGoogle側にユーザーを追加するとAzure ADにユーザーを自動で作成するといったことが可能になります。</p><p>ユーザープロビジョニングの設定は <code>アプリ</code> → <code>SAML アプリ</code> でアプリの一覧から <code>Microsoft Office 365</code> を選択し、<code>ユーザー プロビジョニング</code> を開きます。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-13.png" alt=""></p><p>「ユーザー プロビジョニングを設定」をクリックするとダイアログが開くので「承認」をクリックします。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-14.png" alt=""></p><p>Azure AD側でアクセス許可の確認が表示されるので「組織の代理として同意する」にチェックして「承諾」でGoogleの管理コンソールに戻ります。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-15.png" alt=""></p><p>最後に属性のマッピング設定画面が表示されるので <code>onPremisesImmutableId</code> を <code>基本情報 &gt; ユーザー名</code> にマッピング設定して「次へ」で完了です。</p><p><img src="/shared/Blog/img/2019/07/25/Screen-16.png" alt=""></p><p>後はしばらく待つとGoogle側のユーザー情報がAzure AD側へと反映され、ユーザーが作成されたりします。</p><h2 id="おわりに"><a href="#おわりに" class="headerlink" title="おわりに"></a>おわりに</h2><p>というわけでGoogle (GSuite)をユーザー情報のソースとしてAzure ADを利用できるようになることでOffice 365やAzureに関連したサービス(AzureやAzure DevOps等)などのサインインを一元化できるのでよいのではないでしょうか。</p><p>すでにGSuiteを使用している環境ではAzure ADをIdPにするのが怖かったり抵抗がある、めんどくさいということもあるのでこの構成もオススメです。</p> ]]>
</content>
<summary type="html"> <p>Azure Active Directory(以下Azure AD)とGoogleのアカウント連携について調べるとAzure AD (Office 365)をIdPつまりユーザー情報のソースとしてGoogleにサインイン (SSO) する、ユーザープロビジョニングを行うとい </summary>
</entry>
<entry>
<title>App ServiceやVPSからAzure Kubernetes ServiceとNetlifyへ移行した</title>
<link href="http://www.misuzilla.org/Blog/2018/11/15/MoveToAKS"/>
<id>http://www.misuzilla.org/Blog/2018/11/15/MoveToAKS</id>
<published>2018-11-15T14:35:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p>今までこのブログを含むサイトといくつかのサイトがApp Serviceの上で動いていて、TiarraやTIG等IRC系のものはVPSやVMで動いていたのですが、まるごとAKSとNetlifyに移行しました。</p><ul><li>AKS<ul><li>Tiarra x 3 (IRCnet, Freenode, TIG)<ul><li>fluentd</li></ul></li><li>stunnel x 3</li><li>TweetIrcGateway</li><li>platformstatus.io<ul><li>cert-manager + ingress-nginx (HTTPS)</li></ul></li></ul></li><li>Netlify<ul><li>このブログ (というかサイト)</li></ul></li></ul><p>今まで .NET なアプリがあったりしたのでWindowsなApp Serviceでしたが、.NET Core化して全部Dockerに乗せた結果Windowsで動いているものがなくなってしまった…かなしい。</p><p>ちなみに最初はGKEを考えたのですが、Load Balancerが月2,000円ぐらいかかるのでAKSにしました。まあk8sに乗せてればどこかに行くのも簡単でしょうし(慢心)。</p><p>しかしYAMLこねこね結構めんどくさかったので、改めてApp Serviceのお手軽さはいいですねと感じたのでした。</p> ]]>
</content>
<summary type="html"> <p>今までこのブログを含むサイトといくつかのサイトがApp Serviceの上で動いていて、TiarraやTIG等IRC系のものはVPSやVMで動いていたのですが、まるごとAKSとNetlifyに移行しました。</p> <ul> <li>AKS<ul> <li>Tiarra x </summary>
</entry>
<entry>
<title>MoreLocale 2で日本語設定にしても再起動すると戻る問題</title>
<link href="http://www.misuzilla.org/Blog/2018/09/17/RevertLanguageWhenRebooted"/>
<id>http://www.misuzilla.org/Blog/2018/09/17/RevertLanguageWhenRebooted</id>
<published>2018-09-17T13:35:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p>Androidの端末を使う際に何らかの事情(とは)でMoreLocale 2というアプリを使用して言語設定を日本語に設定することがあります。</p><p>ところが設定しても再起動やユーザースイッチすると言語が英語に戻されてしまうという問題が発生しました。この現象自体は割とよくあるようです。</p><p>とはいえ不便なので試行錯誤したところ手元の環境ではMoreLocale 2で日本語に設定した後、第二言語設定にEnglishを追加したところ戻らなくなりました。もしこの現象で困っている人は試してみるともしかしたらうまくいくかもしれません。というメモです。</p> ]]>
</content>
<summary type="html"> <p>Androidの端末を使う際に何らかの事情(とは)でMoreLocale 2というアプリを使用して言語設定を日本語に設定することがあります。</p> <p>ところが設定しても再起動やユーザースイッチすると言語が英語に戻されてしまうという問題が発生しました。この現象自体は割と </summary>
<category term="Android" scheme="http://www.misuzilla.org/Tags/Android/"/>
</entry>
<entry>
<title>KumoDictionary: クラウドのNoSQLをIDictionary<TKey, TValue>として使う</title>
<link href="http://www.misuzilla.org/Blog/2018/07/15/IntroducingKumoDictionary"/>
<id>http://www.misuzilla.org/Blog/2018/07/15/IntroducingKumoDictionary</id>
<published>2018-07-14T15:00:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p><a href="https://github.com/mayuki/KumoDictionary" target="_blank" rel="noopener">KumoDictionary</a> というライブラリーを公開しました。</p><ul><li><a href="https://github.com/mayuki/KumoDictionary" target="_blank" rel="noopener">mayuki/KumoDictionary - IDictionary&lt;TKey, TValue&gt; in the Cloud - Simple NoSQL/KVS wrapper for .NET</a></li></ul><p>このライブラリは何かというと <code>IDictionary&lt;TKey, TValue&gt;</code> を操作すると、裏側ではネットワークの先にあるNoSQL的なもの、例えばAzure Storage TableやAmazon DynamoDBなどへ透過的に記録、読み取りを行うものです。</p><p><img src="https://raw.githubusercontent.com/mayuki/KumoDictionary/master/docs/images/SampleCodeImage.png" alt=""></p><h2 id="作った動機"><a href="#作った動機" class="headerlink" title="作った動機"></a>作った動機</h2><p>Azure FunctionsやAWS LambdaのようなServerlessアプリ、もしかしたら単なるCLIで小さいツールといったものを作るということはよくありますが、その際に<strong>ちょっとした</strong>データを保存したいということも同時によくあります。</p><p>例えばFunctionsでWebhookを作ったとして、特定のパラメータに対して最後にアクセスされた時刻を記録する…などなど。</p><p>じゃあデータ保存しましょうとなると、ファイルは永続化できる書き込み先はないし、Redisやデータベース的なデータアクセスはサーバーを用意するのもオーバーキル。そうなるとお手軽なのはNoSQL系かとなるのですが、いざSDKを入れて使ってみるとテーブルの設計と呼び出しがめんどくさいものです。</p><p>欲しいのは雑に <code>Dictionary&lt;TKey, TValue&gt;</code> が永続化されてくれるような程度でいいのですがーという気持ちからスタートしています。</p><p><code>IDictionary</code> ということはブロッキングだし筋があまりよくないのではという疑問はごもっともですが、クラウド内であればレイテンシーも小さいことやそもそも低負荷のアプリであることをこのライブラリは期待しています。</p><p>パフォーマンスを真面目に気にする段階になったときは、Asyncメソッドをご利用いただくか、そもそも普通にSDKでアクセスしてください。</p><h2 id="使い方"><a href="#使い方" class="headerlink" title="使い方"></a>使い方</h2><p>使い方はとりあえず Azure Storage Table であれば簡単です。</p><p>まずポータルやCLIでストレージアカウントを作成しておきます。</p><p>次に <code>KumoDictionary.AzureStorageTable</code> NuGetパッケージをインストールします。</p><p>そしてプログラムの頭で一度だけデフォルトのバックエンドの設定を行います。</p><pre><code class="hljs csharp"><span class="hljs-keyword">using</span> KumoDictionary;<span class="hljs-keyword">using</span> KumoDictionary.Provider;<span class="hljs-comment">// Set backend provider for Microsoft Azure Storage Table</span><span class="hljs-keyword">var</span> tableName = <span class="hljs-string">"MyTestTable"</span>;<span class="hljs-keyword">var</span> connectionString = <span class="hljs-string">"DefaultEndpointsProtocol=https;AccountName=..."</span>;KumoDictionaryAzureStorageTableProvider.UseAsDefault(connectionString, tableName);</code></pre><p>あとは <code>KumoDictionary&lt;TValue&gt;</code> クラスを使うだけです。ちなみにこれは <code>KumoDictionary&lt;string, TValue&gt;</code> の別名です。</p><pre><code class="hljs csharp"><span class="hljs-comment">// KumoDictionary を作るときにディクショナリーの名前を付けてあげる</span><span class="hljs-keyword">var</span> dict = <span class="hljs-keyword">new</span> KumoDictionary&lt;MyClass&gt;(<span class="hljs-string">"dictionaryName1"</span>);<span class="hljs-comment">// インデクサで値をセット</span>dict[<span class="hljs-string">"key1"</span>] = <span class="hljs-keyword">new</span> MyClass &#123; ValueA = <span class="hljs-number">1234</span> &#125;;dict[<span class="hljs-string">"key2"</span>] = <span class="hljs-keyword">new</span> MyClass &#123; ValueA = <span class="hljs-number">5678</span> &#125;;<span class="hljs-comment">// インデクサで値を取得</span>Console.WriteLine(dict[<span class="hljs-string">"key1"</span>].ValueA); <span class="hljs-comment">// =&gt; 1234</span></code></pre><p>つまり最初のProviderの設定以外は普通のDictionaryっぽく扱えるのです(もちろん一部機能は実装されていなかったりはしますが)。</p><h3 id="DynamoDB-の場合"><a href="#DynamoDB-の場合" class="headerlink" title="DynamoDB の場合"></a>DynamoDB の場合</h3><p>DynamoDBの場合にはテーブルの作成があらかじめ必要ですが、逆を言えばそれぐらいです。</p><h2 id="裏側"><a href="#裏側" class="headerlink" title="裏側"></a>裏側</h2><p>裏側は単純に<a href="https://github.com/neuecc/MessagePack-CSharp" target="_blank" rel="noopener">MessagePack</a>でシリアライズして適当にストアに保存しているだけです。</p><p>ただ、値がもしデータストア側がネイティブで対応している型の場合にはそのままそれを使います。例えばAzure Storage TableやDynamoDBはStringを直接扱えるのでそのまま突っ込むことで管理画面や別なツールからもフレンドリーになるのです。</p><p>またキーの方も独自のクラスを使えますが、クラスの構造(プロパティやフィールドの名前、型、順番、数)が変更されると壊れるのであまりお勧めはできません。</p><p>雰囲気的にはRedis向けの<a href="https://github.com/neuecc/CloudStructures" target="_blank" rel="noopener">CloudStructures</a>に近いですし、Redisに値を詰めたい or 詰めるのでよいのであればCloudStructuresを使うのをお勧めします。</p><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>これでちょっとしたものを作るときにデータや設定の保存の手助けとなれば幸いです。</p> ]]>
</content>
<summary type="html"> <p><a href="https://github.com/mayuki/KumoDictionary" target="_blank" rel="noopener">KumoDictionary</a> というライブラリーを公開しました。</p> <ul> <li><a hr </summary>
<category term=".NET" scheme="http://www.misuzilla.org/Tags/NET/"/>
<category term="Azure" scheme="http://www.misuzilla.org/Tags/Azure/"/>
<category term="AWS" scheme="http://www.misuzilla.org/Tags/AWS/"/>
</entry>
<entry>
<title>MySQLへのクエリをApplication Insights(.NET)のDependencyに出したい</title>
<link href="http://www.misuzilla.org/Blog/2018/06/06/MySqlCommandsDependency"/>
<id>http://www.misuzilla.org/Blog/2018/06/06/MySqlCommandsDependency</id>
<published>2018-06-06T06:20:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p><img src="/Shared/Blog/img/2018/06/06/Untitled-2.png" alt=""></p><p>Application InsightsにはDependencyテレメトリーというリクエスト中に発行された外部リソースへのアクセスなどを記録する仕組みがあり、.NET向けの一式を入れておけばHTTPリクエストやSQL Serverへの問い合わせが自動で記録されます。</p><p>一方、標準で対応してないものをDependencyに出すには自前で何らかの方法で記録してあげる必要があります。MySQL Connector/Netもその例にもれず自動では記録されません。SQL Serverへの問い合わせが記録されるのはSqlClient (SQL Serverクライアント)のイベントを記録しているからであって、それ以外のデータベースドライバーでは記録されないのです。</p><p>というわけで、当然MySQLへの問い合わせでもDependencyに表示されてほしくなります。</p><h2 id="Dependencyとしての記録"><a href="#Dependencyとしての記録" class="headerlink" title="Dependencyとしての記録"></a>Dependencyとしての記録</h2><p>まずはそもそもApplication Insights上でDependencyとして記録するにはどうすればいいのかというところからです。</p><p>ドキュメントを見るとDependencyはテレメトリーの一種で、<code>DependencyTelemetry</code> というレコードを記録すればよいということになっています。</p><ul><li>See: <a href="https://docs.microsoft.com/ja-jp/azure/application-insights/application-insights-custom-operations-tracking" target="_blank" rel="noopener">Application Insights .NET SDK でカスタム操作を追跡する</a></li></ul><p>記録するには <code>DependencyTelemetry</code> を生成する方法と、<a href="https://docs.microsoft.com/ja-jp/azure/application-insights/app-insights-api-custom-events-metrics#trackdependency" target="_blank" rel="noopener">TrackDependencyメソッドで記録する方法</a>があり、今回は <code>DependencyTelemetry</code> を使った記録方法で実装してみます。後者は細かいことはできないもののメソッド呼び出し一発とお手軽です。</p><p>実際の手順としては次のようになります。</p><ul><li><code>TelemetryClient</code> クラスのインスタンスを作る</li><li><code>TelemetryClient.StartOperation&lt;T&gt;</code> メソッドを <code>DependencyTelemetry</code> 型を指定して呼び出す</li><li>帰ってきた <code>IOperationHolder</code> のTelemetryのプロパティを設定する</li><li>計測する処理を実行</li><li><code>TelemetryClient.StopOperation</code> メソッドを呼ぶ または <code>IOperationHolder.Dispose</code> メソッドを呼ぶ</li></ul><p>これをコードにするとこうなります。</p><pre><code class="hljs csharp"><span class="hljs-comment">// TelemetryClientはスレッドセーフなので使いまわせる</span><span class="hljs-keyword">var</span> telemetryClient = <span class="hljs-keyword">new</span> TelemetryClient();<span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> operation = telemetryClient.StartOperation&lt;DependencyTelemetry&gt;(<span class="hljs-string">"DependencyName"</span>))&#123; <span class="hljs-keyword">var</span> telemetry = operation.Telemetry; telemetry.Type = <span class="hljs-string">"ResourceType"</span>; <span class="hljs-comment">// Dependencyの種類(Http, SQLなど)</span> telemetry.Target = <span class="hljs-string">"リクエスト送信先"</span>; <span class="hljs-comment">// エンドポイントのホスト名とか</span> telemetry.Data = <span class="hljs-string">"何か生データー"</span>; <span class="hljs-comment">// SQLとか</span> <span class="hljs-comment">// 何か時間のかかる処理...</span> <span class="hljs-keyword">await</span> Task.Delay(<span class="hljs-number">1000</span> * <span class="hljs-number">3</span>);&#125;</code></pre><p>難しいことはないですね。これでApplication Insightsに記録する方法はなんとなく理解できました。</p><h2 id="MySQLの呼び出しを記録する"><a href="#MySQLの呼び出しを記録する" class="headerlink" title="MySQLの呼び出しを記録する"></a>MySQLの呼び出しを記録する</h2><p>Dependencyとして記録する方法がわかったので次はMySQLへの問い合わせを記録する方法です。</p><p>今回はMySQLドライバーはMySQL公式のConnector/Netを利用していて、単純にSQLのクエリを記録したいと考えていますが、それにはそのクエリのタイミングをつかむ必要があります。そこで少し調べてみると、Connector/Netには<a href="https://dev.mysql.com/doc/connector-net/en/connector-net-interceptors.html" target="_blank" rel="noopener">Interceptor</a>というExecute系メソッドに割り込んでSQLのロギングなどが行える仕組みがあったのでそれを利用します。</p><p>InterceptorにはあらかじめExecute系メソッドに割り込むためのベースクラスである <code>CommandInterceptorBase</code> クラスがあるので、このクラスを継承して各種メソッドをオーバーライドします。そしてここではオーバーライドしたメソッドでDependencyの記録を行えば良さそうというわけです。</p><p>実際に実装した例はこんな感じになります。DependencyのTargetやNameといった値の設定はSQL Serverでの記録と同じような形にしました。</p><pre><code class="hljs csharp"><span class="hljs-keyword">using</span> System;<span class="hljs-keyword">using</span> System.Collections.Generic;<span class="hljs-keyword">using</span> System.Data;<span class="hljs-keyword">using</span> System.Text;<span class="hljs-keyword">using</span> Microsoft.ApplicationInsights;<span class="hljs-keyword">using</span> Microsoft.ApplicationInsights.DataContracts;<span class="hljs-keyword">using</span> MySql.Data.MySqlClient;<span class="hljs-keyword">namespace</span> <span class="hljs-title">WebApplication2.Diagnostics</span>&#123; <span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ApplicationInsightsBaseCommandInterceptor</span> : <span class="hljs-title">BaseCommandInterceptor</span> &#123; <span class="hljs-keyword">private</span> TelemetryClient _telemetryClient = <span class="hljs-keyword">new</span> TelemetryClient(); <span class="hljs-keyword">private</span> <span class="hljs-keyword">string</span> _name; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">ExecuteNonQuery</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> sql, <span class="hljs-keyword">ref</span> <span class="hljs-keyword">int</span> returnValue</span>)</span> &#123; <span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> operation = _telemetryClient.StartOperation&lt;DependencyTelemetry&gt;(_name)) &#123; <span class="hljs-keyword">var</span> telemetry = operation.Telemetry; telemetry.Type = <span class="hljs-string">"SQL"</span>; telemetry.Data = sql; telemetry.Target = _name; <span class="hljs-keyword">return</span> <span class="hljs-keyword">base</span>.ExecuteNonQuery(sql, <span class="hljs-keyword">ref</span> returnValue); &#125; &#125; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">ExecuteReader</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> sql, CommandBehavior behavior, <span class="hljs-keyword">ref</span> MySqlDataReader returnValue</span>)</span> &#123; <span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> operation = _telemetryClient.StartOperation&lt;DependencyTelemetry&gt;(_name)) &#123; <span class="hljs-keyword">var</span> telemetry = operation.Telemetry; telemetry.Type = <span class="hljs-string">"SQL"</span>; telemetry.Data = sql; telemetry.Target = _name; <span class="hljs-keyword">return</span> <span class="hljs-keyword">base</span>.ExecuteReader(sql, behavior, <span class="hljs-keyword">ref</span> returnValue); &#125; &#125; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">bool</span> <span class="hljs-title">ExecuteScalar</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> sql, <span class="hljs-keyword">ref</span> <span class="hljs-keyword">object</span> returnValue</span>)</span> &#123; <span class="hljs-keyword">using</span> (<span class="hljs-keyword">var</span> operation = _telemetryClient.StartOperation&lt;DependencyTelemetry&gt;(_name)) &#123; <span class="hljs-keyword">var</span> telemetry = operation.Telemetry; telemetry.Type = <span class="hljs-string">"SQL"</span>; telemetry.Data = sql; telemetry.Target = _name; <span class="hljs-keyword">return</span> <span class="hljs-keyword">base</span>.ExecuteScalar(sql, <span class="hljs-keyword">ref</span> returnValue); &#125; &#125; <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">override</span> <span class="hljs-keyword">void</span> <span class="hljs-title">Init</span>(<span class="hljs-params">MySqlConnection connection</span>)</span> &#123; _name = String.Format(<span class="hljs-string">"&#123;0&#125; | &#123;1&#125;"</span>, connection.DataSource, connection.Database); <span class="hljs-keyword">base</span>.Init(connection); &#125; &#125;&#125;</code></pre><p>Interceptorを実装したら最後にアプリケーションの設定でデータベース接続文字列に <code>commandinterceptors</code> パラメータを追加して読み込ませます。パラメータの値は<code>&lt;CommandInterceptorClass&gt;,&lt;Assembly&gt;</code>というフォーマットで、次のようなものになります。</p><pre><code class="hljs plaintext">commandinterceptors=WebApplication2.Diagnostics.ApplicationInsightsBaseCommandInterceptor,WebApplication2</code></pre><p>そしてこれを有効にした状態でアプリケーションを実行するとApplication Insightsに記録されます。もちろんAzureに接続していない場合でもVisual Studioで確認できます。</p><p><img src="/Shared/Blog/img/2018/06/06/Untitled-1.png" alt=""><br><img src="/Shared/Blog/img/2018/06/06/Untitled-2.png" alt=""></p><p>この例では問い合わせが1ms以下なので全く面白くなくて残念ですが、ともあれこれで取れるようになったので無いよりは全然よさそうです。</p> ]]>
</content>
<summary type="html"> <p><img src="/Shared/Blog/img/2018/06/06/Untitled-2.png" alt=""></p> <p>Application InsightsにはDependencyテレメトリーというリクエスト中に発行された外部リソースへのアクセスなどを </summary>
<category term=".NET" scheme="http://www.misuzilla.org/Tags/NET/"/>
<category term="ApplicationInsights" scheme="http://www.misuzilla.org/Tags/ApplicationInsights/"/>
</entry>
<entry>
<title>AuthenticationHandlerで処理する方法をアクションごとに変更する</title>
<link href="http://www.misuzilla.org/Blog/2018/05/26/SwitchStrategyInAuthenticationHandler"/>
<id>http://www.misuzilla.org/Blog/2018/05/26/SwitchStrategyInAuthenticationHandler</id>
<published>2018-05-26T06:50:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p>ASP.NET Core MVCでは認証は基本的にフィルターではなく、認証ハンドラーを認証スキーム名で登録しておき、使用したい認証スキームをAuthorize属性などで指定するという形になっています(デフォルトという指定もできます)。</p><p>今回やりたいこととしては「認証機構は基本的に一つで、あるアクションの時だけ認証中に追加で特殊な処理を行いたい」というパターンです。例えばコントローラーに共通のAuthorizeを付けているが、一部特殊なアクションでは追加の処理をしたいというケースです。</p><h2 id="難しいところ"><a href="#難しいところ" class="headerlink" title="難しいところ"></a>難しいところ</h2><p>そもそも認証ハンドラーはASP.NET Coreの一部であってMVCとは切り離されたものなので、操作可能なものは主にHttpContextとなります。つまりASP.NET Core MVCが認識しているアクションであるとかコントローラーであるとかを取り出すことは難しいため、アクションに付けた属性を引いてくるといったことはできないという悩みがあります。</p><h2 id="うまくいかないパターン"><a href="#うまくいかないパターン" class="headerlink" title="うまくいかないパターン"></a>うまくいかないパターン</h2><p>そこで最初に考えたのは、二つの認証スキームとして登録してそれぞれで利用する認証スキームを変更するという方法です。</p><p>まず次のようなオプションを持つAuthenticationHandlerを用意します。</p><pre><code class="hljs csharp">public class CustomAuthenticationSchemeOptions : AuthenticationSchemeOptions&#123; public bool EnableNanika &#123; get; set; &#125;&#125;public class CustomAuthenticationHandler : AuthenticationHandler&lt;CustomAuthenticationSchemeOptions&gt;&#123; public CustomAuthenticationHandler(IOptionsMonitor&lt;CustomAuthenticationSchemeOptions&gt; options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) &#123; &#125; protected override Task&lt;AuthenticateResult&gt; HandleAuthenticateAsync() &#123; if (Options.EnableNanika) &#123; // 一部で必要な特殊な処理... &#125; return Task.FromResult(AuthenticateResult.Fail("Unauthorized")); &#125; protected override Task HandleChallengeAsync(AuthenticationProperties properties) &#123; Response.Headers["WWW-Authenticate"] = "Nanika"; return base.HandleChallengeAsync(properties); &#125;&#125;</code></pre><p>このハンドラーをStartupで二つの認証スキームとして登録します。</p><pre><code class="hljs csharp">services.AddAuthentication() .AddScheme&lt;CustomAuthenticationSchemeOptions, CustomAuthenticationHandler&gt;(<span class="hljs-string">"Custom1"</span>, options =&gt; &#123; options.EnableNanika = <span class="hljs-literal">false</span>; &#125;) .AddScheme&lt;CustomAuthenticationSchemeOptions, CustomAuthenticationHandler&gt;(<span class="hljs-string">"Custom2"</span>, options =&gt; &#123; options.EnableNanika = <span class="hljs-literal">true</span>; &#125;);</code></pre><p>コントローラーにAuthorize属性を付け、その際にコントローラーとアクションで認証スキームを別々に設定します。</p><pre><code class="hljs csharp">[<span class="hljs-meta">Route(<span class="hljs-meta-string">"api/[controller]"</span>)</span>][<span class="hljs-meta">Authorize(AuthenticationSchemes = <span class="hljs-meta-string">"Custom1"</span>)</span>]<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ValuesController</span> : <span class="hljs-title">Controller</span>&#123; <span class="hljs-comment">// GET api/values</span> [<span class="hljs-meta">HttpGet</span>] [<span class="hljs-meta">Authorize(AuthenticationSchemes = <span class="hljs-meta-string">"Custom2"</span>)</span>] <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerable&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">Get</span>(<span class="hljs-params"></span>)</span> &#123; <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">string</span>[] &#123; <span class="hljs-string">"value1"</span>, <span class="hljs-string">"value2"</span> &#125;; &#125;&#125;</code></pre><p>これでどうかと思ったのですが、この指定で実際に動かすとコントローラーの指定の方が勝つことになります。当然ですがコントローラーにAuthorizeを付けずにアクションごと個別につけると期待通りに動作します。</p><h2 id="うまくいかないパターン-2"><a href="#うまくいかないパターン-2" class="headerlink" title="うまくいかないパターン(2)"></a>うまくいかないパターン(2)</h2><p>認証はフィルターとしても実装できるので(Authorizeも実体はMVCのフィルターで、中で認証ハンドラーを呼び出している)、それをかけてみるのはどうかという風になります。</p><pre><code class="hljs csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">CustomAuthorize</span> : <span class="hljs-title">Attribute</span>, <span class="hljs-title">IAsyncAuthorizationFilter</span>&#123; <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> EnableNanika &#123; <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; &#125; <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">OnAuthorizationAsync</span>(<span class="hljs-params">AuthorizationFilterContext context</span>)</span> &#123; <span class="hljs-comment">// ここで認証処理をあれこれやる</span> <span class="hljs-keyword">return</span> Task.CompletedTask; &#125;&#125;</code></pre><p>この場合にはコントローラーとアクション両方に指定すると二つのフィルターが呼ばれることになる(=二回処理が走る)ので都合が悪い感じになります。それに加えてAuthorize属性と同じようなことを書いてあげる必要があり面倒です。</p><h2 id="解決策-フィルターを組み合わせるパターン"><a href="#解決策-フィルターを組み合わせるパターン" class="headerlink" title="解決策: フィルターを組み合わせるパターン"></a>解決策: フィルターを組み合わせるパターン</h2><p>認証はフィルターとして実装できるという点を利用して、認証自体は行わずリクエストのデータにフラグを立てておき、認証ハンドラーで取り出して何とかする方法があります。その方法であれば 自前のフィルター(ASP.NET Core MVC) → 自前の認証ハンドラー (ASP.NET Core) という形にできます。</p><p>まずアクションにつけるフィルターを作ります。</p><pre><code class="hljs csharp"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UseCustomAuthenticationWithNanikaAttribute</span> : <span class="hljs-title">Attribute</span>, <span class="hljs-title">IAsyncAuthorizationFilter</span>, <span class="hljs-title">IOrderedFilter</span>&#123; <span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> Order =&gt; <span class="hljs-keyword">int</span>.MinValue; <span class="hljs-function"><span class="hljs-keyword">public</span> Task <span class="hljs-title">OnAuthorizationAsync</span>(<span class="hljs-params">AuthorizationFilterContext context</span>)</span> &#123; context.HttpContext.Features.Set&lt;IUseCustomAuthenticationWithNanikaFeature&gt;(<span class="hljs-keyword">new</span> UseCustomAuthenticationWithNanikaFeature &#123; Enable = <span class="hljs-literal">true</span> &#125;); <span class="hljs-keyword">return</span> Task.CompletedTask; &#125;&#125;<span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title">IUseCustomAuthenticationWithNanikaFeature</span>&#123; <span class="hljs-keyword">bool</span> Enable &#123; <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; &#125;&#125;<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">UseCustomAuthenticationWithNanikaFeature</span> : <span class="hljs-title">IUseCustomAuthenticationWithNanikaFeature</span>&#123; <span class="hljs-keyword">public</span> <span class="hljs-keyword">bool</span> Enable &#123; <span class="hljs-keyword">get</span>; <span class="hljs-keyword">set</span>; &#125;&#125;</code></pre><p>このフィルターはOnAuthorizationAsyncでHttpContextのFeatures(リクエスト単位の機能を入れておけるもの)にフラグ情報を持つFeatureを保存します。IOrderedFilterを実装しているのはAuthorize属性より先に来てほしいためです。</p><p>次に認証ハンドラーを作ります。このハンドラーの中でFeaturesから先ほどのフィルターでつけていたFeatureを引っ張り出します。</p><pre><code class="hljs csharp">public class CustomAuthenticationHandler : AuthenticationHandler&lt;AuthenticationSchemeOptions&gt;&#123; public CustomAuthenticationHandler(IOptionsMonitor&lt;AuthenticationSchemeOptions&gt; options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock) &#123; &#125; protected override Task&lt;AuthenticateResult&gt; HandleAuthenticateAsync() &#123; var enableNanika = Context.Features.Get&lt;IUseCustomAuthenticationWithNanikaFeature&gt;()?.Enable ?? false; if (enableNanika) &#123; // 一部で必要な特殊な処理... &#125; return Task.FromResult(AuthenticateResult.Fail("Unauthorized")); &#125; protected override Task HandleChallengeAsync(AuthenticationProperties properties) &#123; Response.Headers["WWW-Authenticate"] = "Nanika"; return base.HandleChallengeAsync(properties); &#125;&#125;</code></pre><p>Startupで認証ハンドラーを登録します。この際、AddAuthenticationで<strong>デフォルト認証スキームを登録しない</strong>ように注意します。デフォルトの認証スキームとなるハンドラーは順番が特別なことになるので意図した挙動になりません。</p><pre><code class="hljs csharp">services.AddAuthentication() .AddScheme&lt;AuthenticationSchemeOptions, CustomAuthenticationHandler&gt;(<span class="hljs-string">"Custom"</span>, options =&gt; &#123; &#125;);</code></pre><p>そしてコントローラーにはAuthorize属性を、特別な処理をしたいアクションにはUseCustomAuthenticationWithNanika属性でフィルターを付けます。</p><pre><code class="hljs csharp">[<span class="hljs-meta">Route(<span class="hljs-meta-string">"api/[controller]"</span>)</span>][<span class="hljs-meta">Authorize(AuthenticationSchemes = <span class="hljs-meta-string">"Custom"</span>)</span>]<span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title">ValuesController</span> : <span class="hljs-title">Controller</span>&#123; <span class="hljs-comment">// GET api/values</span> [<span class="hljs-meta">HttpGet</span>] [<span class="hljs-meta">UseCustomAuthenticationWithNanika</span>] <span class="hljs-function"><span class="hljs-keyword">public</span> IEnumerable&lt;<span class="hljs-keyword">string</span>&gt; <span class="hljs-title">Get</span>(<span class="hljs-params"></span>)</span> &#123; <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-keyword">string</span>[] &#123; <span class="hljs-string">"value1"</span>, <span class="hljs-string">"value2"</span> &#125;; &#125; <span class="hljs-comment">// GET api/values/5</span> [<span class="hljs-meta">HttpGet(<span class="hljs-meta-string">"&#123;id&#125;"</span>)</span>] <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">string</span> <span class="hljs-title">Get</span>(<span class="hljs-params"><span class="hljs-keyword">int</span> id</span>)</span> &#123; <span class="hljs-keyword">return</span> <span class="hljs-string">"value"</span>; &#125;&#125;</code></pre><p>これでアクションに属性を指定するだけで認証時の挙動を少し変更するということが可能になります。</p> ]]>
</content>
<summary type="html"> <p>ASP.NET Core MVCでは認証は基本的にフィルターではなく、認証ハンドラーを認証スキーム名で登録しておき、使用したい認証スキームをAuthorize属性などで指定するという形になっています(デフォルトという指定もできます)。</p> <p>今回やりたいこととしては </summary>
<category term="ASP.NET" scheme="http://www.misuzilla.org/Tags/ASP-NET/"/>
</entry>
<entry>
<title>U-NEXT TV (第二世代)はAndroidおもちゃではない</title>
<link href="http://www.misuzilla.org/Blog/2018/04/08/UNextTvIsNotAndroidTv"/>
<id>http://www.misuzilla.org/Blog/2018/04/08/UNextTvIsNotAndroidTv</id>
<published>2018-04-07T15:15:00.000Z</published>
<updated>2022-05-21T09:11:37.901Z</updated>
<content type="html">
<![CDATA[ <p><a href="https://av.watch.impress.co.jp/docs/news/1113860.html" target="_blank" rel="noopener">U-NEXT、9,800円のHDR対応Android TV「U-NEXT TV」。カラオケやYouTubeも - AV Watch</a>という記事を見て「先着100名で、新規にU-NEXT登録した人を対象にU-NEXT TVをプレゼントするキャンペーン」とあったので登録してみたところ無事届きました。というわけでAndroidとして遊べないかなというところに関してのお話です。</p><p>製品仕様は<a href="https://video.unext.jp/lp/u-nexttv_m380/" target="_blank" rel="noopener">キャンペーンサイト</a>に書いてあり、Android 7.0であることが明記されています。</p><p><a data-flickr-embed="true" href="https://www.flickr.com/photos/mayuki/40400409575/in/dateposted/" title="U-NEXT TV (2nd Gen)" target="_blank" rel="noopener"><img src="https://farm1.staticflickr.com/809/40400409575_1ea04ff0a7_h.jpg" alt="U-NEXT TV (2nd Gen)"></a><script async src="//embedr.flickr.com/assets/client-code.js" charset="utf-8"></script></p><p>端末自体はHuaweiです。この手のセットトップボックスではHuawei製はよくあるようです(たしかauやdocomoのものもそうだったはず)。</p><h2 id="OS"><a href="#OS" class="headerlink" title="OS"></a>OS</h2><p>さて、Androidに関してですがよく見ると公式サイトの製品仕様にはAndroid 7.0とはあるもののAndroid TVとは書いていないのでもしかしたら…と思っていたのですがセットアップし始めるとやはりAndroid TVではなく単なるAndroidです。</p><p>そもそもセットアップステップがAndroid TV風でなくU-NEXTのログインが必須であったり、オンスクリーンキーボードもAndroid TVのそれではない、ホーム画面は独自というあたりでお察しです。</p><p><img src="/Shared/Blog/img/2018/04/08/device-2018-04-07-201851.jpg" alt=""></p><p>製品仕様によればアプリも利用できることになっているのですがAndroidがベースになっているだけでGoogleサービスが入っていないのでGoogle Playは使えません。「U-NEXTサービス、HDMI-CEC対応、Player for YouTube、Androidなど」とあるので使えるのかと思いきや。</p><p>YouTubeを観れそうな雰囲気ですがGoogle Play入ってないのにどうやって?と思ったらHuaweiの独自アプリでした。oh…。</p><p>現状U-NEXT用のアプリストアがあるわけでもないのでプレインストールのアプリのみ利用できます。なお設定のアプリ管理からアプリをアンインストールできるのですが、それらはインストールする口がないので消したら最後です(これはひどい。</p><p>参考までにほぼ同型機の<a href="https://www.nttdocomo.co.jp/product/docomo_select/tt01/index.html" target="_blank" rel="noopener">docomoのドコモテレビターミナル</a>は説明書を見てもわかるのですが正真正銘のAndroid TVでロゴも表記されています。</p><h2 id="adbを使う"><a href="#adbを使う" class="headerlink" title="adbを使う"></a>adbを使う</h2><p>とはいえAndroidなのでUSBデバッグとかできればなんかいろいろできるかもということで探すとちゃんとUSBデバッグはあります。</p><p>「アプリ」→「端末設定」→「情報」→「開発者オプション」→「USBデバッグ」で有効になるので有効にして、USBケーブルをつなぐと許可するかどうかのダイアログが表示されます。ちなみにUSBケーブルはType-Aなので注意が必要です。</p><p>接続後はいつものようにadbでコマンドを実行できshellにも入れます。そこで早速apkをインストールしてみようとすると次のようなエラーが発生しました。</p><pre><code class="hljs plaintext">adb -s 0123456789 install &quot;app-release.apk&quot;adb: failed to install app-release.apk: Failure [INSTALL_FAILED_UPDATE_INCOMPATIBLE: Verify signature failed]</code></pre><p>どうも署名チェックのようなものがあり、そこで引っかかっているようです。</p><p>提供元不明のアプリのインストールを許可する必要があるのか?と思ったので設定画面を開いてセキュリティから許可してみましたがダメです。Webを検索してもそれらしい情報はないので端末固有でロックをかけていそうです。うーん。</p><p>なお、設定画面は am コマンドを使えば開けます。<br><pre><code class="hljs undefined">HWM380:/ $ am start -a android.intent.action.MAIN -n com.android.settings/.Settings</code></pre></p><p>ちなみにバージョン情報はこんな感じです。<br><img src="/Shared/Blog/img/2018/04/08/device-2018-04-07-201053.png" alt=""></p><p>もう少し探ってみたところ <code>/system/etc/security/apk_sign_white_list.xml</code> というファイルがあり、署名のホワイトリストのようなものだったのでユーザーが任意にインストールするのは無理そうですね(HuaweiやU-NEXTだけが含まれている)。</p><p>一応 <code>pm list package</code>の結果、<a href="https://gist.github.com/mayuki/de5079427cb6646c31f45f1a37b698d1" target="_blank" rel="noopener">パッケージの一覧はこんな感じ</a>でした。</p><h2 id="まとめ"><a href="#まとめ" class="headerlink" title="まとめ"></a>まとめ</h2><p>残念ながらAndroid端末としてはいじりがいがないのでそこを期待してはダメそうです。とはいえ特定サービス向けの装置なのでそこはしょうがないかなーという気もします。</p><p>U-NEXTのSTBとしてはきびきび動いたりするので普通に使う分には割とよいのではないかとは思いますが、実際お金を出す場合半額の4,980円でもどうかなー…って感じがしますね。リテラシがなくてつなげば使えるみたいなのを求めていない限り、正直縛りのないAndroid TVである<a href="https://www.cccair.co.jp/airstick/" target="_blank" rel="noopener">AirStick 4K</a>の方がよさそう。</p><p>そうそう、ログイン必須なので退会すると完全に使い物にならなくなります。</p> ]]>
</content>
<summary type="html"> <p><a href="https://av.watch.impress.co.jp/docs/news/1113860.html" target="_blank" rel="noopener">U-NEXT、9,800円のHDR対応Android TV「U-NEXT TV」。カ </summary>
<category term="Android" scheme="http://www.misuzilla.org/Tags/Android/"/>
</entry>
<entry>
<title>App Centerでユーザー定義環境変数を取得できない</title>
<link href="http://www.misuzilla.org/Blog/2018/02/15/AppCenterEnvironmentVariable"/>
<id>http://www.misuzilla.org/Blog/2018/02/15/AppCenterEnvironmentVariable</id>
<published>2018-02-15T01:00:00.000Z</published>
<updated>2022-05-21T09:11:37.897Z</updated>
<content type="html">
<![CDATA[ <p>Visual Studio App Centerではビルド時の環境変数を定義でき、ビルドスクリプトやGradleのようなビルドシステムから値を取得できるのですが、定義しても環境変数が取れないという現象が発生しました。</p><ul><li><a href="https://docs.microsoft.com/en-us/appcenter/build/custom/variables/" target="_blank" rel="noopener">Environment variables</a></li></ul><h2 id="tl-dr"><a href="#tl-dr" class="headerlink" title="tl;dr"></a>tl;dr</h2><p><code>SECRET</code> のようなApp Centerが許可しないキーワードを含んだ環境変数を定義している場合、そのままの名前で露出しないようになっているようです。<code>PASSWORD</code> のような単語は通るので <code>SECRET</code> のみの制限かもしれません(ドキュメントに特に書いてない)。</p><p>ユーザー定義の環境変数は <code>USER-DEFINED_</code> というプレフィックスがついた環境変数も定義されるのでそちらから取得する方法もありますが、ハイフンを含んでいるため参照も定義も一筋縄ではいかないのでお勧めできません。</p><h2 id="問題と調査"><a href="#問題と調査" class="headerlink" title="問題と調査"></a>問題と調査</h2><p><code>API_KEY</code> や <code>NUGET_USERNAME</code> のような環境変数を定義してみたところ特に問題なく取得できたので、もしかして変数名に問題があるのでは?と思いいくつかのパターンを試してみました。すると以下のパターンで違いが出ることがわかりました。</p><ul><li>SECRET_HAUHAU: hauhau1</li><li>SEECRET_HAUHAU: hauhau2</li><li>SECRE__HAUHAU: hauhau3</li></ul><p>この定義を設定した上でビルドし、pre-buildスクリプトで環境変数をすべて出力すると…</p><pre><code class="hljs plaintext">##[section]Starting: Pre Build Script==============================================================================Task : Shell ScriptDescription : Run a shell script using bashVersion : 2.1.3Author : Microsoft CorporationHelp : [More Information](https://go.microsoft.com/fwlink/?LinkID=613738)==============================================================================[command]/bin/bash /Users/vsts/agent/2.127.0/work/1/s/app/appcenter-pre-build.sh...snip...SECRE__HAUHAU=hauhau3...snip...USER-DEFINED_SEECRET_HAUHAU=hauhau2...snip...USER-DEFINED_SECRE__HAUHAU=hauhau3...snip...USER-DEFINED_SECRET_HAUHAU=hauhau1...snip...SEECRET_HAUHAU=hauhau2...snip...</code></pre><p>…となり、<code>SECRET_HAUHAU</code> という変数のみそのまま露出しないようになっています。この結果からApp Centerは <code>SECRET</code> を含む環境変数をそのまま変数として露出しないようにしているようです。セキュリティのためでしょうか。</p><p>というわけで特別必要がなければ <code>SECRET</code> という名前を含めないでおくというのがハマらないポイントです。</p> ]]>
</content>
<summary type="html"> <p>Visual Studio App Centerではビルド時の環境変数を定義でき、ビルドスクリプトやGradleのようなビルドシステムから値を取得できるのですが、定義しても環境変数が取れないという現象が発生しました。</p> <ul> <li><a href="https: </summary>
<category term="AppCenter" scheme="http://www.misuzilla.org/Tags/AppCenter/"/>
</entry>
<entry>
<title>App Centerでビルドスクリプトが動かない</title>
<link href="http://www.misuzilla.org/Blog/2018/02/13/AppCenterBuildScripts"/>
<id>http://www.misuzilla.org/Blog/2018/02/13/AppCenterBuildScripts</id>
<published>2018-02-12T15:55:00.000Z</published>
<updated>2022-05-21T09:11:37.897Z</updated>
<content type="html">
<![CDATA[ <p>Visual Studio App Centerでビルドする際、特定の名前でシェルスクリプトを置いておくことでビルドの前後でコマンドを実行できます。</p><p>2018年2月時点では <a href="https://docs.microsoft.com/en-us/appcenter/build/custom/scripts/" target="_blank" rel="noopener">Build scripts | Microsoft Doc</a> にある通り、以下のスクリプトを認識します(for UWPなら.ps1)。</p><ul><li>appcenter-post-clone.sh: git clone後</li><li>appcenter-pre-build.sh: パッケージ等復元後、ビルド前</li><li>appcenter-post-build.sh: ビルド後</li></ul><p>ここまでは書いてある通りなのですが、実際リポジトリにビルドスクリプトを含めても一向に実行されないという問題が発生しました。設定画面を見ると <code>Build scripts: ✔ Post-clone</code> と表示され認識はされているようでした。</p><p>新しいブランチを作ってビルド設定を追加して試すと問題なく動作したので悩んだのですが、実は <code>Build scripts: ✔ Post-clone</code> となった後に <code>Save</code> もしくは <code>Save &amp; Build</code> で設定を保存する必要があるようです。</p><p>逆を言うと、ビルドスクリプトを削除した後も保存しなおさないとビルドがエラーになります。</p><p>新しいブランチを作った時に動いたのは、すでにビルドスクリプトが含まれていて、ビルド定義を作る際に <code>Build scripts: ✔ Post-clone</code> となった状態だったからということでした。</p> ]]>
</content>
<summary type="html"> <p>Visual Studio App Centerでビルドする際、特定の名前でシェルスクリプトを置いておくことでビルドの前後でコマンドを実行できます。</p> <p>2018年2月時点では <a href="https://docs.microsoft.com/en-us/a </summary>
<category term="AppCenter" scheme="http://www.misuzilla.org/Tags/AppCenter/"/>
</entry>
<entry>
<title>TiarraのIRCログをBigQueryに流し込む</title>
<link href="http://www.misuzilla.org/Blog/2018/01/18/TiarraToBigQuery"/>
<id>http://www.misuzilla.org/Blog/2018/01/18/TiarraToBigQuery</id>
<published>2018-01-18T00:40:00.000Z</published>
<updated>2022-05-21T09:11:37.897Z</updated>
<content type="html">
<![CDATA[ <p><img src="/Shared/Blog/img/2018/01/18/Screen_01.png" alt=""></p><p>ふとTiarraでとったIRCのログをBigQueryに流し込むようにしたら手元にログを長期間残す必要もないし、検索もできるし便利そうだなと思ったので設定してみました。Twitterのログもあって若干流れますがBigQuery的には誤差の範囲レベル(1日1MB程度)なのでStreaming Insertして、たまーに検索しても無料枠で余裕で収まりそうです。</p><p>主な流れはTiarraで吐いたテキストログをfluentdのin_tailで読んで、<a href="https://github.com/kaizenplatform/fluent-plugin-bigquery" target="_blank" rel="noopener">fluent-plugin-bigquery</a>でBigQueryに流し込むという何の変哲もない形です。</p><h2 id="Docker"><a href="#Docker" class="headerlink" title="Docker"></a>Docker</h2><p>ですでに手元のTiarraはDocker(docker-compose)で起動しているのでfluentdもDockerで起動しています。fluent-plugin-bigqueryはfluentdの公式イメージに追加する必要があるので適当に追加します。</p><pre><code class="hljs Dockerfile"><span class="hljs-keyword">FROM</span> fluent/fluentd<span class="hljs-keyword">RUN</span> apk --no-cache --update add ruby-bigdecimal ruby-dev build-base &amp;&amp; \ fluent-gem install fluent-plugin-bigquery</code></pre><p>ruby-bigdecimalが必要ということはREADMEにあるのですが、ruby-devとbuild-baseも必要で追加していないとfluent-plugin-bigqueryをインストール中にstrptimeのビルドで失敗します。</p><h2 id="BigQuery-Schema-schema-json"><a href="#BigQuery-Schema-schema-json" class="headerlink" title="BigQuery Schema (schema.json)"></a>BigQuery Schema (schema.json)</h2><p>次にBigQueryのスキーマを用意します。今回は次のようなものになっています。</p><pre><code class="hljs json">[ &#123; <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Time"</span>, <span class="hljs-attr">"type"</span>: <span class="hljs-string">"TIMESTAMP"</span> &#125;, &#123; <span class="hljs-attr">"name"</span>: <span class="hljs-string">"User"</span>, <span class="hljs-attr">"type"</span>: <span class="hljs-string">"STRING"</span> &#125;, &#123; <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Server"</span>, <span class="hljs-attr">"type"</span>: <span class="hljs-string">"STRING"</span> &#125;, &#123; <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Room"</span>, <span class="hljs-attr">"type"</span>: <span class="hljs-string">"STRING"</span> &#125;, &#123; <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Text"</span>, <span class="hljs-attr">"type"</span>: <span class="hljs-string">"STRING"</span> &#125;]</code></pre><p>テーブル自体はこのスキーマを使ってfluent-plugin-bigqueryに自動生成させるので、BigQuery側にはプロジェクトとデータセットだけ作っておきます。このスキーマファイルは次のfluent.confと同じところにおいてください(最終的にdockerでマウントされるところ)。</p><h2 id="fluentd-fluent-conf"><a href="#fluentd-fluent-conf" class="headerlink" title="fluentd (fluent.conf)"></a>fluentd (fluent.conf)</h2><p>最後にfluentdの設定です。</p><p>source(入力)は<a href="https://ssig33.com/text/Tiarra%20のログを%20fluentd%20で流す" target="_blank" rel="noopener">ssig33 - Tiarra のログを fluentd で流す</a>を参考にin_tailを設定します。手元ではTIGのログが流れてくるのとサーバー名を分解するために少し変更しています。ログはローテートされるので %Y.%m.%d.txt で。</p><p>出力はfluent-plugin-bigqueryの設定を <code>auto_create_table</code> で設定します。これ自体は何のことはない設定なのですが table を年で分けるようにしようとしたところでハマりました。</p><p>table にはプレースホルダ文字列を使えることになっているのですが、文字列置換が有効になる条件としてまず buffer time/timekey を設定しているというものがあります。そこで timekey を設定すると <code>%Y</code> ではなく <code>%d</code> 精度のプレースホルダを持つ設定を要求します(<a href="https://github.com/fluent/fluentd/blob/82121fa4f504d8f52669f8234ca4c53ed085c0de/lib/fluent/plugin/output.rb#L651-L666" target="_blank" rel="noopener">参考: output.rb</a>)。しかし今回は%Yでいいのに…困った…と考えた末に <code>fetch_schema_table</code> という使っていない設定項目に <code>log_%Y%m%d</code> 入れて回避しました。</p><p>とはいえ真面目にBigQueryを使うレベルなら日別にすると思うので(扱いづらいし…)罠にははまらないとは思います。日付パーティションにしてもいいかもとか頭をよぎりますが量も大してないので最悪あとで何とかすればいいでしょう(適当。</p><pre><code class="hljs plaintext">&lt;source&gt; @type tail path /fluentd/log/tig/tig-twitter/%Y.%m.%d.txt,/fluentd/log/ircnet/*/%Y.%m.%d.txt,/fluentd/log/freenode/*/%Y.%m.%d.txt tag tiarra format /^(?&lt;time&gt;[^ ]+)\ (&lt;(?&lt;room&gt;[^ ]*)@(?&lt;server&gt;[^ ]*):(?&lt;user&gt;[^ ]*)&gt;|\((?&lt;room&gt;[^ ]*)@(?&lt;server&gt;[^ ]*):(?&lt;user&gt;[^ ]*)\)|&gt;(?&lt;room&gt;[^ ]*)@(?&lt;server&gt;[^ ]*):(?&lt;user&gt;[^ ]*)&lt;|-(?&lt;user&gt;[^ ]*)-|&gt;(?&lt;user&gt;[^ ]*)@[^ ]*&lt;)\ (?&lt;text&gt;.*?)(?: \u0003\d+\([^)]+\).*)?$/ time_format %H:%M:%S&lt;/source&gt;&lt;match tiarra&gt; @type bigquery method insert auth_method json_key json_key /fluentd/etc/serviceaccount_key.json project &lt;YourProjectName&gt; dataset &lt;YourDataSetName&gt; table Log_%Y auto_create_table true schema_path /fluentd/etc/schema.json # table でplaceholderを使うために必要 # buffer time/timekeyの設定が必要でそのためにplaceholderの%dをもつ設定が必要なので使わない項目をダミーとして使う… fetch_schema_table log_%Y%m%d &lt;buffer time&gt; timekey 1d &lt;/buffer&gt; &lt;inject&gt; time_key time time_type string time_format %s &lt;/inject&gt;&lt;/match&gt;</code></pre><p>あとはこれで<a href="https://github.com/fluent/fluentd-docker-image" target="_blank" rel="noopener">fluent/fluentd-docker-image</a>を参考にDockerでマウントしつつ起動すればログがBigQueryに良しなに送られます。便利。</p> ]]>
</content>
<summary type="html"> <p><img src="/Shared/Blog/img/2018/01/18/Screen_01.png" alt=""></p> <p>ふとTiarraでとったIRCのログをBigQueryに流し込むようにしたら手元にログを長期間残す必要もないし、検索もできるし便利そうだな </summary>
<category term="IRC" scheme="http://www.misuzilla.org/Tags/IRC/"/>
<category term="Tiarra" scheme="http://www.misuzilla.org/Tags/Tiarra/"/>
<category term="BigQuery" scheme="http://www.misuzilla.org/Tags/BigQuery/"/>
</entry>
</feed>