diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs
index 3c0bcd640f..02f464d8f6 100644
--- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs
@@ -209,7 +209,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
filter.OnActionExecuted(actionExecutedContext);
- _diagnosticSource.BeforeOnActionExecuted(actionExecutedContext, filter);
+ _diagnosticSource.AfterOnActionExecuted(actionExecutedContext, filter);
goto case State.ActionEnd;
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IAsyncPageFilter.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IAsyncPageFilter.cs
new file mode 100644
index 0000000000..a4534e7dc2
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IAsyncPageFilter.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Mvc.Filters
+{
+ ///
+ /// A filter that asynchronously surrounds execution of the page handler method.
+ ///
+ public interface IAsyncPageFilter : IFilterMetadata
+ {
+ ///
+ /// Called asynchronously after the handler method has been selected, but before model binding occurs.
+ ///
+ /// The .
+ /// A that on completion indicates the filter has executed.
+ Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context);
+
+ ///
+ /// Called asynchronously before the handler method is invoked, after model binding is complete.
+ ///
+ /// The .
+ ///
+ /// The . Invoked to execute the next page filter or the handler method itself.
+ ///
+ /// A that on completion indicates the filter has executed.
+ Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IPageFilter.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IPageFilter.cs
new file mode 100644
index 0000000000..8c9b45ea58
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IPageFilter.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNetCore.Mvc.Filters
+{
+ ///
+ /// A filter that surrounds execution of a page handler method.
+ ///
+ public interface IPageFilter : IFilterMetadata
+ {
+ ///
+ /// Called after a handler method has been selected, but before model binding occurs.
+ ///
+ /// The .
+ void OnPageHandlerSelected(PageHandlerSelectedContext context);
+
+ ///
+ /// Called before the handler method executes, after model binding is complete.
+ ///
+ /// The .
+ void OnPageHandlerExecuting(PageHandlerExecutingContext context);
+
+ ///
+ /// Called after the handler method executes, before the action result.
+ ///
+ /// The .
+ void OnPageHandlerExecuted(PageHandlerExecutedContext context);
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutedContext.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutedContext.cs
new file mode 100644
index 0000000000..084b44a825
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutedContext.cs
@@ -0,0 +1,124 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.ExceptionServices;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
+
+namespace Microsoft.AspNetCore.Mvc.Filters
+{
+ ///
+ /// A context for page filters, used specifically in
+ /// and
+ /// .
+ ///
+ public class PageHandlerExecutedContext : FilterContext
+ {
+ private Exception _exception;
+ private ExceptionDispatchInfo _exceptionDispatchInfo;
+
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The associated with the current request.
+ /// The set of filters associated with the page.
+ /// The handler method to be invoked, may be null.
+ /// The handler instance associated with the page.
+ public PageHandlerExecutedContext(
+ PageContext pageContext,
+ IList filters,
+ HandlerMethodDescriptor handlerMethod,
+ object handlerInstance)
+ : base(pageContext, filters)
+ {
+ if (handlerInstance == null)
+ {
+ throw new ArgumentNullException(nameof(handlerInstance));
+ }
+
+ HandlerMethod = handlerMethod;
+ HandlerInstance = handlerInstance;
+ }
+
+ ///
+ /// Gets the descriptor associated with the current page.
+ ///
+ public new virtual CompiledPageActionDescriptor ActionDescriptor
+ {
+ get
+ {
+ return (CompiledPageActionDescriptor)base.ActionDescriptor;
+ }
+ }
+
+ ///
+ /// Gets or sets an indication that an page filter short-circuited the action and the page filter pipeline.
+ ///
+ public virtual bool Canceled { get; set; }
+
+ ///
+ /// Gets the handler instance containing the handler method.
+ ///
+ public virtual object HandlerInstance { get; }
+
+ ///
+ /// Gets the descriptor for the handler method that was invoked.
+ ///
+ public virtual HandlerMethodDescriptor HandlerMethod { get; }
+
+ ///
+ /// Gets or sets the caught while executing the action or action filters, if
+ /// any.
+ ///
+ public virtual Exception Exception
+ {
+ get
+ {
+ if (_exception == null && _exceptionDispatchInfo != null)
+ {
+ return _exceptionDispatchInfo.SourceException;
+ }
+ else
+ {
+ return _exception;
+ }
+ }
+
+ set
+ {
+ _exceptionDispatchInfo = null;
+ _exception = value;
+ }
+ }
+
+ ///
+ /// Gets or sets the for the
+ /// , if an was caught and this information captured.
+ ///
+ public virtual ExceptionDispatchInfo ExceptionDispatchInfo
+ {
+ get
+ {
+ return _exceptionDispatchInfo;
+ }
+
+ set
+ {
+ _exception = null;
+ _exceptionDispatchInfo = value;
+ }
+ }
+
+ ///
+ /// Gets or sets an indication that the has been handled.
+ ///
+ public virtual bool ExceptionHandled { get; set; }
+
+ ///
+ /// Gets or sets the .
+ ///
+ public virtual IActionResult Result { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutingContext.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutingContext.cs
new file mode 100644
index 0000000000..0f712502b9
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutingContext.cs
@@ -0,0 +1,81 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
+
+namespace Microsoft.AspNetCore.Mvc.Filters
+{
+ ///
+ /// A context for page filters, used specifically in
+ /// and
+ /// .
+ ///
+ public class PageHandlerExecutingContext : FilterContext
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The associated with the current request.
+ /// The set of filters associated with the page.
+ /// The handler method to be invoked, may be null.
+ /// The arguments to provide to the handler method.
+ /// The handler instance associated with the page.
+ public PageHandlerExecutingContext(
+ PageContext pageContext,
+ IList filters,
+ HandlerMethodDescriptor handlerMethod,
+ IDictionary handlerArguments,
+ object handlerInstance)
+ : base(pageContext, filters)
+ {
+ if (handlerArguments == null)
+ {
+ throw new ArgumentNullException(nameof(handlerArguments));
+ }
+
+ if (handlerInstance == null)
+ {
+ throw new ArgumentNullException(nameof(handlerInstance));
+ }
+
+ HandlerMethod = handlerMethod;
+ HandlerArguments = handlerArguments;
+ HandlerInstance = handlerInstance;
+ }
+
+ ///
+ /// Gets the descriptor associated with the current page.
+ ///
+ public new virtual CompiledPageActionDescriptor ActionDescriptor
+ {
+ get
+ {
+ return (CompiledPageActionDescriptor)base.ActionDescriptor;
+ }
+ }
+
+ ///
+ /// Gets or sets the to execute. Setting to a non-null
+ /// value inside a page filter will short-circuit the page and any remaining page filters.
+ ///
+ public virtual IActionResult Result { get; set; }
+
+ ///
+ /// Gets the arguments to pass when invoking the handler method. Keys are parameter names.
+ ///
+ public virtual IDictionary HandlerArguments { get; }
+
+ ///
+ /// Gets the descriptor for the handler method about to be invoked.
+ ///
+ public virtual HandlerMethodDescriptor HandlerMethod { get; }
+
+ ///
+ /// Gets the object instance containing the handler method.
+ ///
+ public virtual object HandlerInstance { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutionDelegate.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutionDelegate.cs
new file mode 100644
index 0000000000..7bf9708b20
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutionDelegate.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNetCore.Mvc.Filters
+{
+ ///
+ /// A delegate that asynchronously returns a indicating the page or the next
+ /// page filter has executed.
+ ///
+ ///
+ /// A that on completion returns an .
+ ///
+ public delegate Task PageHandlerExecutionDelegate();
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerSelectedContext.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerSelectedContext.cs
new file mode 100644
index 0000000000..49d43e3dfa
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerSelectedContext.cs
@@ -0,0 +1,59 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
+
+namespace Microsoft.AspNetCore.Mvc.Filters
+{
+ ///
+ /// A context for page filters, used specifically in
+ /// and
+ /// .
+ ///
+ public class PageHandlerSelectedContext : FilterContext
+ {
+ ///
+ /// Creates a new instance of .
+ ///
+ /// The associated with the current request.
+ /// The set of filters associated with the page.
+ /// The handler instance associated with the page.
+ public PageHandlerSelectedContext(
+ PageContext pageContext,
+ IList filters,
+ object handlerInstance)
+ : base(pageContext, filters)
+ {
+ if (handlerInstance == null)
+ {
+ throw new ArgumentNullException(nameof(handlerInstance));
+ }
+
+ HandlerInstance = handlerInstance;
+ }
+
+ ///
+ /// Gets the descriptor associated with the current page.
+ ///
+ public new virtual CompiledPageActionDescriptor ActionDescriptor
+ {
+ get
+ {
+ return (CompiledPageActionDescriptor)base.ActionDescriptor;
+ }
+ }
+
+ ///
+ /// Gets or sets the descriptor for the handler method about to be invoked.
+ ///
+ public virtual HandlerMethodDescriptor HandlerMethod { get; set; }
+
+ ///
+ /// Gets the object instance containing the handler method.
+ ///
+ public virtual object HandlerInstance { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/MvcRazorPagesDiagnosticSourceExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/MvcRazorPagesDiagnosticSourceExtensions.cs
new file mode 100644
index 0000000000..212006004e
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/MvcRazorPagesDiagnosticSourceExtensions.cs
@@ -0,0 +1,290 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using Microsoft.AspNetCore.Mvc.Filters;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
+
+namespace Microsoft.AspNetCore.Mvc.Internal
+{
+ public static class MvcRazorPagesDiagnosticSourceExtensions
+ {
+ public static void BeforeHandlerMethod(
+ this DiagnosticSource diagnosticSource,
+ ActionContext actionContext,
+ HandlerMethodDescriptor handlerMethodDescriptor,
+ IDictionary arguments,
+ object instance)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(actionContext != null);
+ Debug.Assert(handlerMethodDescriptor != null);
+ Debug.Assert(arguments != null);
+ Debug.Assert(instance != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeHandlerMethod"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.BeforeHandlerMethod",
+ new
+ {
+ actionContext = actionContext,
+ arguments = arguments,
+ handlerMethodDescriptor = handlerMethodDescriptor,
+ instance = instance,
+ });
+ }
+ }
+
+ public static void AfterHandlerMethod(
+ this DiagnosticSource diagnosticSource,
+ ActionContext actionContext,
+ HandlerMethodDescriptor handlerMethodDescriptor,
+ IDictionary arguments,
+ object instance,
+ IActionResult result)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(actionContext != null);
+ Debug.Assert(handlerMethodDescriptor != null);
+ Debug.Assert(arguments != null);
+ Debug.Assert(instance != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterHandlerMethod"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.AfterHandlerMethod",
+ new
+ {
+ actionContext = actionContext,
+ arguments = arguments,
+ handlerMethodDescriptor = handlerMethodDescriptor,
+ instance = instance,
+ result = result
+ });
+ }
+ }
+
+ public static void BeforeOnPageHandlerExecution(
+ this DiagnosticSource diagnosticSource,
+ PageHandlerExecutingContext handlerExecutionContext,
+ IAsyncPageFilter filter)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(handlerExecutionContext != null);
+ Debug.Assert(filter != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecution"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecution",
+ new
+ {
+ actionDescriptor = handlerExecutionContext.ActionDescriptor,
+ handlerExecutionContext = handlerExecutionContext,
+ filter = filter
+ });
+ }
+ }
+
+ public static void AfterOnPageHandlerExecution(
+ this DiagnosticSource diagnosticSource,
+ PageHandlerExecutedContext handlerExecutedContext,
+ IAsyncPageFilter filter)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(handlerExecutedContext != null);
+ Debug.Assert(filter != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecution"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecution",
+ new
+ {
+ actionDescriptor = handlerExecutedContext.ActionDescriptor,
+ handlerExecutedContext = handlerExecutedContext,
+ filter = filter
+ });
+ }
+ }
+
+ public static void BeforeOnPageHandlerExecuting(
+ this DiagnosticSource diagnosticSource,
+ PageHandlerExecutingContext handlerExecutingContext,
+ IPageFilter filter)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(handlerExecutingContext != null);
+ Debug.Assert(filter != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecuting"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecuting",
+ new
+ {
+ actionDescriptor = handlerExecutingContext.ActionDescriptor,
+ handlerExecutingContext = handlerExecutingContext,
+ filter = filter
+ });
+ }
+ }
+
+ public static void AfterOnPageHandlerExecuting(
+ this DiagnosticSource diagnosticSource,
+ PageHandlerExecutingContext handlerExecutingContext,
+ IPageFilter filter)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(handlerExecutingContext != null);
+ Debug.Assert(filter != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecuting"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecuting",
+ new
+ {
+ actionDescriptor = handlerExecutingContext.ActionDescriptor,
+ handlerExecutingContext = handlerExecutingContext,
+ filter = filter
+ });
+ }
+ }
+
+ public static void BeforeOnPageHandlerExecuted(
+ this DiagnosticSource diagnosticSource,
+ PageHandlerExecutedContext handlerExecutedContext,
+ IPageFilter filter)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(handlerExecutedContext != null);
+ Debug.Assert(filter != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecuted"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecuted",
+ new
+ {
+ actionDescriptor = handlerExecutedContext.ActionDescriptor,
+ handlerExecutedContext = handlerExecutedContext,
+ filter = filter
+ });
+ }
+ }
+
+ public static void AfterOnPageHandlerExecuted(
+ this DiagnosticSource diagnosticSource,
+ PageHandlerExecutedContext handlerExecutedContext,
+ IPageFilter filter)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(handlerExecutedContext != null);
+ Debug.Assert(filter != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecuted"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecuted",
+ new
+ {
+ actionDescriptor = handlerExecutedContext.ActionDescriptor,
+ handlerExecutedContext = handlerExecutedContext,
+ filter = filter
+ });
+ }
+ }
+
+ public static void BeforeOnPageHandlerSelection(
+ this DiagnosticSource diagnosticSource,
+ PageHandlerSelectedContext handlerSelectedContext,
+ IAsyncPageFilter filter)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(handlerSelectedContext != null);
+ Debug.Assert(filter != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerSelection"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerSelection",
+ new
+ {
+ actionDescriptor = handlerSelectedContext.ActionDescriptor,
+ handlerSelectedContext = handlerSelectedContext,
+ filter = filter
+ });
+ }
+ }
+
+ public static void AfterOnPageHandlerSelection(
+ this DiagnosticSource diagnosticSource,
+ PageHandlerSelectedContext handlerSelectedContext,
+ IAsyncPageFilter filter)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(handlerSelectedContext != null);
+ Debug.Assert(filter != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnPageHandlerSelection"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.AfterOnPageHandlerSelection",
+ new
+ {
+ actionDescriptor = handlerSelectedContext.ActionDescriptor,
+ handlerSelectedContext = handlerSelectedContext,
+ filter = filter
+ });
+ }
+ }
+
+ public static void BeforeOnPageHandlerSelected(
+ this DiagnosticSource diagnosticSource,
+ PageHandlerSelectedContext handlerSelectedContext,
+ IPageFilter filter)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(handlerSelectedContext != null);
+ Debug.Assert(filter != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerSelected"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerSelected",
+ new
+ {
+ actionDescriptor = handlerSelectedContext.ActionDescriptor,
+ handlerSelectedContext = handlerSelectedContext,
+ filter = filter
+ });
+ }
+ }
+
+ public static void AfterOnPageHandlerSelected(
+ this DiagnosticSource diagnosticSource,
+ PageHandlerSelectedContext handlerSelectedContext,
+ IPageFilter filter)
+ {
+ Debug.Assert(diagnosticSource != null);
+ Debug.Assert(handlerSelectedContext != null);
+ Debug.Assert(filter != null);
+
+ if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnPageHandlerSelected"))
+ {
+ diagnosticSource.Write(
+ "Microsoft.AspNetCore.Mvc.AfterOnPageHandlerSelected",
+ new
+ {
+ actionDescriptor = handlerSelectedContext.ActionDescriptor,
+ handlerSelectedContext = handlerSelectedContext,
+ filter = filter
+ });
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs
index f926e21dc8..66c01029da 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs
@@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
-using System.Reflection;
+using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
@@ -26,19 +26,24 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private readonly ParameterBinder _parameterBinder;
private readonly ITempDataDictionaryFactory _tempDataFactory;
private readonly HtmlHelperOptions _htmlHelperOptions;
+ private readonly CompiledPageActionDescriptor _actionDescriptor;
- private CompiledPageActionDescriptor _actionDescriptor;
+ private Dictionary _arguments;
+ private HandlerMethodDescriptor _handler;
private Page _page;
- private object _model;
+ private object _pageModel;
private ViewContext _viewContext;
+ private PageHandlerSelectedContext _handlerSelectedContext;
+ private PageHandlerExecutingContext _handlerExecutingContext;
+ private PageHandlerExecutedContext _handlerExecutedContext;
+
public PageActionInvoker(
IPageHandlerMethodSelector handlerMethodSelector,
DiagnosticSource diagnosticSource,
ILogger logger,
PageContext pageContext,
IFilterMetadata[] filterMetadata,
- IList valueProviderFactories,
PageActionInvokerCacheEntry cacheEntry,
ParameterBinder parameterBinder,
ITempDataDictionaryFactory tempDataFactory,
@@ -48,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
logger,
pageContext,
filterMetadata,
- valueProviderFactories)
+ pageContext.ValueProviderFactories)
{
_selector = handlerMethodSelector;
_pageContext = pageContext;
@@ -63,6 +68,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Internal for testing
internal PageActionInvokerCacheEntry CacheEntry { get; }
+ private bool HasPageModel => _actionDescriptor.HandlerTypeInfo != _actionDescriptor.PageTypeInfo;
+
// Internal for testing
internal PageContext PageContext => _pageContext;
@@ -81,12 +88,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
await Next(ref next, ref scope, ref state, ref isCompleted);
}
}
-
+
protected override void ReleaseResources()
{
- if (_model != null && CacheEntry.ReleaseModel != null)
+ if (_pageModel != null && CacheEntry.ReleaseModel != null)
{
- CacheEntry.ReleaseModel(_pageContext, _model);
+ CacheEntry.ReleaseModel(_pageContext, _pageModel);
}
if (_page != null && CacheEntry.ReleasePage != null)
@@ -95,91 +102,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
}
- private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
+ private object CreateInstance()
{
- var diagnosticSource = _diagnosticSource;
- var logger = _logger;
-
- switch (next)
+ if (HasPageModel)
{
- case State.PageBegin:
- {
- var pageContext = _pageContext;
+ // Since this is a PageModel, we need to activate it, and then run a handler method on the model.
+ _pageModel = CacheEntry.ModelFactory(_pageContext);
+ _pageContext.ViewData.Model = _pageModel;
- _cursor.Reset();
-
- next = State.PageEnd;
- return ExecutePageAsync();
- }
-
- case State.PageEnd:
- {
- isCompleted = true;
- return TaskCache.CompletedTask;
- }
-
- default:
- throw new InvalidOperationException();
- }
- }
-
- private Task ExecutePageAsync()
- {
- _pageContext.ValueProviderFactories = _valueProviderFactories;
-
- // There's a fork in the road here between the case where we have a full-fledged PageModel
- // vs just a Page. We need to know up front because we want to execute handler methods
- // on the PageModel without instantiating the Page or ViewContext.
- var hasPageModel = _actionDescriptor.HandlerTypeInfo != _actionDescriptor.PageTypeInfo;
- if (hasPageModel)
- {
- return ExecutePageWithPageModelAsync();
+ return _pageModel;
}
else
{
- return ExecutePageWithoutPageModelAsync();
- }
- }
-
- private async Task ExecutePageWithPageModelAsync()
- {
- // Since this is a PageModel, we need to activate it, and then run a handler method on the model.
- //
- // We also know that the model is the pagemodel at this point.
- Debug.Assert(_actionDescriptor.ModelTypeInfo == _actionDescriptor.HandlerTypeInfo);
- _model = CacheEntry.ModelFactory(_pageContext);
- _pageContext.ViewData.Model = _model;
-
- // Flow the PageModel in places where the result filters would flow the controller.
- _instance = _model;
-
- if (CacheEntry.PropertyBinder != null)
- {
- await CacheEntry.PropertyBinder(_pageContext, _model);
- }
-
- // This is a workaround for not yet having proper filter for Pages.
- PageSaveTempDataPropertyFilter propertyFilter = null;
- for (var i = 0; i < _filters.Length; i++)
- {
- propertyFilter = _filters[i] as PageSaveTempDataPropertyFilter;
- if (propertyFilter != null)
- {
- break;
- }
- }
-
- if (propertyFilter != null)
- {
- propertyFilter.Subject = _model;
- propertyFilter.ApplyTempDataChanges(_pageContext.HttpContext);
- }
-
- _result = await ExecuteHandlerMethod(_model);
- if (_result is PageResult pageResult)
- {
- // If we get here, we are going to render the page, so we need to create it and then initialize
- // the context so we can run the result.
+ // Since this is a Page without a PageModel, we need to create the Page before running a handler method.
_viewContext = new ViewContext(
_pageContext,
NullView.Instance,
@@ -190,39 +125,23 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_page = (Page)CacheEntry.PageFactory(_pageContext, _viewContext);
- pageResult.Page = _page;
- pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData;
+ if (_actionDescriptor.ModelTypeInfo == _actionDescriptor.PageTypeInfo)
+ {
+ _pageContext.ViewData.Model = _page;
+ }
+
+ return _page;
}
}
- private async Task ExecutePageWithoutPageModelAsync()
+ private HandlerMethodDescriptor SelectHandler()
{
- // Since this is a Page without a PageModel, we need to create the Page before running a handler method.
- _viewContext = new ViewContext(
- _pageContext,
- NullView.Instance,
- _pageContext.ViewData,
- _tempDataFactory.GetTempData(_pageContext.HttpContext),
- TextWriter.Null,
- _htmlHelperOptions);
+ return _selector.Select(_pageContext);
+ }
- _page = (Page)CacheEntry.PageFactory(_pageContext, _viewContext);
-
- // Flow the Page in places where the result filters would flow the controller.
- _instance = _page;
-
- if (_actionDescriptor.ModelTypeInfo == _actionDescriptor.PageTypeInfo)
- {
- _model = _page;
- _pageContext.ViewData.Model = _model;
- }
-
- if (CacheEntry.PropertyBinder != null)
- {
- await CacheEntry.PropertyBinder(_pageContext, _model);
- }
-
- // This is a workaround for not yet having proper filter for Pages.
+ private Task BindArgumentsAsync()
+ {
+ // This is a temporary workaround.
PageSaveTempDataPropertyFilter propertyFilter = null;
for (var i = 0; i < _filters.Length; i++)
{
@@ -235,27 +154,37 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
if (propertyFilter != null)
{
- propertyFilter.Subject = _model;
+ propertyFilter.Subject = _instance;
propertyFilter.ApplyTempDataChanges(_pageContext.HttpContext);
}
- _result = await ExecuteHandlerMethod(_model);
- if (_result is PageResult pageResult)
+ // Perf: Avoid allocating async state machines where possible. We only need the state
+ // machine if you need to bind properties or arguments.
+ if (_actionDescriptor.BoundProperties.Count == 0 && (_handler == null || _handler.Parameters.Count == 0))
{
- // If we get here we're going to render the page so we need to initialize the context.
- pageResult.Page = _page;
- pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData;
+ return Task.CompletedTask;
}
+
+ return BindArgumentsCoreAsync();
}
- private async Task