Méthodes de filtre pour les pages Razordans ASP.NET Core

Par Rick Anderson

Les filtres de page RazorIPageFilter et IAsyncPageFilter autorisent les pages Razor à exécuter le code avant et après l’exécution d’un gestionnaire de pages Razor . Les filtres de page Razor sont similaires aux filtres d’action MVC ASP.NET Core, à la différence près qu’ils ne peuvent pas être appliqués aux méthodes de gestionnaire de pages individuelles.

Filtres de page Razor :

  • Exécutent le code après la sélection d’une méthode de gestionnaire, mais avant la liaison de données.
  • Exécutent le code avant l’exécution de la méthode de gestionnaire, une fois la liaison de données terminée.
  • Exécutent le code après l’exécution de la méthode de gestionnaire.
  • Peuvent être implémentés dans une page ou globalement.
  • Ne peuvent pas être appliqués à des méthodes de gestionnaire de page spécifiques.
  • peuvent avoir des dépendances de constructeur remplies par l’injection de dépendances (DI). Pour plus d’informations, consultez ServiceFilterAttribute et TypeFilterAttribute.

Alors que les constructeurs de page et les intergiciels permettent d’exécuter le code personnalisé avant l’exécution d’une méthode de gestionnaire, seuls les filtres de page Razor permettent d’accéder à HttpContext et à la page. L’intergiciel peut accéder au HttpContext, mais pas au « contexte de page ». Les filtres ont un paramètre dérivé FilterContext qui permet d’accéder à HttpContext. Voici un exemple de filtre de page : Implémenter un attribut de filtre qui ajoute un en-tête à la réponse, ce qu’il est impossible de faire avec des constructeurs ou des middlewares. L’accès au contexte de page, qui inclut l’accès aux instances de la page et à son modèle, n’est disponible que lors de l’exécution de filtres, de gestionnaires ou du corps d’une page Razor.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Les filtres de page Razor fournissent les méthodes suivantes que vous pouvez appliquer globalement ou au niveau de la page :

  • Méthodes synchrones :

    • OnPageHandlerSelected : appelée après la sélection d’une méthode de gestionnaire, mais avant la liaison de données.
    • OnPageHandlerExecuting : appelée avant l’exécution de la méthode de gestionnaire, une fois la liaison de données terminée.
    • OnPageHandlerExecuted : appelée avant l’exécution de la méthode de gestionnaire, avant le résultat de l’action.
  • Méthodes asynchrones :

    • OnPageHandlerSelectionAsync : appelée de manière asynchrone après la sélection d’une méthode de gestionnaire, mais avant la liaison de données.
    • OnPageHandlerExecutionAsync : appelée de manière asynchrone avant l’appel de la méthode de gestionnaire, une fois la liaison de modèle terminée.

Implémentez la version synchrone ou bien la version asynchrone d’une interface de filtre, mais pas les deux. Le framework vérifie d’abord si le filtre implémente l’interface asynchrone et, le cas échéant, il appelle cette interface. Dans le cas contraire, il appelle la ou les méthodes de l’interface synchrone. Si les deux interfaces sont implémentées, seules les méthodes asynchrones sont appelées. La même règle s’applique aux substitutions dans les pages : implémentez la version synchrone ou asynchrone de la substitution, mais pas les deux.

Implémenter des filtres de page Razor globalement

Le code suivant implémente 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();
    }
}

Dans le code précédent, ProcessUserAgent.Write est le code fourni par l’utilisateur qui fonctionne avec la chaîne de l’agent utilisateur.

Le code suivant active le filtre SampleAsyncPageFilter dans la classe Startup :

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

Le code suivant appelle AddFolderApplicationModelConvention pour appliquer le SampleAsyncPageFilter uniquement aux pages dans /Movies :

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

Le code suivant implémente le filtre IPageFilter synchrone :

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.");
    }
}

Le code suivant active le filtre SamplePageFilter :

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

Implémenter des filtres de page Razor en remplaçant les méthodes de filtre

Le code suivant remplace les filtres de page Razorasynchrones :

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

Implémenter un attribut de filtre

Le filtre intégré à base d’attribut OnResultExecutionAsync peut être sous-classé. Le filtre suivant ajoute un en-tête à la réponse :

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

Le code suivant applique l’attribut AddHeader :

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

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

        }
    }
}

Utilisez un outil tel que les outils de développement du navigateur pour examiner les en-têtes. Sous En-têtes de réponse, author: Rick s’affiche.

Pour obtenir des instructions sur le remplacement de l’ordre, consultez Remplacement de l’ordre par défaut.

