Методы фильтрации для Razor Pages в ASP.NET Core

Автор: Рик Андерсон (Rick Anderson)

Фильтры Razor Pages IPageFilter и IAsyncPageFilter разрешают Razor Pages выполнять код до и после запуска обработчика Razor Pages. Фильтры страницы Razor похожи на фильтры действий MVC ASP.NET Core, но их нельзя применять к методам обработчика отдельной страницы.

Фильтры страниц Razor:

  • Запускают код после выбора метода обработчика, но до привязки модели.
  • Запускают код до выполнения метода обработчика, но после привязки модели.
  • Запускают код после выполнения метода обработчика.
  • Могут реализовываться глобально или на странице.
  • Не могут применяться к методам обработчика для конкретной страницы.
  • Могут иметь зависимости конструктора, заполняемые путем внедрения зависимостей. Дополнительные сведения см. в описаниях атрибутов ServiceFilterAttribute и TypeFilterAttribute.

Хотя конструкторы страниц и ПО промежуточного слоя позволяют выполнять пользовательский код до выполнения метода обработчика, только фильтры страницы Razor предоставляют доступ к HttpContext и странице. ПО промежуточного слоя имеет доступ к HttpContext, но не к контексту страницы. Фильтры имеют производный параметр FilterContext, который предоставляет доступ к HttpContext. Ниже приведен пример фильтра страницы: реализуйте атрибут фильтра, который добавляет заголовок в ответ, что-то, что невозможно сделать с конструкторами или по промежуточному слоям. Доступ к контексту страницы, который включает в себя доступ к экземплярам страницы и ее модели, возможен только при выполнении фильтров, обработчиков или текста страницы 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 — это пользовательский код, который работает со строкой агента пользователя.

В следующем коде включается SampleAsyncPageFilter в классе Startup:

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.

Инструкции по переопределению порядка см. в разделе Переопределение порядка по умолчанию.

Инструкции по замыканию конвейера фильтров из фильтра см. в разделе Отмена и замыкание.

Атрибут фильтра Authorize

Атрибут Authorize можно применить к 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 Pages IPageFilter и IAsyncPageFilter разрешают Razor Pages выполнять код до и после запуска обработчика Razor Pages. Фильтры страницы Razor похожи на фильтры действий MVC ASP.NET Core, но их нельзя применять к методам обработчика отдельной страницы.

Фильтры страниц Razor:

  • Запускают код после выбора метода обработчика, но до привязки модели.
  • Запускают код до выполнения метода обработчика, но после привязки модели.
  • Запускают код после выполнения метода обработчика.
  • Могут реализовываться глобально или на странице.
  • Не могут применяться к методам обработчика для конкретной страницы.

Код можно запустить перед выполнением метода обработчика с помощью конструктора страницы или ПО промежуточного слоя, но только Razor фильтры страниц имеют доступ к HttpContext. Фильтры имеют производный параметр FilterContext, который предоставляет доступ к HttpContext. Например, образец Применение атрибута фильтра добавляет заголовок к ответу. Это невозможно сделать с помощью конструкторов или ПО промежуточного слоя.

Просмотреть или скачать образец кода (описание загрузки)

Фильтры страницы 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 не требуется. Он используется в образце для предоставления сведений о трассировке для приложения.

В следующем коде включается SampleAsyncPageFilter в классе Startup:

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

Инструкции по переопределению порядка см. в разделе Переопределение порядка по умолчанию.

Инструкции по замыканию конвейера фильтров из фильтра см. в разделе Отмена и замыкание.

Атрибут фильтра Authorize

Атрибут Authorize можно применить к 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();
    }
}