Why not login to Qiita and try out its useful features?

We'll deliver articles that match you.

You can read useful information later.

7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Blazor の OnInitialize で NavigationManager.NavigateTo してはいけない

Last updated at Posted at 2024-12-08

OnInitialize で NavigationManager.NavigateTo すると例外発生する場合がある

ずばり、表題のとおりです。

例えば以下のように実装されたページコンポーネントがある、標準的なプロジェクトテンプレートから生成された Blazor Server アプリケーションがあるとします。

@page "/articles"
@inject NavigationManager NavigationManager

@code {
    protected override void OnInitialized()
    {
        this.NavigationManager.NavigateTo("./articles/1");
    }
}

上記実装は、"https://.../articels" の URL に遷移 (ナビゲーション) されたら、さらに "https://.../articels/1" の URL に遷移 (いわゆる "リダイレクト" 的な動作) することを想定しています。実際、いったん、"https://.../" などの URL にアクセスして、Blazor Server としての対話モードが機能し始めてから、ページ内のリンク経由などで "https://.../articels" に遷移 (ナビゲーション) した場合は、期待したように動作します。

しかし、この Blazor Server アプリケーションに対して、"https://.../articels" の URL をブラウザのアドレスバー直接入力してアクセスすると、NavigationException 例外が発生する場合があります。

ただし、この NavigationException 例外、とくにログ出力されるわけではないようで、かつ、その後のレンダリング処理もそれなりに行なわれるようであるため、このような実装をしていても気づかずに済んでいるかもしれません。Windows 上で Visual Studio を使ってのデバッグ実行時などに、例外発生が報告されて気づくことがあるような感じです。

なぜ例外が発生してしまうのか

このような例外が発生してしまうのは、サーバー側レンダリングがその原因の一端です。Blazor の NavigationManager における NavigateTo 動作は、Web ブラウザの History API などを呼び出して、遷移先の URL をブラウザのアドレスバーに示すなどの処理を行ないます。しかしサーバー側レンダリングの最中は、まだ、ブラウザ内の資源にアクセスできる状態にありません。つまり、Web ブラウザから、指定の URL のドキュメントをください、という HTTP GET 要求が届いたばかりで、その返信用の HTML 文字列を組み立てている最中、というのがサーバー側レンダリングで行なわれていることだからです。

そのような状況なのに、ブラウザの API を触ろうとする処理 = NavigationManager の NavigateTo メソッド呼び出しなどを行なってしまったことで、前述の例外に至ってしまう、という流れになります。

このような仕組みであるため、Blazor Server や Blazor SSR に限らず、Blazor WebAssembly であっても、プリレンダリングを行なっている場合は、同じ問題が発生します。

改善方法

プリレンダリングをやめる

このような例外を発生させない回避策のとしては、プリレンダリングを行なわない、という方法があります。

例えばクラシカルな Blazor Server アプリケーションの場合、フォールバック Razor Pages 内に、アプリケーションコンポーネントをレンダリングするための下記のようなタグヘルパーが記述されていると思いますが、

<component type="typeof(App)" render-mode="ServerPrerendered" />

このタグヘルパー中の render-mode 指定を、ServerPrerendered から Server に変更することで、サーバー側プリレンダリングが発生しなくなり、必ずブラウザとの対話モードが機能しはじめてから OnInitialized などのライフサイクルメソッドが実行されるようになりますので、NavigationManaget.NavigateTo も例外を起すことがなくなります (下記実装例)。

<component type="typeof(App)" render-mode="Server" />

ただし、当然のことながら、プリレンダリングをやめる以上、プリレンダリングの利点 (ページが表示されるまでの速度向上や、SEO 対策) はすべて失われます。

OnAfterRender で実行する

もうひとつの方法は、OnInitialized ライフサイクルメソッドではなく、OnAfterRender ライフサイクルメソッド内で NavigationManager.NavigateTo メソッド呼び出しを行なう、という方法があります。OnAfterRender ライフサイクルメソッドのタイミングであれば、ブラウザ上での対話モードが機能している状況であることが保証されていますので、このタイミングであれば NavigationManager.NavigateTo メソッドは正しく動作します (下記実装例)。

@page "/articles"
@inject NavigationManager NavigationManager

@code {
    protected override void OnAfterRender(bool firstRender)
    {
        if (firstRender)
        {
            this.NavigationManager.NavigateTo("./articles/1");
        }
    }
}

RendererInfo を参照する

あるいはまた、.NET 9 から利用可能になった、Razor コンポーネントのプロパティ、RenderInfo を参照し、対話モードが機能しているかどうかを if 文で判定して、OnInitialized ライフサイクルメソッド中で NavigationManager.NavigateTo を呼び出す方法も考えられます (下記実装例)。

@page "/articles"
@inject NavigationManager NavigationManager

@code {
    protected override void OnInitialized()
    {
        // 対話モードが機能している最中か?
        if (this.RendererInfo.IsInteractive)
        {
            // もしそうなら、NavigateTo しても大丈夫
            this.NavigationManager.NavigateTo("./articles/1");
        }
    }
}

まとめ

サーバー側レンダリング (プリレンダリング) の最中は、NavigationManager.NavigateTo は使えず、呼び出すと NavigationException 例外が発生します。NavigationManager.NavigateTo は、対話モードが機能しているときだけ使えるので、その点に配慮して呼び出しを工夫するのが最善かと思われます。

もっとも、何も考慮せずに、雑に OnInitialized ライフサイクルメソッド内で NavigationManager.NavigateTo を呼び出していても、それでサーバー側プリレンダリング中に例外が発生していても、実質的な害はなさそうに見えるので、それはそのままでもいいのかもしれません。ただ、もしかすると、Application Insights などの APM のログに例外が記録されるかもしれないため (未確認、コンソール出力に例外メッセージが表示されていないところを見ると記録されないかも)、その場合は、無用な例外メッセージで汚染されると思いますので、本記事で紹介したような何らかの処置はすべきではないか、と思われました。

その他、以前に投稿した以下の記事もあわせて参照されるとより理解が深まるかも知れません、ご参考までに。

7
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?