ASP.NET Core でリダイレクト機能を持つ Action Filter を作っていたときのこと。Controller や View で利用できる IUrlHelper
を Action Filter で利用できないことに気が付きました。Controller だと以下のように Url
プロパティで IUrlHelper
にアクセスできるので簡単ですが、Action Filter には IUrlHelper
にアクセスするためのプロパティなどがどこにもありません。これでは特定の Action に対するルーティングを考慮した URL の生成ができない!うーん、困った。
// Controller だとこう書けるから簡単だけど public class ApplicationController { [HttpGet("{market}/app/{id}")] public IActionResult Index([FromQuery]string id) { var action = "Index"; var controller = "Application"; var routeValues = new { market = "jp", id = "abc123" }; var url = this.Url.Action(action, controller, routeValues); // url : "/jp/app/abc123" } }
ドキュメントを読んでみる
IUrlHelper
がない環境下でルーティングを考慮した URL の生成ができないかと調べていたら、公式ドキュメントにヒントがありました。
- URL generation is based on addresses, which support arbitrary extensibility:
- The Link Generator API (LinkGenerator) can be resolved anywhere using dependency injection (DI) to generate URLs.
- Where the Link Generator API isn't available via DI, IUrlHelper offers methods to build URLs.
この英語の雰囲気的には以下のような感じかと思います。なるほど LinkGenerator
を DI すれば良い、と。
LinkGenerator
なる機能があり、それを DI を介して取得できるLinkGenerator
を使えないところにはIUrlHelper
が提供されている
使ってみる
簡単な認証フィルターを例に LinkGenerator
型を使ってみましょう。アカウントのサインインが必要なページに来たら認証画面にリダイレクトし、認証が終わったらページに戻す実装です。
public class AccountController { [AllowAnonymous] public IActionResult SignIn(string returnUrl = "/") { // 認証済みなら即リダイレクト if (this.User.Identity.IsAuthenticated) return this.LocalRedirect(returnUrl); // リダイレクト先を決定して認証チャレンジ var props = new AuthenticationProperties { RedirectUri = returnUrl }; return this.Challenge(props); } }
public class AuthorizationFilter : IAuthorizationFilter { private LinkGenerator LinkGenerator { get; } public AuthorizationFilter(LinkGenerator linkGenerator) => this.LinkGenerator = linkGenerator; public void OnAuthorization(AuthorizationFilterContext context) { // AllowAnonymous 属性が付いている場合はスキップ if (context.Filters.Any(x => x is IAllowAnonymousFilter)) return; // 認証済みなら何もしない if (context.HttpContext.User.Identity.IsAuthenticated) return; // 認証ページにリダイレクト var returnUrl = context.HttpContext.Request.GetEncodedPathAndQuery(); var routeValues = new { returnUrl }; var url = this.LinkGenerator.GetPathByAction(context.HttpContext, "SignIn", "Account", routeValues); context.Result = new LocalRedirectResult(url); } }
IUrlHelper
と使い方が若干違いますが、概ね同じような感覚で使えますね。LinkGenerator
のこと、ときどきでいいから...思い出してください。