ASP.NET Core の Razor Pages のフィルター メソッド

作成者: Rick Anderson

Razor ページ フィルター IPageFilterIAsyncPageFilter を使うと、Razor Pages で Razor ページ ハンドラーの実行前と実行後にコードを実行できるようになります。 Razor ページ フィルターは、個々のページ ハンドラー メソッドに適用できないことを除き、ASP.NET Core MVC アクション フィルターと類似しています。

Razor ページ フィルター

  • モデルのバインドが行われる前の、ハンドラー メソッドが選択された後にコードを実行します。
  • モデルのバインドの完了後の、ハンドラー メソッドの実行前にコードを実行します。
  • ハンドラー メソッドの実行後にコードを実行します。
  • ページまたはグローバルに実装できます。
  • 特定のページ ハンドラー メソッドには適用できません。
  • コンストラクターの依存関係は、依存関係の挿入 (DI) によって入力されるようにできます。 詳細については、「ServiceFilterAttribute」と「TypeFilterAttribute」を参照してください。

ページ コンストラクターとミドルウェアにより、ハンドラー メソッドが実行される前にカスタム コードの実行が可能になりますが、HttpContext とページへのアクセスを可能にするのは Razor ページ フィルターのみです。 ミドルウェアは HttpContext にアクセスできますが、"ページ コンテキスト" にはアクセスできません。 フィルターには、HttpContext へのアクセスを提供する派生型のパラメーター FilterContext があります。 ページ フィルターの例を次に示します。応答にヘッダーが追加するフィルター属性を実装する。これは、コンストラクターやミドルウェアでは実行できません。 ページとそのモデルのインスタンスへのアクセスを含む、ページ コンテキストへのアクセスは、フィルター、ハンドラー、Razor ページの本文を実行するときにのみ利用できます。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

Razor ページ フィルターには、グローバルまたはページ レベルで適用できる次のメソッドがあります。

  • 同期メソッド:

    • OnPageHandlerSelected:ハンドラー メソッドが選択された後の、モデル バインドが行われる前に呼び出されます。
    • OnPageHandlerExecuting:ハンドラー メソッドが実行される前の、モデル バインドが完了した後に呼び出されます。
    • OnPageHandlerExecuted:ハンドラー メソッドが実行された後の、アクションの結果の前に呼び出されます。
  • 非同期メソッド:

    • OnPageHandlerSelectionAsync:ハンドラー メソッドが選択された後の、モデル バインドが行われる前に非同期で呼び出されます。
    • OnPageHandlerExecutionAsync:ハンドラー メソッドが呼び出される前の、モデル バインドの完了後に非同期で呼び出されます。

フィルター インターフェイスの同期と非同期バージョンの両方ではなくいずれかを実装します。 フレームワークは、最初にフィルターが非同期インターフェイスを実装しているかどうかをチェックして、している場合はそれを呼び出します。 していない場合は、同期インターフェイスのメソッドを呼び出します。 両方のインターフェイスを実装した場合、非同期メソッドのみが呼び出されます。 ページのオーバーライドでもこの規則は同じです。オーバーライドの同期バージョンまたは非同期バージョンを実装でき、両方はできません。

Razor ページにフィルターをグローバルに実装する

IAsyncPageFilter は、次のコードによって実装されます。

public class SampleAsyncPageFilter : IAsyncPageFilter
{
    private readonly IConfiguration _config;

    public SampleAsyncPageFilter(IConfiguration config)
    {
        _config = config;
    }

    public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
    {
        var key = _config["UserAgentID"];
        context.HttpContext.Request.Headers.TryGetValue("user-agent",
                                                        out StringValues value);
        ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,
                               "SampleAsyncPageFilter.OnPageHandlerSelectionAsync",
                               value, key.ToString());

        return Task.CompletedTask;
    }

    public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context,
                                                  PageHandlerExecutionDelegate next)
    {
        // Do post work.
        await next.Invoke();
    }
}

