アプリケーションルートがドメインルートにない場合のリンクの動作と実装方法 - ASP.NET

アプリケーションルートがドメインルートにない場合のリンクの動作と実装方法を紹介します。

概要

アプリケーションのルートがサーバーのルートでない場合、アプリケーションルートのurlの末尾に "/" が無い場合に意図しない動作になる場合があります。
この記事では現象を確認しつつ、対処方法を紹介します。

現象の確認

RazorPagesのアプリケーションで以下のアプリケーションを作成します。
Main1.cshtml
@page "/"
@model TrailingSlash.Pages.Test1Model
@{
}
<html>
  <head>
  </head>
  <body>
    <h2>テストです。</h2>
    <a href="sub/Sub1">リンク1</a><br/>
    <a href="/sub/Sub1">リンク2</a><br/>
    <a href="./sub/Sub1">リンク3</a><br/>
    <a href="/myapp/sub/Sub1">リンク4</a><br/>
  </body>
</html>
Sub1.cshtml
@page "/sub/Sub1"
@model TrailingSlash.Pages.Sub1Model
@{
}
<html>
  <head>
  </head>
  <body>
    <p>テストです。</p>
  </body>
</html>
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;

namespace TrailingSlash
{
  public class Startup
  {
    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
      }

      app.UseRouting();

      app.UseEndpoints(endpoints =>
      {
        endpoints.MapRazorPages();
      });
    }
  }
}

解説

Main1.cshtml@page "/" を記述してアプリケーションルートのページとします。
Sub1.cshtml@page "/sub/Sub1" を記述し https://(アプリケーションルート)/sub/Sub1` のURLのページとします。

Main1.cshtmlに Sub1 ページへのリンクを設置し動作を確認します。

動作結果

アプリケーションURLがドメインルートでない場合

アプリケーションのURLを https://(ドメインルート)/myapp とします。


https://(ドメインルート)/myapp の場合
プロジェクトを実行し、Webブラウザでhttps://(ドメインルート)/myappのURLを開きます。Main1.cshtmlのページが表示されます。それぞれのリンクの動作を確認します。


[リンク1]をクリックします。https://(ドメインルート)/sub/Sub1 への遷移となり、NotFoundとなります。


[リンク2]をクリックします。https://(ドメインルート)/sub/Sub1 への遷移となり、NotFoundとなります。


[リンク3]をクリックします。https://(ドメインルート)/sub/Sub1 への遷移となり、NotFoundとなります。


[リンク4]をクリックします。https://(ドメインルート)/myapp/sub/Sub1 への遷移となり、Sub1 のページが表示されます。

https://(ドメインルート)/myapp/ の場合
プロジェクトを実行し、Webブラウザでhttps://(ドメインルート)/myapp/のURLを開きます。URLの末尾に"/"を追加するとどのように動作が変わるかを確認します。 Main1.cshtmlのページが表示されます。それぞれのリンクの動作を確認します。


[リンク1]をクリックします。https://(ドメインルート)/myapp/sub/Sub1 への遷移となり、Sub1のページが表示されます。


[リンク2]をクリックします。https://(ドメインルート)/sub/Sub1 への遷移となり、NotFoundとなります。


[リンク3]をクリックします。https://(ドメインルート)/myapp/sub/Sub1 への遷移となり、Sub1のページが表示されます。


[リンク4]をクリックします。https://(ドメインルート)/myapp/sub/Sub1 への遷移となり、Sub1のページが表示されます。


URLの末尾に"/" (トレイリングスラッシュ) があるかないかでリンクの動作が変わることが確認できました。

アプリケーションURLがドメインルートの場合

アプリケーションのURLを https://(ドメインルート) とします。


プロジェクトを実行し、Webブラウザでhttps://(ドメインルート)のURLを開きます。Main1.cshtmlのページが表示されます。それぞれのリンクの動作を確認します。


[リンク1]をクリックします。https://(ドメインルート)/sub/Sub1 への遷移となり、Sub1のページが表示されます。


[リンク2]をクリックします。https://(ドメインルート)/sub/Sub1 への遷移となり、Sub1のページが表示されます。


[リンク3]をクリックします。https://(ドメインルート)/sub/Sub1 への遷移となり、Sub1のページが表示されます。


[リンク4]をクリックします。https://(ドメインルート)/myapp/sub/Sub1 への遷移となり、HTTP Error 500.35のページが表示されます。


アプリケーションルートが、ドメイン直下かそうでないかでリンクの動作が変わることが確認できました。末尾に"/"が付くことで、相対URLが正しいリンク先に遷移できるようになります。

対処法1 : "/" を付加するURLにリダイレクトする

