ASP.NET Core에서 Razor Pages를 위한 필터 메서드

작성자: Rick Anderson

Razor 페이지 필터 IPageFilterIAsyncPageFilter는 Razor Pages가 Razor 페이지 처리기 실행 전후에 코드를 실행하도록 허용합니다. Razor 페이지 필터는 개별 페이지 처리기 메서드에 적용할 수 없다는 점을 제외하고는 ASP.NET Core MVC 작업 필터와 유사합니다.

Razor 페이지 필터:

  • 처리기 메서드를 선택한 후 모델 바인딩이 발생하기 전에 코드를 실행합니다.
  • 모델 바인딩이 완료된 후 처리기 메서드가 실행되기 전에 코드를 실행합니다.
  • 처리기 메서드가 실행된 후 코드를 실행합니다.
  • 한 페이지에 또는 전역으로 구현할 수 있습니다.
  • 특정 페이지 처리기 메서드에는 적용할 수 없습니다.
  • 생성자 종속성이 DI(종속성 주입)를 통해서 채워질 수 있습니다. 자세한 내용은 ServiceFilterAttributeTypeFilterAttribute를 참조하세요.

페이지 생성자 및 미들웨어는 처리기 메서드가 실행되기 전에 사용자 지정 코드를 실행할 수 있지만 Razor 페이지 필터를 통해서만 HttpContext 및 페이지에 액세스할 수 있습니다. 미들웨어는 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을 호출하여 SampleAsyncPageFilter/Movies의 페이지에만 적용합니다.

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이 표시됩니다.

순서 재정의에 대한 지침은 기본 순서 재정의를 참조하세요.

필터에서 필터 파이프라인을 단락(short-circuit)시키는 지침은 취소 및 단락을 참조하세요.

권한 부여 필터 특성

권한 부여 특성은 PageModel에 적용될 수 있습니다.

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 페이지 필터:

  • 처리기 메서드를 선택한 후 모델 바인딩이 발생하기 전에 코드를 실행합니다.
  • 모델 바인딩이 완료된 후 처리기 메서드가 실행되기 전에 코드를 실행합니다.
  • 처리기 메서드가 실행된 후 코드를 실행합니다.
  • 한 페이지에 또는 전역으로 구현할 수 있습니다.
  • 특정 페이지 처리기 메서드에는 적용할 수 없습니다.

페이지 생성자 또는 미들웨어를 사용하여 처리기 메서드를 실행하기 전에 코드를 실행할 수 있지만 페이지 필터만 Razor 액세스할 수 HttpContext있습니다. 필터에는 HttpContext에 대한 액세스를 제공하는 FilterContext 파생 매개 변수가 있습니다. 예를 들어 필터 특성 구현 샘플은 생성자 또는 미들웨어로 수행할 수 없는 응답에 헤더를 추가합니다.

샘플 코드 보기 및 다운로드(다운로드 방법)

Razor 페이지 필터는 전역 또는 페이지 수준에서 적용할 수 있는 다음과 같은 메서드를 제공합니다.

  • 동기 메서드:

    • OnPageHandlerSelected : 처리기 메서드를 선택한 후 모델 바인딩이 발생하기 전에 호출됩니다.
    • OnPageHandlerExecuting : 모델 바인딩이 완료된 후 처리기 메서드가 실행되기 전에 호출됩니다.
    • OnPageHandlerExecuted : 처리기 메서드가 실행된 후 작업 결과 전에 호출됩니다.
  • 비동기 메서드:

    • OnPageHandlerSelectionAsync : 처리기 메서드를 선택한 후 모델 바인딩이 발생하기 전에 비동기적으로 호출됩니다.
    • OnPageHandlerExecutionAsync : 모델 바인딩이 완료된 후 처리기 메서드가 호출되기 전에 비동기적으로 호출됩니다.

참고 항목

필터 인터페이스의 동기 또는 비동기 버전 중 하나를 구현합니다. 프레임워크는 먼저 필터가 비동기 인터페이스를 구현하는지를 확인하고 그렇다면 이를 호출합니다. 그렇지 않으면 동기 인터페이스의 메서드를 호출합니다. 두 인터페이스가 구현되는 경우 비동기 메서드만 호출됩니다. 페이지의 재정의에 동일한 규칙이 적용되며, 재정의의 동기 또는 비동기 버전 중 하나만 구현합니다.

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을 호출하여 SampleAsyncPageFilter/subFolder의 페이지에만 적용합니다.

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;
    }
}

순서 재정의에 대한 지침은 기본 순서 재정의를 참조하세요.

필터에서 필터 파이프라인을 단락(short-circuit)시키는 지침은 취소 및 단락을 참조하세요.

권한 부여 필터 특성

권한 부여 특성은 PageModel에 적용될 수 있습니다.

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();
    }
}