上記のコードでは、ProcessUserAgent.Write が、ユーザー エージェント文字列を使用して機能するユーザー指定のコードです。

次のコードは、Startup クラスで SampleAsyncPageFilter を有効にします。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.Filters.Add(new SampleAsyncPageFilter(Configuration));
        });
}

次のコードでは、AddFolderApplicationModelConvention を呼び出して、 /Movies 内のページにのみ SampleAsyncPageFilter を適用しています。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages(options =>
    {
        options.Conventions.AddFolderApplicationModelConvention(
            "/Movies",
            model => model.Filters.Add(new SampleAsyncPageFilter(Configuration)));
    });
}

次のコードは、同期 IPageFilter を実装します。

public class SamplePageFilter : IPageFilter
{
    private readonly IConfiguration _config;

    public SamplePageFilter(IConfiguration config)
    {
        _config = config;
    }

    public void OnPageHandlerSelected(PageHandlerSelectedContext context)
    {
        var key = _config["UserAgentID"];
        context.HttpContext.Request.Headers.TryGetValue("user-agent", out StringValues value);
        ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,
                               "SamplePageFilter.OnPageHandlerSelected",
                               value, key.ToString());
    }

    public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
    {
        Debug.WriteLine("Global sync OnPageHandlerExecuting called.");
    }

    public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
    {
        Debug.WriteLine("Global sync OnPageHandlerExecuted called.");
    }
}

次のコードは、SamplePageFilter を有効にします。

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages()
        .AddMvcOptions(options =>
        {
            options.Filters.Add(new SamplePageFilter(Configuration));
        });
}

フィルター メソッドをオーバーライドして Razor ページにフィルターを実装する

次のコードでは、非同期 Razor ページ フィルターをオーバーライドしています。

public class IndexModel : PageModel
{
    private readonly IConfiguration _config;

    public IndexModel(IConfiguration config)
    {
        _config = config;
    }

    public override Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
    {
        Debug.WriteLine("/IndexModel OnPageHandlerSelectionAsync");
        return Task.CompletedTask;
    }

    public async override Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, 
                                                           PageHandlerExecutionDelegate next)
    {
        var key = _config["UserAgentID"];
        context.HttpContext.Request.Headers.TryGetValue("user-agent", out StringValues value);
        ProcessUserAgent.Write(context.ActionDescriptor.DisplayName,
                               "/IndexModel-OnPageHandlerExecutionAsync",
                                value, key.ToString());

        await next.Invoke();
    }
}

フィルター属性を実装する

組み込みの属性ベースのフィルターである OnResultExecutionAsync フィルターは、サブクラス化できます。 次のフィルターは、応答にヘッダーを追加します。

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;

namespace PageFilter.Filters
{
    public class AddHeaderAttribute  : ResultFilterAttribute
    {
        private readonly string _name;
        private readonly string _value;

        public AddHeaderAttribute (string name, string value)
        {
            _name = name;
            _value = value;
        }

        public override void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(_name, new string[] { _value });
        }
    }
}

次のコードは、AddHeader 属性を追加します。

using Microsoft.AspNetCore.Mvc.RazorPages;
using PageFilter.Filters;

namespace PageFilter.Movies
{
    [AddHeader("Author", "Rick")]
    public class TestModel : PageModel
    {
        public void OnGet()
        {

        }
    }
}

ブラウザー開発者ツールなどのツールを使用して、ヘッダーを調べます。 [応答ヘッダー] の下に author: Rick が表示されます。

順序をオーバーライドする手順については、「既定の順序のオーバーライド」を参照してください。

フィルターからフィルター パイプラインをショート サーキットする手順については、「キャンセルとショート サーキット」を参照してください。

Authorize フィルター属性

PageModelAuthorize 属性を適用できます。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace PageFilter.Pages
{
    [Authorize]
    public class ModelWithAuthFilterModel : PageModel
    {
        public IActionResult OnGet() => Page();
    }
}

