ASP.NET Web Forms アプリで登録・パスワード再取得のためのメール送信機能を利用する場合、テンプレートで実装されたコードを使うとメール本文に含まれる確認先の URL にアプリケーション名が含まれないという注意事項を書きます。(結果、メール本文に張られたリンクの URL が正しくないのでクリックしても確認できません)
.NET Framework 版の ASP.NET Web Forms の Web アプリケーションプロジェクトを Visual Studio 2019 のテンプレートを使って、ユーザー認証に「個別のユーザーアカウント」(ASP.NET Identity) を選んで作った場合の話です。ASP.NET MVC5 ではこの問題はありません。
元の話は Teratail のスレッド「リセット画面のURLをアプリケーションルートにしたい」のものです。そのスレッドを見て初めてそういう問題があることを知りました。
メール本文に含めて送信する確認先の URL は、Models/IdentityModels.cs に含まれるヘルパーメソッド GetUserConfirmationRedirectUrl と GetResetPasswordRedirectUrl で Uri(Uri, String) コンストラクタを使って取得しています。
Uri(Uri, String) コンストラクタの第 1 引数に HttpRequest.Url プロパティで取得する Uri を、第 2 引数に "/Account/..." から始まる相対 URL の文字列を設定して Uri オブジェクトを作り、それから AbsoluteUri プロパティを使って絶対 URL を取得して確認メールの本文に張り付けています。
この記事の一番上のデバッグ画像を見てください。問題のヘルパーメソッド GetUserConfirmationRedirectUrl, GetResetPasswordRedirectUrl ではないですが、簡単に問題を再現するために Page_Load メソッドに同等のコードを書いて検証してみました。
なお、Visual Studio (IIS Express) では以下のように Request.Uri にアプリケーション名 myapp が含まれるよう[プロジェクトの URL (J)]を設定しています。
上のデバッグ画像のとおり、ローカル変数の rquestUri にはアプリケーション名 myapp を含めた Uri オブジェクトを取得できています。ローカル変数 callbackUrl が確認先の URL になりますが、アプリケーション名 myapp は含まれません。これが問題です。
ちなみに、ASP.NET MVC アプリでは Url.Action を使ってアプリケーション名を含めた絶対 URL を取得していますのでこういう問題はないです。
対処方法として、ヘルパーメソッド GetUserConfirmationRedirectUrl と GetResetPasswordRedirectUrl 内にアプリケーション名をハードコーディングするのは好ましくはなさそうです。アプリケーション名の変更、アプリの移植で問題となりますので。
ルート演算子 (~) と VirtualPathUtility.ToAbsolute メソッドを利用して "~" に相当するパスを取得するのがよさそうです。具体例は以下の画像を見てください。
この記事の例のように、アプリケーション名が myapp の場合、VirtualPathUtility.ToAbsolute メソッドの引数に "~" を設定すると戻り値は "/myapp" になります。引数に "~/Account/ResetPassword" を設定すると(文字列先頭に "~" を付与している点に注意)戻り値は "/myapp/Account/ResetPassword" となります。以下の画像を見てください。
このようにすればアプリケーション名をハードコーディングしなくて済みます。
その他のパス設定の問題
テンプレートで生成されたコードのパス設定の問題は他にもあって、例えば Account/Manage.aspx ページでも以下のようにリンク先の URL にルート演算子 (~) が設定されてないです。
リンクをクリックしても、普通はパスが通らないので、404 Not Found エラーとなってすぐ気が付くと思うかもしれませんが、開発環境ですと訳が分からないサーバーエラーになることがあるかもしれません。
実は、上の画像にあるように Visual Studio で[プロジェクトの URL (J)]を設定したのですが、その際 IIS Express 用の applicationHost.config 設定で application path="/" と application path="/myapp" が両方有効になって、/Account/ManagePassword.aspx に遷移できてしまったのです。
認証チケットは /mayapp/Acount/Login.aspx で取得しているのですが、/Account/ManagePassword.aspx では認証されないので User.Identity.GetUserId() でユーザー ID を取得できず訳が分からないサーバーエラーになりました。
これには半日ぐらい悩まされました。どうも Microsoft としては Web Forms アプリには力が入ってなくて、テンプレートで生成されるコードでも 100% 信頼できないような感じがします。