しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Core 3.0 への移行時に悩んだ点と新しくなった Endpoint Routing について

ASP.NET Core 2.x から 3.0 への移行をプライベートと仕事のアプリケーション両方で試しました。

基本的にはドキュメントの通り行えば良いので簡単ですが、少し別途対応が必要だった部分があるのでメモとして残しておきます。あと Endpoint Routing 周りについて少し追加します。

Razor 系のツールを削除する

2.2 まではテンプレートから View を作る時にインストールされていた Microsoft.AspNetCore.Razor.Design は 3.0 ではサポートされなくなりました。

インストールされていると Visual Studio でのビルド時にエラーとなるので削除します。

f:id:shiba-yan:20190925013210p:plain

他にも発行時に Razor をプリコンパイルする Microsoft.AspNetCore.Mvc.Razor.ViewCompilation というパッケージがインストールされているケースがありますが、既に SDK 側にプリコンパイル機能が組み込まれ不要になっているので削除しておきましょう。

パッケージ更新後に 3.0.0 になっていないものは大体サポートされなくなったものなので削除した方が良いです。必要な場合は Visual Studio が都度インストールしてくれます。

Entity Framework Core 関連パッケージの追加

3.0 の MicrosoftAspNetCore.App からは Entity Framework Core 関連が除外されているので、2.2 からの移行時にパッケージが見つからないというエラーが出るはずです。

大抵は Microsoft.EntityFrameworkCore.SqlServer をインストールすれば良いですが、Identity や DB Migration 周りで以下のパッケージに依存していることもあるので、インストールする必要があります。

  • Microsoft.AspNetCore.Identity.EntityFrameworkCore
    • ASP.NET Core Identity を Entity Framework Core で使う場合に必要
  • Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
    • UseDatabaseErrorPage などを使う場合に必要

開発時に DB がない場合はブラウザから作成できる UseDatabaseErrorPage は便利なので使っているケースは多いでしょう。これまで暗黙的に参照されていたものはパッケージ名がぱっと見は分からないので、都度調べる必要があるのが少し手間です。

Entity Framework Core 3.0 は Azure Functions で利用不可

Entity Framework Core 3.0 はターゲットが .NET Standard 2.1 となったため、現状 .NET Core 2.2 をターゲットとする Azure Functions v2 では利用できないです。

f:id:shiba-yan:20190925161912p:plain

Azure Functions v3 では .NET Core 3.0 に対応するようになるので、その時までは 2.2 を使い続ける必要があります。.NET Standard 2.1 のみ対応するパッケージは同様にインストール出来ません。

3.0 から完全に消えたクラス

.NET API Browser や GitHub から検索してヒットしないクラスやメソッドは 3.0 から消えたので、別の方法に変えたり使っていない場合は削除する必要があります。

今回の移行時には ASP.NET Core Identity で使われていた AuthenticationDescription が該当しました。

テンプレートから自動生成されたクラスで使われていたのと、そもそも参照されていなかったのでクラスごと削除して対応しました。不要なものを残していると苦労します。

ASP.NET Core 3.0 の Endpoint Routing を理解する

以前にも書いた気がしますが、これまでの ASP.NET Core のルーティングは ASP.NET MVC の初期に実装された仕組みから大きく変わっておらず、各フレームワークごとにルーティングの仕組みが用意されてきました。

更に問題となるのが、CORS や認可周りが各フレームワークごとに別々に用意されていたことです。これはこれまでのルーティングの仕組み上、共通の処理を書くことが出来なかったのが理由です。Endpoint Routing によってフレームワークから切り離されたルーティングが実現されました。

なので、これまで別々に用意されてきた CORS や認可周りは、共通のパイプライン上で処理が行えるようになりました。Endpoint Routing に関しては情報が少なく、Preview 4 の記事が比較的しっかりしてます。

ASP.NET Core 2.2 の Endpoint Routing とは異なり、パイプラインで明示的に UseRoutingUseEndpoints を呼び出す必要があります。それぞれのメソッドはルートのマッチングを行うタイミングと、実際にリクエストの処理を行うタイミングを表しています。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // ルーティングに依存しない処理は UseRouting の前に書く
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseDatabaseErrorPage();
    }
    else
    {
        app.UseExceptionHandler("/Home/Error");
        app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    // ここでルートのマッチングが行われ、結果は HttpContext にセット
    app.UseRouting();

    // 認証・認可はルーティングの結果が必要
    app.UseAuthentication();
    app.UseAuthorization();

    // CORS もルーティングの結果が必要
    app.UseCors();

    // マッチング結果に従って各処理に振り分けられる
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
        endpoints.MapRazorPages();
    });
}

よくあるパイプラインと同じなのでリクエストは上から順に実行されていき、一般的には UseEndpoints でレスポンスが生成されて、リクエストとは逆の順番で実行されてクライアントに返っていきます。

ここまで理解できれば、Endpoint Routing はほぼ理解したことになります。

Status Code Page が動かなくなった

ASP.NET Core 3.0 への移行後に UseStatusCodePagesWithReExecute を使って実装していたエラー画面が表示されなくなりました。これは HTTP ステータスコードごとにカスタムエラーページを実現する機能です。

移行後のアプリケーションでは存在しないページへのアクセス時に、ブラウザの 404 ページが表示されるようになってしまい、用意したエラーページは表示されなくなりました。

f:id:shiba-yan:20190925173314p:plain

ここまで行ってきた Endpoint Routing の説明を踏まえると動かなくなった理由が分かると思いますが、原因は単純に UseStatusCodePagesWithReExecute のパイプラインでの呼び出し位置が間違っていたことです。

リダイレクトの場合は問題ないのですが、パイプラインの再実行を行う場合はルーティングの前に呼び出しておく必要があります。ルーティング後に呼び出すと、実行されるエンドポイントが確定できません。

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseHttpsRedirection();
    app.UseStaticFiles();

    // 指定した URL でパイプラインを再実行するので UseRouting の前に呼ぶ必要がある
    app.UseStatusCodePagesWithReExecute("/Error/Index", "?statusCode={0}");

    app.UseRouting();

    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllerRoute(
            name: "default",
            pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}

正しく UseRouting の前に呼び出すように修正すると、エラーページが表示されるようになりました。

f:id:shiba-yan:20190925173326p:plain

最初の方は Endpoint Routing の挙動に慣れが必要な感じですが、これまで Core MVC のフィルタなどで独自に書いていた部分を、Middleware として ASP.NET Core 全体で使えるようになるのは良い感じです。

暫くすると公式ドキュメントも Endpoint Routing 対応に更新されるかと思います。