作成者: Rick Anderson

Razor ページ フィルター IPageFilterIAsyncPageFilter を使うと、Razor Pages で Razor ページ ハンドラーの実行前と実行後にコードを実行できるようになります。 Razor ページ フィルターは、個々のページ ハンドラー メソッドに適用できないことを除き、ASP.NET Core MVC アクション フィルターと類似しています。

Razor ページ フィルター

  • モデルのバインドが行われる前の、ハンドラー メソッドが選択された後にコードを実行します。
  • モデルのバインドの完了後の、ハンドラー メソッドの実行前にコードを実行します。
  • ハンドラー メソッドの実行後にコードを実行します。
  • ページまたはグローバルに実装できます。
  • 特定のページ ハンドラー メソッドには適用できません。

コードは、ページ コンストラクターまたはミドルウェアを使用してハンドラー メソッドの実行前に実行できますが、HttpContext にアクセスできるのは Razor ページ フィルターのみです。 フィルターには、HttpContext へのアクセスを提供する派生型のパラメーター FilterContext があります。 たとえば、「フィルター属性を実装する」のサンプルでは、応答にヘッダーが追加されます。これは、コンストラクターやミドルウェアでは実行できません。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

Razor ページ フィルターには、グローバルまたはページ レベルで適用できる次のメソッドがあります。

  • 同期メソッド:

    • OnPageHandlerSelected:ハンドラー メソッドが選択された後の、モデル バインドが行われる前に呼び出されます。
    • OnPageHandlerExecuting:ハンドラー メソッドが実行される前の、モデル バインドが完了した後に呼び出されます。
    • OnPageHandlerExecuted:ハンドラー メソッドが実行された後の、アクションの結果の前に呼び出されます。
  • 非同期メソッド:

    • OnPageHandlerSelectionAsync:ハンドラー メソッドが選択された後の、モデル バインドが行われる前に非同期で呼び出されます。
    • OnPageHandlerExecutionAsync:ハンドラー メソッドが呼び出される前の、モデル バインドの完了後に非同期で呼び出されます。

Note

フィルター インターフェイスの同期と非同期バージョンの両方ではなく、いずれかを実装します。 フレームワークは、最初にフィルターが非同期インターフェイスを実装しているかどうかをチェックして、している場合はそれを呼び出します。 していない場合は、同期インターフェイスのメソッドを呼び出します。 両方のインターフェイスを実装した場合、非同期メソッドのみが呼び出されます。 ページのオーバーライドでもこの規則は同じです。オーバーライドの同期バージョンまたは非同期バージョンを実装でき、両方はできません。

Razor ページにフィルターをグローバルに実装する

IAsyncPageFilter は、次のコードによって実装されます。

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;

namespace PageFilter.Filters
{
    public class SampleAsyncPageFilter : IAsyncPageFilter
    {
        private readonly ILogger _logger;

        public SampleAsyncPageFilter(ILogger logger)
        {
            _logger = logger;
        }

        public async Task OnPageHandlerSelectionAsync(
                                            PageHandlerSelectedContext context)
        {
            _logger.LogDebug("Global OnPageHandlerSelectionAsync called.");
            await Task.CompletedTask;
        }

        public async Task OnPageHandlerExecutionAsync(
                                            PageHandlerExecutingContext context,
                                            PageHandlerExecutionDelegate next)
        {
            _logger.LogDebug("Global OnPageHandlerExecutionAsync called.");
            await next.Invoke();
        }
    }
}

前述のコードでは、ILogger は不要です。 これは、アプリケーションのトレース情報を提供するためにサンプルで使用されています。

次のコードは、Startup クラスで SampleAsyncPageFilter を有効にします。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(new SampleAsyncPageFilter(_logger));
    });
}

次は、完全な Startup クラスのコードです。

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PageFilter.Filters;

