xin9le.net

Microsoft の製品/技術が大好きな Microsoft MVP な管理人の技術ブログです。

LinkGenerator : ASP.NET Core の DI で利用可能な URL 生成機構

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:

この英語の雰囲気的には以下のような感じかと思います。なるほど 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 のこと、ときどきでいいから...思い出してください。