Filters for Asp.Net Core Razor Pages

Filters have been a very powerful feature of Asp.Net. These provide ability to execute your custom workflow before and after a controller's action is executed. With introduction of Razor Pages in Asp.Net core, filters continue to provide same control as you have in Asp.Net MVC.

Asp.Net Core filters work slightly different than what you are used for Asp.Net MVC. Razor Page filters are applied to pages. You can not apply filter at action level. In a way it makes sense. Razor Pages are granular representation of actions of your MVC controllers. E.g. for a CRUD operation on some object, now you will have multiple razor pages like Create, Edit, Delete and Update. In MVC controllers, you have have 4 actions on same controller that provide these CRUD operations. So in Razor Pages, filters work at page level.

Razor Page filters implement IPageFilter or IAsyncPageFilter interface. Depending on what interface you implement, you will provide implementation for following methods.

IPageFilter

OnPageHandlerSelected(PageHandlerSelectedContext) Called after the handler method executes, before the action result executes.
OnPageHandlerExecuted(PageHandlerExecutedContext)Called after the handler method executes, before the action result executes.
OnPageHandlerExecuting(PageHandlerExecutingContext)Called before the handler method executes, after model binding is complete.

IAsyncPageFilter

OnPageHandlerExecutionAsync(PageHandlerExecutingContext, PageHandlerExecutionDelegate)Called asynchronously before the handler method is invoked, after model binding is complete.
OnPageHandlerSelectionAsync(PageHandlerSelectedContext)Called asynchronously after the handler method has been selected, but before model binding occurs.

The names and description of these methods are self explanatory. They tell you implement your code at appropriate stage of the pipe line as per your requirements.

Razor Page Filter Example

I will show you a sample implementation of Razor Page Filter from our Quality Management System (QMS) platform. The platform has very tight security requirements. Access to our QMS platform requires that each user has to provide information if they are accessing the application from a public or private device. This requires that all requests to every Razor Page has to filter requests to ensure that user has provided device trust level verification. Obvious choice is that I implement page filter that processes each request that come in Let's do a walk through on how this Razor Page is implemented.

Implement IAsyncPageFilter Interface

public class TrustedComputerCheckFilter:IAsyncPageFilter
    {
        #region Fields

        private readonly IWorkContext _workContext;
        private readonly ITenantContext _tenantContext;
        private readonly IQmsLogger _logger;
        #endregion

        public TrustedComputerCheckFilter(IWorkContext workContext,
            ITenantContext tenantContext,
            IQmsLogger logger)
        {
            this._tenantContext = tenantContext;
            this._workContext = workContext;
            this._logger = logger;
        }
        public async Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
        {
            
        }

        public async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
        {
            // Do not perform any checks for anonymous calls.
            if (context.HttpContext.User.Identity.IsAuthenticated)
            {
                // Don't redirect for verify page itself. Otherwise you will end up in
                // an infinite redirect loop. That will ultimately throw an error.
                if (!context.ActionDescriptor.DisplayName.Contains("VerifyDeviceTrustLevel",
                    StringComparison.OrdinalIgnoreCase))
                {
                    // Check if user's device has already provided trust
                    // information or not.
                    var deviceVerified = false; //Hard wired for sample
                    if (!deviceVerified) context.Result = new RedirectToPageResult("/Security/VerifyDeviceTrustLevel");
                }
            }
            else
            {
                await next.Invoke();
            }
        }
    }

As you can see how easy it is to implement a Razor Page filter. Above implementation also shows that you can easily use Dependency Injection (DI) in your filter. This was a big issue in MVC filter implementations.

Register Razor Page Filter

Next step is to register your page filter in Asp.Net Core pipe line. Since we have implemented a global page filter, it will need to be registered when adding MVC in your Asp.Net core pipe line. Following code shows how we added our global Razor Page filter to MVC options.

public void RegisterFilters(MvcOptions options)
{
     options.Filters.Add(typeof(TrustedComputerCheckFilter));
}

My code is abstracted to implement various Asp.Net Core pipe line's service injections. That is the reason above implementation looks different from what you may see in lot of sample codes. Following snippet shows you how this looks like when code is not abstracted.

services.AddMvc().AddMvcOptions(options =>
{
 // Register globaal filters here...
});

Very simple and powerful use of Asp.Net Core Razor Page filters. One of the concepts you see in my sample global filter is how to short circuit filter pipe line. It is very straightforward. If you assign a valid ActionResult object to Result property of PageHandlerExecutingContext, pipe line will stop execution at that handler only.

comments powered by Disqus

Blog Tags