namespace PageFilter
{
    public class Startup
    {
        ILogger _logger;
        public Startup(ILoggerFactory loggerFactory, IConfiguration configuration)
        {
            _logger = loggerFactory.CreateLogger<GlobalFiltersLogger>();
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(options =>
            {
                options.Filters.Add(new SampleAsyncPageFilter(_logger));
            });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc();
        }
    }
}

次のコードは、AddFolderApplicationModelConvention を呼び出し、 /subFolder のページにのみ SampleAsyncPageFilter を適用します。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
       .AddRazorPagesOptions(options =>
       {
           options.Conventions.AddFolderApplicationModelConvention(
               "/subFolder",
               model => model.Filters.Add(new SampleAsyncPageFilter(_logger)));
       });
}

次のコードは、同期 IPageFilter を実装します。

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;

namespace PageFilter.Filters
{
    public class SamplePageFilter : IPageFilter
    {
        private readonly ILogger _logger;

        public SamplePageFilter(ILogger logger)
        {
            _logger = logger;
        }

        public void OnPageHandlerSelected(PageHandlerSelectedContext context)
        {
            _logger.LogDebug("Global sync OnPageHandlerSelected called.");
        }

        public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
        {
            _logger.LogDebug("Global sync PageHandlerExecutingContext called.");
        }

        public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
        {
            _logger.LogDebug("Global sync OnPageHandlerExecuted called.");
        }
    }
}

次のコードは、SamplePageFilter を有効にします。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(new SamplePageFilter(_logger));
    });
}

フィルター メソッドをオーバーライドして Razor ページにフィルターを実装する

次のコードでは、同期 Razor ページ フィルターをオーバーライドしています。

using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;

namespace PageFilter.Pages
{
    public class IndexModel : PageModel
    {
        private readonly ILogger _logger;

        public IndexModel(ILogger<IndexModel> logger)
        {
            _logger = logger;
        }
        public string Message { get; set; }

        public void OnGet()
        {
            _logger.LogDebug("IndexModel/OnGet");
        }
        
        public override void OnPageHandlerSelected(
                                    PageHandlerSelectedContext context)
        {
            _logger.LogDebug("IndexModel/OnPageHandlerSelected");          
        }

        public override void OnPageHandlerExecuting(
                                    PageHandlerExecutingContext context)
        {
            Message = "Message set in handler executing";
            _logger.LogDebug("IndexModel/OnPageHandlerExecuting");
        }


        public override void OnPageHandlerExecuted(
                                    PageHandlerExecutedContext context)
        {
            _logger.LogDebug("IndexModel/OnPageHandlerExecuted");
        }
    }
}

フィルター属性を実装する

組み込みの属性ベースのフィルターである OnResultExecutionAsync フィルターは、サブクラス化できます。 次のフィルターは、応答にヘッダーを追加します。

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;

namespace PageFilter.Filters
{
    public class AddHeaderAttribute  : ResultFilterAttribute
    {
        private readonly string _name;
        private readonly string _value;

        public AddHeaderAttribute (string name, string value)
        {
            _name = name;
            _value = value;
        }

        public override void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(_name, new string[] { _value });
        }
    }
}

次のコードは、AddHeader 属性を追加します。

[AddHeader("Author", "Rick")]
public class ContactModel : PageModel
{
    private readonly ILogger _logger;

    public ContactModel(ILogger<ContactModel> logger)
    {
        _logger = logger;
    }
    public string Message { get; set; }

    public async Task OnGetAsync()
    {
        Message = "Your contact page.";
        _logger.LogDebug("Contact/OnGet");
        await Task.CompletedTask;
    }
}

順序をオーバーライドする手順については、「既定の順序のオーバーライド」を参照してください。

フィルターからフィルター パイプラインをショート サーキットする手順については、「キャンセルとショート サーキット」を参照してください。

Authorize フィルター属性

PageModelAuthorize 属性を適用できます。

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace PageFilter.Pages
{
    [Authorize]
    public class ModelWithAuthFilterModel : PageModel
    {
        public IActionResult OnGet() => Page();
    }
}