Razor Pages route and app conventions in ASP.NET Core
Learn how to use page route and app model provider conventions to control page routing, discovery, and processing in Razor Pages apps.
To specify a page route, add route segments, or add parameters to a route, use the page's @page
directive. For more information, see Custom routes.
There are reserved words that can't be used as route segments or parameter names. For more information, see Routing: Reserved routing names.
View or download sample code (how to download)
Scenario | The sample demonstrates |
---|---|
Model conventions Conventions.Add |
Add a route template and header to an app's pages. |
Page route action conventions | Add a route template to pages in a folder and to a single page. |
Page model action conventions
|
Add a header to pages in a folder, add a header to a single page, and configure a filter factory to add a header to an app's pages. |
Razor Pages conventions are configured using an AddRazorPages overload that configures RazorPagesOptions. The following convention examples are explained later in this topic:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages(options =>
{
options.Conventions.Add( ... );
options.Conventions.AddFolderRouteModelConvention(
"/OtherPages", model => { ... });
options.Conventions.AddPageRouteModelConvention(
"/About", model => { ... });
options.Conventions.AddPageRoute(
"/Contact", "TheContactPage/{text?}");
options.Conventions.AddFolderApplicationModelConvention(
"/OtherPages", model => { ... });
options.Conventions.AddPageApplicationModelConvention(
"/About", model => { ... });
options.Conventions.ConfigureFilter(model => { ... });
options.Conventions.ConfigureFilter( ... );
});
}
Route order
Routes specify an Order for processing (route matching).
Route order | Behavior |
---|---|
-1 | The route is processed before other routes are processed. |
0 | Order isn't specified (default value). Not assigning Order (Order = null ) defaults the route Order to 0 (zero) for processing. |
1, 2, … n | Specifies the route processing order. |
Route processing is established by convention:
- Routes are processed in sequential order (-1, 0, 1, 2, … n).
- When routes have the same
Order
, the most specific route is matched first followed by less specific routes. - When routes with the same
Order
and the same number of parameters match a request URL, routes are processed in the order that they're added to the PageConventionCollection.
If possible, avoid depending on an established route processing order. Generally, routing selects the correct route with URL matching. If you must set route Order
properties to route requests correctly, the app's routing scheme is probably confusing to clients and fragile to maintain. Seek to simplify the app's routing scheme. The sample app requires an explicit route processing order to demonstrate several routing scenarios using a single app. However, you should attempt to avoid the practice of setting route Order
in production apps.
Razor Pages routing and MVC controller routing share an implementation. Information on route order in the MVC topics is available at Routing to controller actions: Ordering attribute routes.
Model conventions
Add a delegate for IPageConvention to add model conventions that apply to Razor Pages.
Add a route model convention to all pages
Use Conventions to create and add an IPageRouteModelConvention to the collection of IPageConvention instances that are applied during page route model construction.
The sample app contains the GlobalTemplatePageRouteModelConvention
class to add a {globalTemplate?}
route template to all of the pages in the app:
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace SampleApp.Conventions;
public class GlobalTemplatePageRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{globalTemplate?}"),
}
});
}
}
}
In the preceding code:
- The PageRouteModel is passed to the Apply method.
- The PageRouteModel.Selectors gets the count of selectors.
- A new SelectorModel is added which contains a AttributeRouteModel
Razor Pages options, such as adding Conventions, are added when Razor Pages is added to the service collection. For an example, see the sample app.
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.EntityFrameworkCore;
using SampleApp.Conventions;
using SampleApp.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("InMemoryDb"));
builder.Services.AddRazorPages(options =>
{
options.Conventions.Add(new GlobalTemplatePageRouteModelConvention());
options.Conventions.AddFolderRouteModelConvention("/OtherPages", model =>
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{otherPagesTemplate?}"),
}
});
}
});
options.Conventions.AddPageRouteModelConvention("/About", model =>
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{aboutTemplate?}"),
}
});
}
});
});
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Consider the GlobalTemplatePageRouteModelConvention
class:
using Microsoft.AspNetCore.Mvc.ApplicationModels;
namespace SampleApp.Conventions;
public class GlobalTemplatePageRouteModelConvention : IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{globalTemplate?}"),
}
});
}
}
}
The Order property for the AttributeRouteModel is set to 1
. This ensures the following route matching behavior in the sample app:
A route template for
TheContactPage/{text?}
is added later in this topic. TheContact Page
route has a default order ofnull
(Order = 0
), so it matches before the{globalTemplate?}
route template which hasOrder = 1
.The
{aboutTemplate?}
route template is show in the preceding code. The{aboutTemplate?}
template is given anOrder
of2
. When the About page is requested at/About/RouteDataValue
, "RouteDataValue" is loaded intoRouteData.Values["globalTemplate"]
(Order = 1
) and notRouteData.Values["aboutTemplate"]
(Order = 2
) due to setting theOrder
property.The
{otherPagesTemplate?}
route template is shown in the preceding code. The{otherPagesTemplate?}
template is given anOrder
of2
. When any page in the Pages/OtherPages folder is requested with a route parameter:For example,
/OtherPages/Page1/xyz
The route data value
"xyz"
is loaded intoRouteData.Values["globalTemplate"]
(Order = 1
).RouteData.Values["otherPagesTemplate"]
with (Order = 2
) is not loaded due to theOrder
property2
having a higher value.
When possible, don't set the Order
. When Order
is not set, it defaults to Order = 0
. Rely on routing to select the correct route rather than the Order
property.
Request the sample's About
page at localhost:{port}/About/GlobalRouteValue
and inspect the result:
The sample app uses the Rick.Docs.Samples.RouteInfo NuGet package to display routing information in the logging output. Using localhost:{port}/About/GlobalRouteValue
, the logger displays the request, the Order
, and the template used:
info: SampleApp.Pages.AboutModel[0]
/About/GlobalRouteValue Order = 1 Template = About/{globalTemplate?}
Add an app model convention to all pages
Use Conventions to create and add an IPageApplicationModelConvention to the collection of IPageConvention instances that are applied during page app model construction.
To demonstrate this and other conventions later in the topic, the sample app includes an AddHeaderAttribute
class. The class constructor accepts a name
string and a values
string array. These values are used in its OnResultExecuting
method to set a response header. The full class is shown in the Page model action conventions section later in the topic.
The sample app uses the AddHeaderAttribute
class to add a header, GlobalHeader
, to all of the pages in the app:
public class GlobalHeaderPageApplicationModelConvention
: IPageApplicationModelConvention
{
public void Apply(PageApplicationModel model)
{
model.Filters.Add(new AddHeaderAttribute(
"GlobalHeader", new string[] { "Global Header Value" }));
}
}
Program.cs
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseInMemoryDatabase("InMemoryDb"));
builder.Services.AddRazorPages(options =>
{
options.Conventions.Add(new GlobalTemplatePageRouteModelConvention());
options.Conventions.Add(new GlobalHeaderPageApplicationModelConvention());
Request the sample's About page at localhost:{port}/About
and inspect the headers to view the result:
Add a handler model convention to all pages
Use Conventions to create and add an IPageHandlerModelConvention to the collection of IPageConvention instances that are applied during page handler model construction.
public class GlobalPageHandlerModelConvention
: IPageHandlerModelConvention
{
public void Apply(PageHandlerModel model)
{
// Access the PageHandlerModel
}
}
Page route action conventions
The default route model provider that derives from IPageRouteModelProvider invokes conventions which are designed to provide extensibility points for configuring page routes.
Folder route model convention
Use AddFolderRouteModelConvention to create and add an IPageRouteModelConvention that invokes an action on the PageRouteModel for all of the pages under the specified folder.
The sample app uses AddFolderRouteModelConvention to add an {otherPagesTemplate?}
route template to the pages in the OtherPages folder:
options.Conventions.AddFolderRouteModelConvention("/OtherPages", model =>
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{otherPagesTemplate?}"),
}
});
}
});
The Order property for the AttributeRouteModel is set to 2
. This ensures that the template for {globalTemplate?}
(set earlier in the topic to 1
) is given priority for the first route data value position when a single route value is provided. If a page in the Pages/OtherPages folder is requested with a route parameter value (for example, /OtherPages/Page1/RouteDataValue
), "RouteDataValue" is loaded into RouteData.Values["globalTemplate"]
(Order = 1
) and not RouteData.Values["otherPagesTemplate"]
(Order = 2
) due to setting the Order
property.
Wherever possible, don't set the Order
, which results in Order = 0
. Rely on routing to select the correct route.
Request the sample's Page1 page at localhost:5000/OtherPages/Page1/GlobalRouteValue/OtherPagesRouteValue
and inspect the result:
Page route model convention
Use AddPageRouteModelConvention to create and add an IPageRouteModelConvention that invokes an action on the PageRouteModel for the page with the specified name.
The sample app uses AddPageRouteModelConvention
to add an {aboutTemplate?}
route template to the About page:
options.Conventions.AddPageRouteModelConvention("/About", model =>
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel!.Template,
"{aboutTemplate?}"),
}
});
}
});
The Order property for the AttributeRouteModel is set to 2
. This ensures that the template for {globalTemplate?}
(set earlier in the topic to 1
) is given priority for the first route data value position when a single route value is provided. If the About page is requested with a route parameter value at /About/RouteDataValue
, "RouteDataValue" is loaded into RouteData.Values["globalTemplate"]
(Order = 1
) and not RouteData.Values["aboutTemplate"]
(Order = 2
) due to setting the Order
property.
Wherever possible, don't set the Order
, which results in Order = 0
. Rely on routing to select the correct route.
Request the sample's About page at localhost:{port}/About/GlobalRouteValue/AboutRouteValue
and inspect the result:
The logger output displays:
info: SampleApp.Pages.AboutModel[0]
/About/GlobalRouteValue/AboutRouteValue Order = 2 Template = About/{globalTemplate?}/{aboutTemplate?}
Use a parameter transformer to customize page routes
Configure a page route
Use AddPageRoute to configure a route to a page at the specified page path. Generated links to the page use the specified route. AddPageRoute uses AddPageRouteModelConvention to establish the route.
The sample app creates a route to /TheContactPage
for the Contact
Razor Page:
options.Conventions.AddPageRoute("/Contact", "TheContactPage/{text?}");
The Contact
page can also be reached at /
Contact1` via its default route.
The sample app's custom route to the Contact
page allows for an optional text
route segment ({text?}
). The page also includes this optional segment in its @page
directive in case the visitor accesses the page at its /Contact
route:
@page "{text?}"
@model ContactModel
@{
ViewData["Title"] = "Contact";
}
<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>
<address>
One Microsoft Way<br>
Redmond, WA 98052-6399<br>
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br>
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>
<p>@Model.RouteDataTextTemplateValue</p>
Note that the URL generated for the Contact link in the rendered page reflects the updated route:
Visit the Contact
page at either its ordinary route, /Contact
, or the custom route, /TheContactPage
. If you supply an additional text
route segment, the page shows the HTML-encoded segment that you provide:
Page model action conventions
The default page model provider that implements IPageApplicationModelProvider invokes conventions which are designed to provide extensibility points for configuring page models. These conventions are useful when building and modifying page discovery and processing scenarios.
For the examples in this section, the sample app uses an AddHeaderAttribute
class, which is a ResultFilterAttribute, that applies a response header:
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string[] _values;
public AddHeaderAttribute(string name, string[] values)
{
_name = name;
_values = values;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(_name, _values);
base.OnResultExecuting(context);
}
}
Using conventions, the sample demonstrates how to apply the attribute to all of the pages in a folder and to a single page.
Folder app model convention
Use AddFolderApplicationModelConvention to create and add an IPageApplicationModelConvention that invokes an action on PageApplicationModel instances for all pages under the specified folder.
The sample demonstrates the use of AddFolderApplicationModelConvention
by adding a header, OtherPagesHeader
, to the pages inside the OtherPages folder of the app:
options.Conventions.AddFolderApplicationModelConvention("/OtherPages", model =>
{
model.Filters.Add(new AddHeaderAttribute(
"OtherPagesHeader", new string[] { "OtherPages Header Value" }));
});
Request the sample's Page1 page at localhost:5000/OtherPages/Page1
and inspect the headers to view the result:
Page app model convention
Use AddPageApplicationModelConvention to create and add an IPageApplicationModelConvention that invokes an action on the PageApplicationModel for the page with the specified name.
The sample demonstrates the use of AddPageApplicationModelConvention
by adding a header, AboutHeader
, to the About page:
options.Conventions.AddPageApplicationModelConvention("/About", model =>
{
model.Filters.Add(new AddHeaderAttribute(
"AboutHeader", new string[] { "About Header Value" }));
});
Request the sample's About page at localhost:5000/About
and inspect the headers to view the result:
Configure a filter
ConfigureFilter configures the specified filter to apply. You can implement a filter class, but the sample app shows how to implement a filter in a lambda expression, which is implemented behind-the-scenes as a factory that returns a filter:
options.Conventions.ConfigureFilter(model =>
{
if (model.RelativePath.Contains("OtherPages/Page2"))
{
return new AddHeaderAttribute(
"OtherPagesPage2Header",
new string[] { "OtherPages/Page2 Header Value" });
}
return new EmptyFilter();
});
The page app model is used to check the relative path for segments that lead to the Page2 page in the OtherPages folder. If the condition passes, a header is added. If not, the EmptyFilter
is applied.
EmptyFilter
is an Action filter. Since Action filters are ignored by Razor Pages, the EmptyFilter
has no effect as intended if the path doesn't contain OtherPages/Page2
.
Request the sample's Page2 page at localhost:5000/OtherPages/Page2
and inspect the headers to view the result:
Configure a filter factory
ConfigureFilter configures the specified factory to apply filters to all Razor Pages.
The sample app provides an example of using a filter factory by adding a header, FilterFactoryHeader
, with two values to the app's pages:
options.Conventions.ConfigureFilter(new AddHeaderWithFactory());
AddHeaderWithFactory.cs
:
public class AddHeaderWithFactory : IFilterFactory
{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new AddHeaderFilter();
}
private class AddHeaderFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
"FilterFactoryHeader",
new string[]
{
"Filter Factory Header Value 1",
"Filter Factory Header Value 2"
});
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
Request the sample's About page at localhost:5000/About
and inspect the headers to view the result:
MVC Filters and the Page filter (IPageFilter)
MVC Action filters are ignored by Razor Pages, since Razor Pages use handler methods. Other types of MVC filters are available for you to use: Authorization, Exception, Resource, and Result. For more information, see the Filters topic.
The Page filter (IPageFilter) is a filter that applies to Razor Pages. For more information, see Filter methods for Razor Pages.
Additional resources
Learn how to use page route and app model provider conventions to control page routing, discovery, and processing in Razor Pages apps.
When you need to configure custom page routes for individual pages, configure routing to pages with the AddPageRoute convention described later in this topic.
To specify a page route, add route segments, or add parameters to a route, use the page's @page
directive. For more information, see Custom routes.
There are reserved words that can't be used as route segments or parameter names. For more information, see Routing: Reserved routing names.
View or download sample code (how to download)
Scenario | The sample demonstrates ... |
---|---|
Model conventions Conventions.Add
|
Add a route template and header to an app's pages. |
Page route action conventions
|
Add a route template to pages in a folder and to a single page. |
Page model action conventions
|
Add a header to pages in a folder, add a header to a single page, and configure a filter factory to add a header to an app's pages. |
Razor Pages conventions are configured using an AddRazorPages overload that configures RazorPagesOptions in Startup.ConfigureServices
. The following convention examples are explained later in this topic:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.Conventions.Add( ... );
options.Conventions.AddFolderRouteModelConvention(
"/OtherPages", model => { ... });
options.Conventions.AddPageRouteModelConvention(
"/About", model => { ... });
options.Conventions.AddPageRoute(
"/Contact", "TheContactPage/{text?}");
options.Conventions.AddFolderApplicationModelConvention(
"/OtherPages", model => { ... });
options.Conventions.AddPageApplicationModelConvention(
"/About", model => { ... });
options.Conventions.ConfigureFilter(model => { ... });
options.Conventions.ConfigureFilter( ... );
});
}
Route order
Routes specify an Order for processing (route matching).
Order | Behavior |
---|---|
-1 | The route is processed before other routes are processed. |
0 | Order isn't specified (default value). Not assigning Order (Order = null ) defaults the route Order to 0 (zero) for processing. |
1, 2, … n | Specifies the route processing order. |
Route processing is established by convention:
- Routes are processed in sequential order (-1, 0, 1, 2, … n).
- When routes have the same
Order
, the most specific route is matched first followed by less specific routes. - When routes with the same
Order
and the same number of parameters match a request URL, routes are processed in the order that they're added to the PageConventionCollection.
If possible, avoid depending on an established route processing order. Generally, routing selects the correct route with URL matching. If you must set route Order
properties to route requests correctly, the app's routing scheme is probably confusing to clients and fragile to maintain. Seek to simplify the app's routing scheme. The sample app requires an explicit route processing order to demonstrate several routing scenarios using a single app. However, you should attempt to avoid the practice of setting route Order
in production apps.
Razor Pages routing and MVC controller routing share an implementation. Information on route order in the MVC topics is available at Routing to controller actions: Ordering attribute routes.
Model conventions
Add a delegate for IPageConvention to add model conventions that apply to Razor Pages.
Add a route model convention to all pages
Use Conventions to create and add an IPageRouteModelConvention to the collection of IPageConvention instances that are applied during page route model construction.
The sample app adds a {globalTemplate?}
route template to all of the pages in the app:
public class GlobalTemplatePageRouteModelConvention
: IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel.Template,
"{globalTemplate?}"),
}
});
}
}
}
The Order property for the AttributeRouteModel is set to 1
. This ensures the following route matching behavior in the sample app:
- A route template for
TheContactPage/{text?}
is added later in the topic. The Contact Page route has a default order ofnull
(Order = 0
), so it matches before the{globalTemplate?}
route template. - An
{aboutTemplate?}
route template is added later in the topic. The{aboutTemplate?}
template is given anOrder
of2
. When the About page is requested at/About/RouteDataValue
, "RouteDataValue" is loaded intoRouteData.Values["globalTemplate"]
(Order = 1
) and notRouteData.Values["aboutTemplate"]
(Order = 2
) due to setting theOrder
property. - An
{otherPagesTemplate?}
route template is added later in the topic. The{otherPagesTemplate?}
template is given anOrder
of2
. When any page in the Pages/OtherPages folder is requested with a route parameter (for example,/OtherPages/Page1/RouteDataValue
), "RouteDataValue" is loaded intoRouteData.Values["globalTemplate"]
(Order = 1
) and notRouteData.Values["otherPagesTemplate"]
(Order = 2
) due to setting theOrder
property.
Wherever possible, don't set the Order
, which results in Order = 0
. Rely on routing to select the correct route.
Razor Pages options, such as adding Conventions, are added when Razor Pages is added to the service collection in Startup.ConfigureServices
. For an example, see the sample app.
options.Conventions.Add(new GlobalTemplatePageRouteModelConvention());
Request the sample's About page at localhost:5000/About/GlobalRouteValue
and inspect the result:
Add an app model convention to all pages
Use Conventions to create and add an IPageApplicationModelConvention to the collection of IPageConvention instances that are applied during page app model construction.
To demonstrate this and other conventions later in the topic, the sample app includes an AddHeaderAttribute
class. The class constructor accepts a name
string and a values
string array. These values are used in its OnResultExecuting
method to set a response header. The full class is shown in the Page model action conventions section later in the topic.
The sample app uses the AddHeaderAttribute
class to add a header, GlobalHeader
, to all of the pages in the app:
public class GlobalHeaderPageApplicationModelConvention
: IPageApplicationModelConvention
{
public void Apply(PageApplicationModel model)
{
model.Filters.Add(new AddHeaderAttribute(
"GlobalHeader", new string[] { "Global Header Value" }));
}
}
Startup.cs
:
options.Conventions.Add(new GlobalHeaderPageApplicationModelConvention());
Request the sample's About page at localhost:5000/About
and inspect the headers to view the result:
Add a handler model convention to all pages
Use Conventions to create and add an IPageHandlerModelConvention to the collection of IPageConvention instances that are applied during page handler model construction.
public class GlobalPageHandlerModelConvention
: IPageHandlerModelConvention
{
public void Apply(PageHandlerModel model)
{
// Access the PageHandlerModel
}
}
Startup.cs
:
options.Conventions.Add(new GlobalPageHandlerModelConvention());
Page route action conventions
The default route model provider that derives from IPageRouteModelProvider invokes conventions which are designed to provide extensibility points for configuring page routes.
Folder route model convention
Use AddFolderRouteModelConvention to create and add an IPageRouteModelConvention that invokes an action on the PageRouteModel for all of the pages under the specified folder.
The sample app uses AddFolderRouteModelConvention to add an {otherPagesTemplate?}
route template to the pages in the OtherPages folder:
options.Conventions.AddFolderRouteModelConvention("/OtherPages", model =>
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel.Template,
"{otherPagesTemplate?}"),
}
});
}
});
The Order property for the AttributeRouteModel is set to 2
. This ensures that the template for {globalTemplate?}
(set earlier in the topic to 1
) is given priority for the first route data value position when a single route value is provided. If a page in the Pages/OtherPages folder is requested with a route parameter value (for example, /OtherPages/Page1/RouteDataValue
), "RouteDataValue" is loaded into RouteData.Values["globalTemplate"]
(Order = 1
) and not RouteData.Values["otherPagesTemplate"]
(Order = 2
) due to setting the Order
property.
Wherever possible, don't set the Order
, which results in Order = 0
. Rely on routing to select the correct route.
Request the sample's Page1 page at localhost:5000/OtherPages/Page1/GlobalRouteValue/OtherPagesRouteValue
and inspect the result:
Page route model convention
Use AddPageRouteModelConvention to create and add an IPageRouteModelConvention that invokes an action on the PageRouteModel for the page with the specified name.
The sample app uses AddPageRouteModelConvention
to add an {aboutTemplate?}
route template to the About page:
options.Conventions.AddPageRouteModelConvention("/About", model =>
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel.Template,
"{aboutTemplate?}"),
}
});
}
});
The Order property for the AttributeRouteModel is set to 2
. This ensures that the template for {globalTemplate?}
(set earlier in the topic to 1
) is given priority for the first route data value position when a single route value is provided. If the About page is requested with a route parameter value at /About/RouteDataValue
, "RouteDataValue" is loaded into RouteData.Values["globalTemplate"]
(Order = 1
) and not RouteData.Values["aboutTemplate"]
(Order = 2
) due to setting the Order
property.
Wherever possible, don't set the Order
, which results in Order = 0
. Rely on routing to select the correct route.
Request the sample's About page at localhost:5000/About/GlobalRouteValue/AboutRouteValue
and inspect the result:
Use a parameter transformer to customize page routes
Page routes generated by ASP.NET Core can be customized using a parameter transformer. A parameter transformer implements IOutboundParameterTransformer
and transforms the value of parameters. For example, a custom SlugifyParameterTransformer
parameter transformer changes the SubscriptionManagement
route value to subscription-management
.
The PageRouteTransformerConvention
page route model convention applies a parameter transformer to the folder and file name segments of automatically generated page routes in an app. For example, the Razor Pages file at /Pages/SubscriptionManagement/ViewAll.cshtml
would have its route rewritten from /SubscriptionManagement/ViewAll
to /subscription-management/view-all
.
PageRouteTransformerConvention
only transforms the automatically generated segments of a page route that come from the Razor Pages folder and file name. It doesn't transform route segments added with the @page
directive. The convention also doesn't transform routes added by AddPageRoute.
The PageRouteTransformerConvention
is registered as an option in Startup.ConfigureServices
:
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages(options =>
{
options.Conventions.Add(
new PageRouteTransformerConvention(
new SlugifyParameterTransformer()));
});
}
public class SlugifyParameterTransformer : IOutboundParameterTransformer
{
public string TransformOutbound(object value)
{
if (value == null) { return null; }
return Regex.Replace(value.ToString(),
"([a-z])([A-Z])",
"$1-$2",
RegexOptions.CultureInvariant,
TimeSpan.FromMilliseconds(100)).ToLowerInvariant();
}
}
Warning
When using System.Text.RegularExpressions to process untrusted input, pass a timeout. A malicious user can provide input to RegularExpressions
causing a Denial-of-Service attack. ASP.NET Core framework APIs that use RegularExpressions
pass a timeout.
Configure a page route
Use AddPageRoute to configure a route to a page at the specified page path. Generated links to the page use your specified route. AddPageRoute
uses AddPageRouteModelConvention
to establish the route.
The sample app creates a route to /TheContactPage
for Contact.cshtml
:
options.Conventions.AddPageRoute("/Contact", "TheContactPage/{text?}");
The Contact page can also be reached at /Contact
via its default route.
The sample app's custom route to the Contact page allows for an optional text
route segment ({text?}
). The page also includes this optional segment in its @page
directive in case the visitor accesses the page at its /Contact
route:
@page "{text?}"
@model ContactModel
@{
ViewData["Title"] = "Contact";
}
<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>
<address>
One Microsoft Way<br>
Redmond, WA 98052-6399<br>
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br>
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>
<p>@Model.RouteDataTextTemplateValue</p>
Note that the URL generated for the Contact link in the rendered page reflects the updated route:
Visit the Contact page at either its ordinary route, /Contact
, or the custom route, /TheContactPage
. If you supply an additional text
route segment, the page shows the HTML-encoded segment that you provide:
Page model action conventions
The default page model provider that implements IPageApplicationModelProvider invokes conventions which are designed to provide extensibility points for configuring page models. These conventions are useful when building and modifying page discovery and processing scenarios.
For the examples in this section, the sample app uses an AddHeaderAttribute
class, which is a ResultFilterAttribute, that applies a response header:
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string[] _values;
public AddHeaderAttribute(string name, string[] values)
{
_name = name;
_values = values;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(_name, _values);
base.OnResultExecuting(context);
}
}
Using conventions, the sample demonstrates how to apply the attribute to all of the pages in a folder and to a single page.
Folder app model convention
Use AddFolderApplicationModelConvention to create and add an IPageApplicationModelConvention that invokes an action on PageApplicationModel instances for all pages under the specified folder.
The sample demonstrates the use of AddFolderApplicationModelConvention
by adding a header, OtherPagesHeader
, to the pages inside the OtherPages folder of the app:
options.Conventions.AddFolderApplicationModelConvention("/OtherPages", model =>
{
model.Filters.Add(new AddHeaderAttribute(
"OtherPagesHeader", new string[] { "OtherPages Header Value" }));
});
Request the sample's Page1 page at localhost:5000/OtherPages/Page1
and inspect the headers to view the result:
Page app model convention
Use AddPageApplicationModelConvention to create and add an IPageApplicationModelConvention that invokes an action on the PageApplicationModel for the page with the specified name.
The sample demonstrates the use of AddPageApplicationModelConvention
by adding a header, AboutHeader
, to the About page:
options.Conventions.AddPageApplicationModelConvention("/About", model =>
{
model.Filters.Add(new AddHeaderAttribute(
"AboutHeader", new string[] { "About Header Value" }));
});
Request the sample's About page at localhost:5000/About
and inspect the headers to view the result:
Configure a filter
ConfigureFilter configures the specified filter to apply. You can implement a filter class, but the sample app shows how to implement a filter in a lambda expression, which is implemented behind-the-scenes as a factory that returns a filter:
options.Conventions.ConfigureFilter(model =>
{
if (model.RelativePath.Contains("OtherPages/Page2"))
{
return new AddHeaderAttribute(
"OtherPagesPage2Header",
new string[] { "OtherPages/Page2 Header Value" });
}
return new EmptyFilter();
});
The page app model is used to check the relative path for segments that lead to the Page2 page in the OtherPages folder. If the condition passes, a header is added. If not, the EmptyFilter
is applied.
EmptyFilter
is an Action filter. Since Action filters are ignored by Razor Pages, the EmptyFilter
has no effect as intended if the path doesn't contain OtherPages/Page2
.
Request the sample's Page2 page at localhost:5000/OtherPages/Page2
and inspect the headers to view the result:
Configure a filter factory
ConfigureFilter configures the specified factory to apply filters to all Razor Pages.
The sample app provides an example of using a filter factory by adding a header, FilterFactoryHeader
, with two values to the app's pages:
options.Conventions.ConfigureFilter(new AddHeaderWithFactory());
AddHeaderWithFactory.cs
:
public class AddHeaderWithFactory : IFilterFactory
{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new AddHeaderFilter();
}
private class AddHeaderFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
"FilterFactoryHeader",
new string[]
{
"Filter Factory Header Value 1",
"Filter Factory Header Value 2"
});
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
Request the sample's About page at localhost:5000/About
and inspect the headers to view the result:
MVC Filters and the Page filter (IPageFilter)
MVC Action filters are ignored by Razor Pages, since Razor Pages use handler methods. Other types of MVC filters are available for you to use: Authorization, Exception, Resource, and Result. For more information, see the Filters topic.
The Page filter (IPageFilter) is a filter that applies to Razor Pages. For more information, see Filter methods for Razor Pages.
Additional resources
Learn how to use page route and app model provider conventions to control page routing, discovery, and processing in Razor Pages apps.
When you need to configure custom page routes for individual pages, configure routing to pages with the AddPageRoute convention described later in this topic.
To specify a page route, add route segments, or add parameters to a route, use the page's @page
directive. For more information, see Custom routes.
There are reserved words that can't be used as route segments or parameter names. For more information, see Routing: Reserved routing names.
View or download sample code (how to download)
Scenario | The sample demonstrates ... |
---|---|
Model conventions Conventions.Add
|
Add a route template and header to an app's pages. |
Page route action conventions
|
Add a route template to pages in a folder and to a single page. |
Page model action conventions
|
Add a header to pages in a folder, add a header to a single page, and configure a filter factory to add a header to an app's pages. |
Razor Pages conventions are added and configured using the AddRazorPagesOptions extension method to AddMvc on the service collection in the Startup
class. The following convention examples are explained later in this topic:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.Add( ... );
options.Conventions.AddFolderRouteModelConvention(
"/OtherPages", model => { ... });
options.Conventions.AddPageRouteModelConvention(
"/About", model => { ... });
options.Conventions.AddPageRoute(
"/Contact", "TheContactPage/{text?}");
options.Conventions.AddFolderApplicationModelConvention(
"/OtherPages", model => { ... });
options.Conventions.AddPageApplicationModelConvention(
"/About", model => { ... });
options.Conventions.ConfigureFilter(model => { ... });
options.Conventions.ConfigureFilter( ... );
});
}
Route order
Routes specify an Order for processing (route matching).
Order | Behavior |
---|---|
-1 | The route is processed before other routes are processed. |
0 | Order isn't specified (default value). Not assigning Order (Order = null ) defaults the route Order to 0 (zero) for processing. |
1, 2, … n | Specifies the route processing order. |
Route processing is established by convention:
- Routes are processed in sequential order (-1, 0, 1, 2, … n).
- When routes have the same
Order
, the most specific route is matched first followed by less specific routes. - When routes with the same
Order
and the same number of parameters match a request URL, routes are processed in the order that they're added to the PageConventionCollection.
If possible, avoid depending on an established route processing order. Generally, routing selects the correct route with URL matching. If you must set route Order
properties to route requests correctly, the app's routing scheme is probably confusing to clients and fragile to maintain. Seek to simplify the app's routing scheme. The sample app requires an explicit route processing order to demonstrate several routing scenarios using a single app. However, you should attempt to avoid the practice of setting route Order
in production apps.
Razor Pages routing and MVC controller routing share an implementation. Information on route order in the MVC topics is available at Routing to controller actions: Ordering attribute routes.
Model conventions
Add a delegate for IPageConvention to add model conventions that apply to Razor Pages.
Add a route model convention to all pages
Use Conventions to create and add an IPageRouteModelConvention to the collection of IPageConvention instances that are applied during page route model construction.
The sample app adds a {globalTemplate?}
route template to all of the pages in the app:
public class GlobalTemplatePageRouteModelConvention
: IPageRouteModelConvention
{
public void Apply(PageRouteModel model)
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 1,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel.Template,
"{globalTemplate?}"),
}
});
}
}
}
The Order property for the AttributeRouteModel is set to 1
. This ensures the following route matching behavior in the sample app:
- A route template for
TheContactPage/{text?}
is added later in the topic. The Contact Page route has a default order ofnull
(Order = 0
), so it matches before the{globalTemplate?}
route template. - An
{aboutTemplate?}
route template is added later in the topic. The{aboutTemplate?}
template is given anOrder
of2
. When the About page is requested at/About/RouteDataValue
, "RouteDataValue" is loaded intoRouteData.Values["globalTemplate"]
(Order = 1
) and notRouteData.Values["aboutTemplate"]
(Order = 2
) due to setting theOrder
property. - An
{otherPagesTemplate?}
route template is added later in the topic. The{otherPagesTemplate?}
template is given anOrder
of2
. When any page in the Pages/OtherPages folder is requested with a route parameter (for example,/OtherPages/Page1/RouteDataValue
), "RouteDataValue" is loaded intoRouteData.Values["globalTemplate"]
(Order = 1
) and notRouteData.Values["otherPagesTemplate"]
(Order = 2
) due to setting theOrder
property.
Wherever possible, don't set the Order
, which results in Order = 0
. Rely on routing to select the correct route.
Razor Pages options, such as adding Conventions, are added when MVC is added to the service collection in Startup.ConfigureServices
. For an example, see the sample app.
options.Conventions.Add(new GlobalTemplatePageRouteModelConvention());
Request the sample's About page at localhost:5000/About/GlobalRouteValue
and inspect the result:
Add an app model convention to all pages
Use Conventions to create and add an IPageApplicationModelConvention to the collection of IPageConvention instances that are applied during page app model construction.
To demonstrate this and other conventions later in the topic, the sample app includes an AddHeaderAttribute
class. The class constructor accepts a name
string and a values
string array. These values are used in its OnResultExecuting
method to set a response header. The full class is shown in the Page model action conventions section later in the topic.
The sample app uses the AddHeaderAttribute
class to add a header, GlobalHeader
, to all of the pages in the app:
public class GlobalHeaderPageApplicationModelConvention
: IPageApplicationModelConvention
{
public void Apply(PageApplicationModel model)
{
model.Filters.Add(new AddHeaderAttribute(
"GlobalHeader", new string[] { "Global Header Value" }));
}
}
Startup.cs
:
options.Conventions.Add(new GlobalHeaderPageApplicationModelConvention());
Request the sample's About page at localhost:5000/About
and inspect the headers to view the result:
Add a handler model convention to all pages
Use Conventions to create and add an IPageHandlerModelConvention to the collection of IPageConvention instances that are applied during page handler model construction.
public class GlobalPageHandlerModelConvention
: IPageHandlerModelConvention
{
public void Apply(PageHandlerModel model)
{
// Access the PageHandlerModel
}
}
Startup.cs
:
options.Conventions.Add(new GlobalPageHandlerModelConvention());
Page route action conventions
The default route model provider that derives from IPageRouteModelProvider invokes conventions which are designed to provide extensibility points for configuring page routes.
Folder route model convention
Use AddFolderRouteModelConvention to create and add an IPageRouteModelConvention that invokes an action on the PageRouteModel for all of the pages under the specified folder.
The sample app uses AddFolderRouteModelConvention to add an {otherPagesTemplate?}
route template to the pages in the OtherPages folder:
options.Conventions.AddFolderRouteModelConvention("/OtherPages", model =>
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel.Template,
"{otherPagesTemplate?}"),
}
});
}
});
The Order property for the AttributeRouteModel is set to 2
. This ensures that the template for {globalTemplate?}
(set earlier in the topic to 1
) is given priority for the first route data value position when a single route value is provided. If a page in the Pages/OtherPages folder is requested with a route parameter value (for example, /OtherPages/Page1/RouteDataValue
), "RouteDataValue" is loaded into RouteData.Values["globalTemplate"]
(Order = 1
) and not RouteData.Values["otherPagesTemplate"]
(Order = 2
) due to setting the Order
property.
Wherever possible, don't set the Order
, which results in Order = 0
. Rely on routing to select the correct route.
Request the sample's Page1 page at localhost:5000/OtherPages/Page1/GlobalRouteValue/OtherPagesRouteValue
and inspect the result:
Page route model convention
Use AddPageRouteModelConvention to create and add an IPageRouteModelConvention that invokes an action on the PageRouteModel for the page with the specified name.
The sample app uses AddPageRouteModelConvention
to add an {aboutTemplate?}
route template to the About page:
options.Conventions.AddPageRouteModelConvention("/About", model =>
{
var selectorCount = model.Selectors.Count;
for (var i = 0; i < selectorCount; i++)
{
var selector = model.Selectors[i];
model.Selectors.Add(new SelectorModel
{
AttributeRouteModel = new AttributeRouteModel
{
Order = 2,
Template = AttributeRouteModel.CombineTemplates(
selector.AttributeRouteModel.Template,
"{aboutTemplate?}"),
}
});
}
});
The Order property for the AttributeRouteModel is set to 2
. This ensures that the template for {globalTemplate?}
(set earlier in the topic to 1
) is given priority for the first route data value position when a single route value is provided. If the About page is requested with a route parameter value at /About/RouteDataValue
, "RouteDataValue" is loaded into RouteData.Values["globalTemplate"]
(Order = 1
) and not RouteData.Values["aboutTemplate"]
(Order = 2
) due to setting the Order
property.
Wherever possible, don't set the Order
, which results in Order = 0
. Rely on routing to select the correct route.
Request the sample's About page at localhost:5000/About/GlobalRouteValue/AboutRouteValue
and inspect the result:
Configure a page route
Use AddPageRoute to configure a route to a page at the specified page path. Generated links to the page use your specified route. AddPageRoute
uses AddPageRouteModelConvention
to establish the route.
The sample app creates a route to /TheContactPage
for Contact.cshtml
:
options.Conventions.AddPageRoute("/Contact", "TheContactPage/{text?}");
The Contact page can also be reached at /Contact
via its default route.
The sample app's custom route to the Contact page allows for an optional text
route segment ({text?}
). The page also includes this optional segment in its @page
directive in case the visitor accesses the page at its /Contact
route:
@page "{text?}"
@model ContactModel
@{
ViewData["Title"] = "Contact";
}
<h1>@ViewData["Title"]</h1>
<h2>@Model.Message</h2>
<address>
One Microsoft Way<br>
Redmond, WA 98052-6399<br>
<abbr title="Phone">P:</abbr>
425.555.0100
</address>
<address>
<strong>Support:</strong> <a href="mailto:Support@example.com">Support@example.com</a><br>
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
</address>
<p>@Model.RouteDataTextTemplateValue</p>
Note that the URL generated for the Contact link in the rendered page reflects the updated route:
Visit the Contact page at either its ordinary route, /Contact
, or the custom route, /TheContactPage
. If you supply an additional text
route segment, the page shows the HTML-encoded segment that you provide:
Page model action conventions
The default page model provider that implements IPageApplicationModelProvider invokes conventions which are designed to provide extensibility points for configuring page models. These conventions are useful when building and modifying page discovery and processing scenarios.
For the examples in this section, the sample app uses an AddHeaderAttribute
class, which is a ResultFilterAttribute, that applies a response header:
public class AddHeaderAttribute : ResultFilterAttribute
{
private readonly string _name;
private readonly string[] _values;
public AddHeaderAttribute(string name, string[] values)
{
_name = name;
_values = values;
}
public override void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(_name, _values);
base.OnResultExecuting(context);
}
}
Using conventions, the sample demonstrates how to apply the attribute to all of the pages in a folder and to a single page.
Folder app model convention
Use AddFolderApplicationModelConvention to create and add an IPageApplicationModelConvention that invokes an action on PageApplicationModel instances for all pages under the specified folder.
The sample demonstrates the use of AddFolderApplicationModelConvention
by adding a header, OtherPagesHeader
, to the pages inside the OtherPages folder of the app:
options.Conventions.AddFolderApplicationModelConvention("/OtherPages", model =>
{
model.Filters.Add(new AddHeaderAttribute(
"OtherPagesHeader", new string[] { "OtherPages Header Value" }));
});
Request the sample's Page1 page at localhost:5000/OtherPages/Page1
and inspect the headers to view the result:
Page app model convention
Use AddPageApplicationModelConvention to create and add an IPageApplicationModelConvention that invokes an action on the PageApplicationModel for the page with the specified name.
The sample demonstrates the use of AddPageApplicationModelConvention
by adding a header, AboutHeader
, to the About page:
options.Conventions.AddPageApplicationModelConvention("/About", model =>
{
model.Filters.Add(new AddHeaderAttribute(
"AboutHeader", new string[] { "About Header Value" }));
});
Request the sample's About page at localhost:5000/About
and inspect the headers to view the result:
Configure a filter
ConfigureFilter configures the specified filter to apply. You can implement a filter class, but the sample app shows how to implement a filter in a lambda expression, which is implemented behind-the-scenes as a factory that returns a filter:
options.Conventions.ConfigureFilter(model =>
{
if (model.RelativePath.Contains("OtherPages/Page2"))
{
return new AddHeaderAttribute(
"OtherPagesPage2Header",
new string[] { "OtherPages/Page2 Header Value" });
}
return new EmptyFilter();
});
The page app model is used to check the relative path for segments that lead to the Page2 page in the OtherPages folder. If the condition passes, a header is added. If not, the EmptyFilter
is applied.
EmptyFilter
is an Action filter. Since Action filters are ignored by Razor Pages, the EmptyFilter
has no effect as intended if the path doesn't contain OtherPages/Page2
.
Request the sample's Page2 page at localhost:5000/OtherPages/Page2
and inspect the headers to view the result:
Configure a filter factory
ConfigureFilter configures the specified factory to apply filters to all Razor Pages.
The sample app provides an example of using a filter factory by adding a header, FilterFactoryHeader
, with two values to the app's pages:
options.Conventions.ConfigureFilter(new AddHeaderWithFactory());
AddHeaderWithFactory.cs
:
public class AddHeaderWithFactory : IFilterFactory
{
// Implement IFilterFactory
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return new AddHeaderFilter();
}
private class AddHeaderFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
context.HttpContext.Response.Headers.Add(
"FilterFactoryHeader",
new string[]
{
"Filter Factory Header Value 1",
"Filter Factory Header Value 2"
});
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
}
public bool IsReusable
{
get
{
return false;
}
}
}
Request the sample's About page at localhost:5000/About
and inspect the headers to view the result:
MVC Filters and the Page filter (IPageFilter)
MVC Action filters are ignored by Razor Pages, since Razor Pages use handler methods. Other types of MVC filters are available for you to use: Authorization, Exception, Resource, and Result. For more information, see the Filters topic.
The Page filter (IPageFilter) is a filter that applies to Razor Pages. For more information, see Filter methods for Razor Pages.
Additional resources
ASP.NET Core