Main1.cshtml のページモデルクラスを下記のコードに変更します。
Main1.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace TrailingSlash.Pages
{
  public class Test1Model : PageModel
  {
    public void OnGet()
    {
      if (Request.Path.Value == "") {
        Response.Redirect("/myapp/");
      }
    }
  }
}

アプリケーションのディレクトリ名をコードに記述したくない場合は、~ を利用する下記のコードでも同じ動作にできます。
Main1.cshtml.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace TrailingSlash.Pages
{
  public class Test1Model : PageModel
  {
    public void OnGet()
    {
      if (Request.Path.Value == "") {
        Response.Redirect(Url.Content("~/"));
      }
    }
  }
}

実行結果

プロジェクトを実行し、https://(ドメインルート)/myapp のURLにアクセスすると、https://(ドメインルート)/myapp/ にリダイレクトされ、末尾に "/" (トレイリングスラッシュ)が付いたURLになります。

対処法2 : asp-page 属性を利用する

asp-page 属性を利用してRazorPage名を指定してリンクを実行すると、URLの末尾の "/" に関係なく正しいURLに遷移できます。

コード

Main1.cshtmlのコードを下記に変更します。
Main1.cshtml
@page "/"
@model TrailingSlash.Pages.Test1Model
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
}
<html>
<head>
</head>
<body>
  <h2>テストです。</h2>
  <a asp-page="/Sub1">リンク1</a><br />
</body>
</html>

解説

a タグのリンクのhref 属性を使わずに asp-page 属性を利用します。属性の値には 遷移する RazorPage のページ名を指定します。 asp-page の動作の詳細についてはこちらの記事も参照してください。

実行結果

プロジェクトを実行します。https://(ドメインルート)/myapp のURLにアクセスします。下図のページが表示されます。
[リンク1]をクリックします。


https://(ドメインルート)/myapp/sub/Sub1 に遷移し、Sub1のRazorPageが表示できます。


https://(ドメインルート)/myapp/ 末尾に"/"を追加したURLにアクセスします。下図のページが表示されます。
[リンク1]をクリックします。


https://(ドメインルート)/myapp/sub/Sub1 に遷移し、Sub1のRazorPageが表示できます。


どちらのURLでもNotFoundにならずにページに遷移できることが確認できます。

対処法3 : asp-route 属性を利用する

asp-route 属性を利用してRazorPage名を指定してリンクを実行すると、URLの末尾の "/" に関係なく正しいURLに遷移できます。

コード

Startup.csとMain1.cshtmlのコードを下記に変更します。
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Routing;

namespace TrailingSlash
{
  public class Startup
  {
    // This method gets called by the runtime. Use this method to add services to the container.
    // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
    public void ConfigureServices(IServiceCollection services)
    {
      services.AddRazorPages();
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
      }

      app.UseRouting();

      app.UseEndpoints(endpoints =>
      {
        endpoints.MapControllerRoute(
          name: "route001",
          pattern: "/sub/Sub1");

        endpoints.MapRazorPages();
      });
    }
  }
}
Main1.cshtml
@page "/"
@model TrailingSlash.Pages.Test1Model
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
}
<html>
<head>

</head>
<body>
  <h2>テストです。</h2>
  <a asp-route="route001">リンク1</a><br/>
</body>
</html>

解説

Starup.cs ファイルのUseEndpointsメソッド内に、名前付きのルーティングを作成します。ルーティングの追加は MapControllerRoute()メソッドを呼び出して追加します。
下記のコードでは、route001 という名称のルーティングを追加しています。
  app.UseEndpoints(endpoints =>
  {
    endpoints.MapControllerRoute(
      name: "route001",
      pattern: "/sub/Sub1");

    endpoints.MapRazorPages();
  });

RazorPage側では、aタグのリンクをhref属性ではなく、asp-route 属性で記述します。asp-route属性の値にStartup.csファイルのコードで追加したルーティングの名称を指定しています。
  <a asp-route="route001">リンク1</a><br/>

実行結果

プロジェクトを実行します。https://(ドメインルート)/myapp のURLにアクセスします。下図のページが表示されます。
[リンク1]をクリックします。


https://(ドメインルート)/myapp/sub/Sub1 に遷移し、Sub1のRazorPageが表示できます。


https://(ドメインルート)/myapp/ 末尾に"/"を追加したURLにアクセスします。下図のページが表示されます。
[リンク1]をクリックします。


https://(ドメインルート)/myapp/sub/Sub1 に遷移し、Sub1のRazorPageが表示できます。


どちらのURLでもNotFoundにならずにページに遷移できることが確認できます。
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2021-10-27
作成日: 2021-10-26
iPentec all rights reserverd.