koudenpaのブログ

趣味のブログです。株式会社はてなでWebアプリケーションエンジニアをやっています。職業柄IT関連の記事が多いと思います。

ホイホイさん NEW EDITION

10年の時を越えて超名作プラモデルホイホイさんがNEW EDITIONとして帰ってきたので組み立てた。

www.kotobukiya.co.jp

NEW EDITIONでは肌と服の艶消し仕上げに加えて、当時は別売だった「専用クレイドル(ホイホイさん用充電器型スタンド)」がセットになっている。

このクレイドルは良いもので、足の裏に仕込まれる磁石がクレイドルに仕込まれる鉄板にピタッと吸い付く。その上、クレイドルには前面に充電のインジケータランプがある。

これは磁石でランプを光らせるしかないな、と10年前から思っていた。それをついに実現した。やったー。

スイッチにはこれを使った。

www.amazon.co.jp

ホイホイさんの背中の端子はUSB-Bだが、クレイドルは(多分)micro USBだ。端子とケーブルを買うときは気を付けろ。俺は勘違いしてBを購入して無駄にした。

1/1スケールらしい遊びができてとても満足した。

良い時間なので寝ます。おやすみなさいホイホイさん

f:id:koudenpa:20200522025554j:plain

Azure Static Web Apps に Blazor WebAssembly を配置する

先日のMicrosoft BuildApp ServiceStatic Web Appsという静的Webサイト(+ Azure Functions)をホスティングできるサービスのプレビューが発表された。

azure.microsoft.com

SPA向けに構成されている性質上、チュートリアルはJavaScriptのフレームワーク(Next.jsとNuxt.js)向けに作られている。

f:id:koudenpa:20200521010330p:plain

しかし、同じBuildでBlazor WebAssemblyのGAも発表された。

であるなら、これをホスティングしたくなるのが人情だ。

という訳でデプロイを試してみた。

Static Web Appsデプロイのステップ(Azure/static-web-apps-deploy)前にデプロイ対象をビルドしておき、それを配置するように指定すればデプロイされた。やったね。BlazorもStatic Web Appsできるぜ! はやくチュートリアルに載せてくれ!!!

ワークフロー 抜粋。

jobs:
  build_and_deploy_job:
    if: github.event_name == 'push' || (github.event_name == 'pull_request' && github.event.action != 'closed')
    runs-on: ubuntu-latest
    name: Build and Deploy Job
    steps:
    - uses: actions/checkout@v1
    - name: Set up .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: '3.1.100'
    - name: Build Blazor Application
      run: dotnet publish -c Release ./BlazorAppSandbox/bazor-app-sandbox.csproj
    - name: Build And Deploy
      id: builddeploy
      uses: Azure/static-web-apps-deploy@v0.0.1-preview
      with:
        azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_BRAVE_ISLAND_0D044B21E }}
        repo_token: ${{ secrets.GITHUB_TOKEN }} 
        action: 'upload'
        app_location: 'BlazorAppSandbox/bin/Release/netstandard2.1/publish/bazor-app-sandbox/dist' # App source code path
        #api_location: 'api' # Api source code path - optional
        #app_artifact_location: '' # Built app content directory - optional

デプロイ先:https://brave-island-0d044b21e.azurestaticapps.net/

ダイナミックルート(404ページをいい感じにSPAするやつ)は設定していない(そもそもできるのか知らない)のでルート以外へのアクセスは404する状態だし、そもそも手元にあったデプロイできそうなBlazorのプロジェクトはプレビュー版なのでGAした版をデプロイしたわけではない。

本当にとりあえずデプロイしただけ! しかし、今後に期待できる結果で満足だ。

GA前にはFunctionsのランタイムに.NETも頼む。


2020-07-26追記:

ダイナミックルート(404ページをいい感じにSPAするやつ)は設定していない(そもそもできるのか知らない)のでルート以外へのアクセスは404する状態だし、そもそも手元にあったデプロイできそうなBlazorのプロジェクトはプレビュー版なのでGAした版をデプロイしたわけではない。

これ、半端な状態だったので解消しておいた。

アプリはプレビュー版ママだけれど、Static Web Appsのルート設定をした。

docs.microsoft.com

さしあたってリソースが存在しなったパスへのアクセスに対して index.html を返却して、パスに応じた動作をクライアントサイドでさせるなら routes.json にその旨の設定をするだけで期待通りの動きをしてくれた。

{
  "routes": [
    {
      "route": "/*",
      "serve": "/index.html"
    }
  ]
}

対応PR(他のコミットも混ざってるけれど) Staticwebapp update by 7474 · Pull Request #58 · 7474/bazor-app-sandbox · GitHub

Blazor WebAssembly のSSRを試した

Blazor的にはSSRではなくプリレンダリング(Prerendering)と呼ぶらしいけれど、やりたいこと的にはサーバサイドレンダリングの方が通りがいいのではないかと思う。

どうすれば Blazor WebAssembly をプリレンダリングできるのか