Pour obtenir des instructions sur le court-circuitage du pipeline de filtres à partir d’un filtre, consultez Annulation et court-circuit.

Attribut de filtre Authorize

L’attribut Authorize peut être appliqué à 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();
    }
}

Par Rick Anderson

Les filtres de page RazorIPageFilter et IAsyncPageFilter autorisent les pages Razor à exécuter le code avant et après l’exécution d’un gestionnaire de pages Razor . Les filtres de page Razor sont similaires aux filtres d’action MVC ASP.NET Core, à la différence près qu’ils ne peuvent pas être appliqués aux méthodes de gestionnaire de pages individuelles.

Filtres de page Razor :

  • Exécutent le code après la sélection d’une méthode de gestionnaire, mais avant la liaison de données.
  • Exécutent le code avant l’exécution de la méthode de gestionnaire, une fois la liaison de données terminée.
  • Exécutent le code après l’exécution de la méthode de gestionnaire.
  • Peuvent être implémentés dans une page ou globalement.
  • Ne peuvent pas être appliqués à des méthodes de gestionnaire de page spécifiques.

Le code peut être exécuté avant l’exécution d’une méthode de gestionnaire à l’aide du constructeur de page ou d’un intergiciel, mais seuls les filtres de page Razor ont accès à HttpContext. Les filtres ont un paramètre dérivé FilterContext qui permet d’accéder à HttpContext. Par exemple, l’exemple Implémenter un attribut de filtre ajoute un en-tête à la réponse, ce qu’il est impossible de faire avec des constructeurs ou des middlewares.

Affichez ou téléchargez l’exemple de code (procédure de téléchargement)

Les filtres de page Razor fournissent les méthodes suivantes que vous pouvez appliquer globalement ou au niveau de la page :

  • Méthodes synchrones :

    • OnPageHandlerSelected : appelée après la sélection d’une méthode de gestionnaire, mais avant la liaison de données.
    • OnPageHandlerExecuting : appelée avant l’exécution de la méthode de gestionnaire, une fois la liaison de données terminée.
    • OnPageHandlerExecuted : appelée avant l’exécution de la méthode de gestionnaire, avant le résultat de l’action.
  • Méthodes asynchrones :

    • OnPageHandlerSelectionAsync : appelée de manière asynchrone après la sélection d’une méthode de gestionnaire, mais avant la liaison de données.
    • OnPageHandlerExecutionAsync : appelée de manière asynchrone avant l’appel de la méthode de gestionnaire, une fois la liaison de modèle terminée.

Notes

Implémentez la version synchrone ou bien la version asynchrone d’une interface de filtre, mais pas les deux. Le framework vérifie d’abord si le filtre implémente l’interface asynchrone et, le cas échéant, il appelle cette interface. Dans le cas contraire, il appelle la ou les méthodes de l’interface synchrone. Si les deux interfaces sont implémentées, seules les méthodes asynchrones sont appelées. La même règle s’applique aux substitutions dans les pages : implémentez la version synchrone ou asynchrone de la substitution, mais pas les deux.

Implémenter des filtres de page Razor globalement

Le code suivant implémente 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();
        }
    }
}

Dans le code précédent, ILogger n’est pas obligatoire. Il est utilisé dans l’exemple pour fournir des informations de trace pour l’application.

Le code suivant active le filtre SampleAsyncPageFilter dans la classe Startup :

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

Le code suivant montre l’intégralité de la classe 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();
        }
    }
}

Le code suivant appelle AddFolderApplicationModelConvention pour appliquer le filtre SampleAsyncPageFilter uniquement aux pages dans /subFolder :

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

Le code suivant implémente le filtre IPageFilter synchrone :

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.");
        }
    }
}

Le code suivant active le filtre SamplePageFilter :

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

Implémenter des filtres de page Razor en remplaçant les méthodes de filtre

Le code suivant remplace les filtres de page Razorsynchrones :

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

Implémenter un attribut de filtre

Le filtre intégré à base d’attribut OnResultExecutionAsync peut être sous-classé. Le filtre suivant ajoute un en-tête à la réponse :

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

Le code suivant applique l’attribut 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;
    }
}

Pour obtenir des instructions sur le remplacement de l’ordre, consultez Remplacement de l’ordre par défaut.

Pour obtenir des instructions sur le court-circuitage du pipeline de filtres à partir d’un filtre, consultez Annulation et court-circuit.

Attribut de filtre Authorize

L’attribut Authorize peut être appliqué à 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();
    }
}