プリレンダリングするには前提としてサイトをASP.NET Coreでホスティングする必要がある。この辺りに案内がある。

その上で、アプリケーションのエントリポイントを静的な index.html として配信するのではなく、動的にRazorテンプレートをレンダリングするように構成する。

ここで注意が必要なのは、このプリレンダリングASP.NET Coreのサーバサイドアプリケーションで行われる点だ。

実行環境がWebAssemblyではなく.NET Coreなので、サポートしている機能は異なっている。加えて、Webブラウザ上での実行ではないためJavaScriptの相互運用機能は使用できない(できないよと例外する)。

これを動作させるにはRazorテンプレートは.NET Standardに依存する形で記述し、クライアントサイドとサーバサイドでそれぞれに適した実装をDIしてやればよい。

余談だけれど、どうすればSSRできるのかちょっととっつきが悪かった。そういったIssue(Document prerendering in Blazor (WebAssembly) · Issue #11366 · dotnet/AspNetCore.Docs · GitHub)は立っていた。『そもそもプリレンダリングについて分かりづらいんじゃないの?』と『WebAssembly のサーバサイドレンダリングしたい』の2トピックがある感じだろうか。

こんな感じに実装してみた

先日作ったこれを WebAssembly してプリレンダリングした。

koudenpa.hatenablog.com

Blazor Serverでのホスティングも生きているので、Blazor Serverでのホスティング、Blazor WebAssembly のクライアントサイドとサーバサイドで3通りのRazorコンポーネントレンダリングをしていることになる。Blazor楽しいじゃないか。

App Serviceでホスティングしている。

基本構成はコピペ

ASP.NET Core Blazor WebAssembly additional security scenarios | Microsoft Docs から。

プリレンダリングの起点となるテンプレートはこれ。MultiComputerVision/_Host.cshtml at 43c61baf77936b207f5ee413e6fe0a70e5ae1d39 · 7474/MultiComputerVision · GitHub

    <app>
        @if (!HttpContext.Request.Path.StartsWithSegments("/authentication"))
        {
            <component type="typeof(App)" render-mode="Static" />
        }
        else
        {
            <text>Loading...</text>
        }
    </app>

(認証のないページは存在しないので)この if 文意味ないな。後で消そう。

Razorテンプレートは.NET Standardに依存する形で記述

まず、必要な処理のインタフェースを定義。 MultiComputerVision/IResultDocumentService.cs at 43c61baf77936b207f5ee413e6fe0a70e5ae1d39 · 7474/MultiComputerVision · GitHub

    public interface IResultDocumentService
    {
        Task<IResultDocument> GetResult(Guid id);
        Task<IList<IResultDocument>> GetResults(DateTimeOffset offset);
    }

テンプレートではインタフェースを依存関係として注入(DI)する。 MultiComputerVision/Result.razor at 43c61baf77936b207f5ee413e6fe0a70e5ae1d39 · 7474/MultiComputerVision · GitHub

@page "/results/{id}"
@inject IResultDocumentService ResultDocumentService

@if (doc == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <div>
        <img src="@doc.Image.Uri" style="max-width: 100%" />
    </div>
}

@code {
                doc = await ResultDocumentService.GetResult(Guid.Parse(Id));
                title = doc.GetTitle();
                description = doc.GetDescription();
}

※コードは適当に抜粋している

クライアントサイドとサーバサイドでそれぞれに適した実装をDI

サーバサイドでは適当に処理する。クライアントサイドで動作しないので何でもやり放題だ。 MultiComputerVision/ServerSideResultDocumentService.cs at 43c61baf77936b207f5ee413e6fe0a70e5ae1d39 · 7474/MultiComputerVision · GitHub

    public class ServerSideResultDocumentService : IResultDocumentService
    {
        private readonly IResultRepositoryService resultRepositoryService;

        public ServerSideResultDocumentService(IResultRepositoryService resultRepositoryService)
        {
            this.resultRepositoryService = resultRepositoryService;
        }

        public async Task<IResultDocument> GetResult(Guid id)
        {
            return await resultRepositoryService.GetResult(id);
        }

で、それをDI。MultiComputerVision/Startup.cs at 43c61baf77936b207f5ee413e6fe0a70e5ae1d39 · 7474/MultiComputerVision · GitHub

        public void ConfigureServices(IServiceCollection services)
        {
            // ~略~
            services.AddSingleton<IResultDocumentService, ServerSideResultDocumentService>();
            services.AddSingleton<IUploadImageService, ServerSideUploadImageService>();

クライアントサイドではサーバーが提供(自分で作ったわけだけれど)しているAPIへのリクエストとしてインタフェースを実装する。 MultiComputerVision/ResultDocumentService.cs at 43c61baf77936b207f5ee413e6fe0a70e5ae1d39 · 7474/MultiComputerVision · GitHub

    public class ResultDocumentService : IResultDocumentService
    {
        private readonly HttpClient httpClient;

        public ResultDocumentService(AllowGuestHttpClient httpClient)
        {
            this.httpClient = httpClient;
        }

        public async Task<IResultDocument> GetResult(Guid id)
        {
            return await httpClient.GetFromJsonAsync<PlainResultDocument>($"Api/ResultDocument/{id}");
        }

で、それをDI。MultiComputerVision/Program.cs at 43c61baf77936b207f5ee413e6fe0a70e5ae1d39 · 7474/MultiComputerVision · GitHub

        public static async Task Main(string[] args)
        {
            // ~略~
            builder.Services.AddSingleton<IResultDocumentService, ResultDocumentService>();
            builder.Services.AddSingleton<IUploadImageService, UploadImageService>();

サーバサイドでは.NET Coreでプリレンダリングされて、その後クライアントサイドではWebAssemblyで動作する複合的なアプリケーションとなった。実にカオス。

感想

「これこそが依存関係の注入だよ!」って感じでテンションが上がった。趣味で試す分にはよい。

大き目のプロジェクトで開発・運用するのは地獄を見そうだが、JavaScriptのクライアントサイドとサーバサイドレンダリングと比べてどちらが地獄かは分からない。意外といけるんじゃないか? って気になってきている。

ページネート? そんなものいいからとりあえず全部よこせよ! Azure SDK for .NET 編

先日AzureのAPIを.NETのSDKから呼び出したのだけれど、ページネートを忘れていてAPIの結果が欠落した。

Fix pagenate by 7474 · Pull Request #2 · 7474/PostAzureCostToMackerelFunction · GitHub でFixした(つもり)。

ページネートという概念は多量のデータをAPIで扱う際には必須な要素ではあるけれど、とりあえず全部欲しい場面もある。

その場合は IPage<T>AsContinuousCollection すればいいようだった。

IPageには次の結果を得るためのNextPageLinkプロパティがある。愚直に実装する場合は、その値があればXxxNextメソッドに渡すことで次の結果を得られる。

AsContinuousCollection拡張メソッドはそれをよしなに処理してIEnumerableとして列挙できるようにしてくれる。

続きを読む

PLAMAX MF-43 minimum factory ディードリット

を組んだ感想。

これ。

www.goodsmile.info

がこうなった。

やったこと。

  • 髪と肌にリアルタッチマーカーで影を少し入れた
  • 目とサークレットのデカール貼った
  • 髪と肌に半艶のトップコートした
  • ハードレザーに青、エングレービングに金塗った(Mr.カラー)
  • 剣塗った
  • 髪と肌以外にシタデルのShadeジャブジャブした

僕は基本的にパチ組モデラーなので、力作は他の方のを探してくれ。沢山出てきて眼福。

twitter.com

続きを読む

リングドリーム 女子プロレス大戦完結に寄せて

明日、2020-03-26に7年間続いたリングドリームがサービス終了する。特設ページがある。

自分はここしばらくログインしていない(ソーシャルゲームを継続的にプレイするのが大変で全般に距離を取っている)のだけれど、それでもなかなか感無量だ。7年間ずっと追い続けてきた人たちにとっていかほどのものか、想像もできない。

この記事はその感無量さにあてられて、とりとめもない思い出を書いたものだ。

続きを読む

WIP Blazor WebAssembly から EasyAuth で認証して Azure Functions でホスティングしているAPIを呼び出したい

のでライブラリを書いている。

github.com

↓の記事を参考にしている。記事は3.0の頃のものなのだけれど、今は3.2のプレビュー中だ。BlazorもASP.NET Coreもちょっとずつ構成(関連NuGetパッケージが細かく分れるなど)が変わっていたりしているのでその差分に対応したり、自分が使いやすいようにしたりしている。

medium.com

取りあえず最低限Twitterで認証して認証したユーザーとしてFunction Appにアクセスできるようにはなった。

他のプロバイダに対応したリ、セッションを更新したり、認証ヘッダをいい感じに扱ったり、などまだやることはあるけれど、やるかは分からない。

f:id:koudenpa:20200323015718g:plain

https://identityhistory.z11.web.core.windows.net/GitHub - 7474/IdentityHistoryTwitter認証するとkoudenpaのテスト用Slackワークスペースのユーザーを見られる素晴らしいアプリケーション)をホスティングしている。

Azure Blob Storage + Azure Functions、これなら従量課金でとてもお安くアプリケーションを動かせる(アクセス数によってはお金がかかるだろうけれど、趣味で色々試すだけならそんなことにはならない)。

Azureでお安くアプリケーションホスティングは僕の日曜プログラムの一つのテーマであるようだ。他にも定期的に別の形でEasyAuthしている気がする。ありものをパズルのように組み合わせるのが好きなのだろう。

この辺。

これからも楽しんでいきたい。


感想追記

EasyAuthってどのくらいの知名度かあるのだろう? 記事を書くなら知らん人にも魅力が伝わるようにしたいものだ。