Add Page filters

This commit is contained in:
Ryan Nowak 2017-05-26 16:05:04 -07:00
parent b1bc67e92e
commit b796bc0f39
17 changed files with 2437 additions and 287 deletions

View File

@ -209,7 +209,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
filter.OnActionExecuted(actionExecutedContext);
_diagnosticSource.BeforeOnActionExecuted(actionExecutedContext, filter);
_diagnosticSource.AfterOnActionExecuted(actionExecutedContext, filter);
goto case State.ActionEnd;
}

View File

@ -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
{
/// <summary>
/// A filter that asynchronously surrounds execution of the page handler method.
/// </summary>
public interface IAsyncPageFilter : IFilterMetadata
{
/// <summary>
/// Called asynchronously after the handler method has been selected, but before model binding occurs.
/// </summary>
/// <param name="context">The <see cref="PageHandlerSelectedContext"/>.</param>
/// <returns>A <see cref="Task"/> that on completion indicates the filter has executed.</returns>
Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context);
/// <summary>
/// Called asynchronously before the handler method is invoked, after model binding is complete.
/// </summary>
/// <param name="context">The <see cref="PageHandlerExecutingContext"/>.</param>
/// <param name="next">
/// The <see cref="PageHandlerExecutionDelegate"/>. Invoked to execute the next page filter or the handler method itself.
/// </param>
/// <returns>A <see cref="Task"/> that on completion indicates the filter has executed.</returns>
Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next);
}
}

View File

@ -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
{
/// <summary>
/// A filter that surrounds execution of a page handler method.
/// </summary>
public interface IPageFilter : IFilterMetadata
{
/// <summary>
/// Called after a handler method has been selected, but before model binding occurs.
/// </summary>
/// <param name="context">The <see cref="PageHandlerSelectedContext"/>.</param>
void OnPageHandlerSelected(PageHandlerSelectedContext context);
/// <summary>
/// Called before the handler method executes, after model binding is complete.
/// </summary>
/// <param name="context">The <see cref="PageHandlerExecutingContext"/>.</param>
void OnPageHandlerExecuting(PageHandlerExecutingContext context);
/// <summary>
/// Called after the handler method executes, before the action result.
/// </summary>
/// <param name="context">The <see cref="PageHandlerExecutedContext"/>.</param>
void OnPageHandlerExecuted(PageHandlerExecutedContext context);
}
}

View File

@ -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
{
/// <summary>
/// A context for page filters, used specifically in
/// <see cref="IPageFilter.OnPageHandlerExecuted(PageHandlerExecutedContext)"/> and
/// <see cref="IAsyncPageFilter.OnPageHandlerExecutionAsync(PageHandlerExecutingContext, PageHandlerExecutionDelegate)"/>.
/// </summary>
public class PageHandlerExecutedContext : FilterContext
{
private Exception _exception;
private ExceptionDispatchInfo _exceptionDispatchInfo;
/// <summary>
/// Creates a new instance of <see cref="PageHandlerExecutedContext"/>.
/// </summary>
/// <param name="pageContext">The <see cref="PageContext"/> associated with the current request.</param>
/// <param name="filters">The set of filters associated with the page.</param>
/// <param name="handlerMethod">The handler method to be invoked, may be null.</param>
/// <param name="handlerInstance">The handler instance associated with the page.</param>
public PageHandlerExecutedContext(
PageContext pageContext,
IList<IFilterMetadata> filters,
HandlerMethodDescriptor handlerMethod,
object handlerInstance)
: base(pageContext, filters)
{
if (handlerInstance == null)
{
throw new ArgumentNullException(nameof(handlerInstance));
}
HandlerMethod = handlerMethod;
HandlerInstance = handlerInstance;
}
/// <summary>
/// Gets the descriptor associated with the current page.
/// </summary>
public new virtual CompiledPageActionDescriptor ActionDescriptor
{
get
{
return (CompiledPageActionDescriptor)base.ActionDescriptor;
}
}
/// <summary>
/// Gets or sets an indication that an page filter short-circuited the action and the page filter pipeline.
/// </summary>
public virtual bool Canceled { get; set; }
/// <summary>
/// Gets the handler instance containing the handler method.
/// </summary>
public virtual object HandlerInstance { get; }
/// <summary>
/// Gets the descriptor for the handler method that was invoked.
/// </summary>
public virtual HandlerMethodDescriptor HandlerMethod { get; }
/// <summary>
/// Gets or sets the <see cref="System.Exception"/> caught while executing the action or action filters, if
/// any.
/// </summary>
public virtual Exception Exception
{
get
{
if (_exception == null && _exceptionDispatchInfo != null)
{
return _exceptionDispatchInfo.SourceException;
}
else
{
return _exception;
}
}
set
{
_exceptionDispatchInfo = null;
_exception = value;
}
}
/// <summary>
/// Gets or sets the <see cref="System.Runtime.ExceptionServices.ExceptionDispatchInfo"/> for the
/// <see cref="Exception"/>, if an <see cref="System.Exception"/> was caught and this information captured.
/// </summary>
public virtual ExceptionDispatchInfo ExceptionDispatchInfo
{
get
{
return _exceptionDispatchInfo;
}
set
{
_exception = null;
_exceptionDispatchInfo = value;
}
}
/// <summary>
/// Gets or sets an indication that the <see cref="Exception"/> has been handled.
/// </summary>
public virtual bool ExceptionHandled { get; set; }
/// <summary>
/// Gets or sets the <see cref="IActionResult"/>.
/// </summary>
public virtual IActionResult Result { get; set; }
}
}

View File

@ -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
{
/// <summary>
/// A context for page filters, used specifically in
/// <see cref="IPageFilter.OnPageHandlerExecuting(PageHandlerExecutingContext)"/> and
/// <see cref="IAsyncPageFilter.OnPageHandlerExecutionAsync(PageHandlerExecutingContext, PageHandlerExecutionDelegate)"/>.
/// </summary>
public class PageHandlerExecutingContext : FilterContext
{
/// <summary>
/// Creates a new instance of <see cref="PageHandlerExecutingContext"/>.
/// </summary>
/// <param name="pageContext">The <see cref="PageContext"/> associated with the current request.</param>
/// <param name="filters">The set of filters associated with the page.</param>
/// <param name="handlerMethod">The handler method to be invoked, may be null.</param>
/// <param name="handlerArguments">The arguments to provide to the handler method.</param>
/// <param name="handlerInstance">The handler instance associated with the page.</param>
public PageHandlerExecutingContext(
PageContext pageContext,
IList<IFilterMetadata> filters,
HandlerMethodDescriptor handlerMethod,
IDictionary<string, object> 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;
}
/// <summary>
/// Gets the descriptor associated with the current page.
/// </summary>
public new virtual CompiledPageActionDescriptor ActionDescriptor
{
get
{
return (CompiledPageActionDescriptor)base.ActionDescriptor;
}
}
/// <summary>
/// Gets or sets the <see cref="IActionResult"/> to execute. Setting <see cref="Result"/> to a non-<c>null</c>
/// value inside a page filter will short-circuit the page and any remaining page filters.
/// </summary>
public virtual IActionResult Result { get; set; }
/// <summary>
/// Gets the arguments to pass when invoking the handler method. Keys are parameter names.
/// </summary>
public virtual IDictionary<string, object> HandlerArguments { get; }
/// <summary>
/// Gets the descriptor for the handler method about to be invoked.
/// </summary>
public virtual HandlerMethodDescriptor HandlerMethod { get; }
/// <summary>
/// Gets the object instance containing the handler method.
/// </summary>
public virtual object HandlerInstance { get; }
}
}

View File

@ -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
{
/// <summary>
/// A delegate that asynchronously returns a <see cref="PageHandlerExecutedContext"/> indicating the page or the next
/// page filter has executed.
/// </summary>
/// <returns>
/// A <see cref="Task"/> that on completion returns an <see cref="PageHandlerExecutedContext"/>.
/// </returns>
public delegate Task<PageHandlerExecutedContext> PageHandlerExecutionDelegate();
}

View File

@ -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
{
/// <summary>
/// A context for page filters, used specifically in
/// <see cref="IPageFilter.OnPageHandlerSelected(PageHandlerSelectedContext)"/> and
/// <see cref="IAsyncPageFilter.OnPageHandlerSelectionAsync(PageHandlerSelectedContext)"/>.
/// </summary>
public class PageHandlerSelectedContext : FilterContext
{
/// <summary>
/// Creates a new instance of <see cref="PageHandlerExecutedContext"/>.
/// </summary>
/// <param name="pageContext">The <see cref="PageContext"/> associated with the current request.</param>
/// <param name="filters">The set of filters associated with the page.</param>
/// <param name="handlerInstance">The handler instance associated with the page.</param>
public PageHandlerSelectedContext(
PageContext pageContext,
IList<IFilterMetadata> filters,
object handlerInstance)
: base(pageContext, filters)
{
if (handlerInstance == null)
{
throw new ArgumentNullException(nameof(handlerInstance));
}
HandlerInstance = handlerInstance;
}
/// <summary>
/// Gets the descriptor associated with the current page.
/// </summary>
public new virtual CompiledPageActionDescriptor ActionDescriptor
{
get
{
return (CompiledPageActionDescriptor)base.ActionDescriptor;
}
}
/// <summary>
/// Gets or sets the descriptor for the handler method about to be invoked.
/// </summary>
public virtual HandlerMethodDescriptor HandlerMethod { get; set; }
/// <summary>
/// Gets the object instance containing the handler method.
/// </summary>
public virtual object HandlerInstance { get; }
}
}

View File

@ -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<string, object> 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<string, object> 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
});
}
}
}
}

View File

@ -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<string, object> _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<IValueProviderFactory> 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<object[]> GetArguments(HandlerMethodDescriptor handler)
private async Task BindArgumentsCoreAsync()
{
var arguments = new object[handler.Parameters.Count];
if (CacheEntry.PropertyBinder != null)
{
await CacheEntry.PropertyBinder(_pageContext, _instance);
}
if (_handler == null)
{
return;
}
var valueProvider = await CompositeValueProvider.CreateAsync(_pageContext, _pageContext.ValueProviderFactories);
for (var i = 0; i < handler.Parameters.Count; i++)
for (var i = 0; i < _handler.Parameters.Count; i++)
{
var parameter = handler.Parameters[i];
var parameter = _handler.Parameters[i];
var result = await _parameterBinder.BindModelAsync(
_pageContext,
@ -265,29 +194,50 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
if (result.IsModelSet)
{
arguments[i] = result.Model;
_arguments[parameter.Name] = result.Model;
}
}
}
private static object[] PrepareArguments(
IDictionary<string, object> argumentsInDictionary,
HandlerMethodDescriptor handler)
{
if (handler.Parameters.Count == 0)
{
return null;
}
var arguments = new object[handler.Parameters.Count];
for (var i = 0; i < arguments.Length; i++)
{
var parameter = handler.Parameters[i];
if (argumentsInDictionary.TryGetValue(parameter.ParameterInfo.Name, out var value))
{
// Do nothing, already set the value.
}
else if (parameter.ParameterInfo.HasDefaultValue)
{
arguments[i] = parameter.ParameterInfo.DefaultValue;
value = parameter.ParameterInfo.DefaultValue;
}
else if (parameter.ParameterType.GetTypeInfo().IsValueType)
else if (parameter.ParameterInfo.ParameterType.IsValueType)
{
arguments[i] = Activator.CreateInstance(parameter.ParameterType);
value = Activator.CreateInstance(parameter.ParameterInfo.ParameterType);
}
arguments[i] = value;
}
return arguments;
}
private async Task<IActionResult> ExecuteHandlerMethod(object instance)
private async Task InvokeHandlerMethodAsync()
{
IActionResult result = null;
var handler = _selector.Select(_pageContext);
if (handler != null)
var handler = _handler;
if (_handler != null)
{
var arguments = await GetArguments(handler);
var arguments = PrepareArguments(_arguments, handler);
Func<object, object[], Task<IActionResult>> executor = null;
for (var i = 0; i < _actionDescriptor.HandlerMethods.Count; i++)
@ -299,15 +249,423 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
}
result = await executor(instance, arguments);
_diagnosticSource.BeforeHandlerMethod(_pageContext, handler, _arguments, _instance);
_logger.ExecutingHandlerMethod(_pageContext, handler, arguments);
try
{
_result = await executor(_instance, arguments);
_logger.ExecutedHandlerMethod(_pageContext, handler, _result);
}
finally
{
_diagnosticSource.AfterHandlerMethod(_pageContext, handler, _arguments, _instance, _result);
}
}
if (result == null)
// Pages have an implicit 'return Page()' even without a handler method.
if (_result == null)
{
result = new PageResult();
_result = new PageResult();
}
return result;
// We also have some special initialization we need to do for PageResult.
if (_result is PageResult pageResult)
{
// If we used a PageModel then the Page isn't initialized yet.
if (_viewContext == null)
{
_viewContext = new ViewContext(
_pageContext,
NullView.Instance,
_pageContext.ViewData,
_tempDataFactory.GetTempData(_pageContext.HttpContext),
TextWriter.Null,
_htmlHelperOptions);
}
if (_page == null)
{
_page = (Page)CacheEntry.PageFactory(_pageContext, _viewContext);
}
pageResult.Page = _page;
pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData;
}
}
private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
{
switch (next)
{
case State.PageBegin:
{
_instance = CreateInstance();
goto case State.PageSelectHandlerBegin;
}
case State.PageSelectHandlerBegin:
{
_cursor.Reset();
_handler = SelectHandler();
goto case State.PageSelectHandlerNext;
}
case State.PageSelectHandlerNext:
var currentSelector = _cursor.GetNextFilter<IPageFilter, IAsyncPageFilter>();
if (currentSelector.FilterAsync != null)
{
if (_handlerSelectedContext == null)
{
_handlerSelectedContext = new PageHandlerSelectedContext(_pageContext, _filters, _instance)
{
HandlerMethod = _handler,
};
}
state = currentSelector.FilterAsync;
goto case State.PageSelectHandlerAsyncBegin;
}
else if (currentSelector.Filter != null)
{
if (_handlerSelectedContext == null)
{
_handlerSelectedContext = new PageHandlerSelectedContext(_pageContext, _filters, _instance)
{
HandlerMethod = _handler,
};
}
state = currentSelector.Filter;
goto case State.PageSelectHandlerSync;
}
else
{
goto case State.PageSelectHandlerEnd;
}
case State.PageSelectHandlerAsyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_handlerSelectedContext != null);
var filter = (IAsyncPageFilter)state;
var handlerSelectedContext = _handlerSelectedContext;
_diagnosticSource.BeforeOnPageHandlerSelection(handlerSelectedContext, filter);
var task = filter.OnPageHandlerSelectionAsync(handlerSelectedContext);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.PageSelectHandlerAsyncEnd;
return task;
}
goto case State.PageSelectHandlerAsyncEnd;
}
case State.PageSelectHandlerAsyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_handlerSelectedContext != null);
var filter = (IAsyncPageFilter)state;
_diagnosticSource.AfterOnPageHandlerSelection(_handlerSelectedContext, filter);
goto case State.PageSelectHandlerNext;
}
case State.PageSelectHandlerSync:
{
Debug.Assert(state != null);
Debug.Assert(_handlerSelectedContext != null);
var filter = (IPageFilter)state;
var handlerSelectedContext = _handlerSelectedContext;
_diagnosticSource.BeforeOnPageHandlerSelected(handlerSelectedContext, filter);
filter.OnPageHandlerSelected(handlerSelectedContext);
_diagnosticSource.AfterOnPageHandlerSelected(handlerSelectedContext, filter);
goto case State.PageSelectHandlerNext;
}
case State.PageSelectHandlerEnd:
{
if (_handlerSelectedContext != null)
{
_handler = _handlerSelectedContext.HandlerMethod;
}
_arguments = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
_cursor.Reset();
var task = BindArgumentsAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.PageNext;
return task;
}
goto case State.PageNext;
}
case State.PageNext:
{
var current = _cursor.GetNextFilter<IPageFilter, IAsyncPageFilter>();
if (current.FilterAsync != null)
{
if (_handlerExecutingContext == null)
{
_handlerExecutingContext = new PageHandlerExecutingContext(_pageContext, _filters, _handler, _arguments, _instance);
}
state = current.FilterAsync;
goto case State.PageAsyncBegin;
}
else if (current.Filter != null)
{
if (_handlerExecutingContext == null)
{
_handlerExecutingContext = new PageHandlerExecutingContext(_pageContext, _filters,_handler, _arguments, _instance);
}
state = current.Filter;
goto case State.PageSyncBegin;
}
else
{
goto case State.PageInside;
}
}
case State.PageAsyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_handlerExecutingContext != null);
var filter = (IAsyncPageFilter)state;
var handlerExecutingContext = _handlerExecutingContext;
_diagnosticSource.BeforeOnPageHandlerExecution(handlerExecutingContext, filter);
var task = filter.OnPageHandlerExecutionAsync(handlerExecutingContext, InvokeNextPageFilterAwaitedAsync);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.PageAsyncEnd;
return task;
}
goto case State.PageAsyncEnd;
}
case State.PageAsyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_handlerExecutingContext != null);
var filter = (IAsyncPageFilter)state;
if (_handlerExecutedContext == null)
{
// If we get here then the filter didn't call 'next' indicating a short circuit.
_logger.PageFilterShortCircuited(filter);
_handlerExecutedContext = new PageHandlerExecutedContext(
_pageContext,
_filters,
_handler,
_instance)
{
Canceled = true,
Result = _handlerExecutingContext.Result,
};
}
_diagnosticSource.AfterOnPageHandlerExecution(_handlerExecutedContext, filter);
goto case State.PageEnd;
}
case State.PageSyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_handlerExecutingContext != null);
var filter = (IPageFilter)state;
var handlerExecutingContext = _handlerExecutingContext;
_diagnosticSource.BeforeOnPageHandlerExecuting(handlerExecutingContext, filter);
filter.OnPageHandlerExecuting(handlerExecutingContext);
_diagnosticSource.AfterOnPageHandlerExecuting(handlerExecutingContext, filter);
if (handlerExecutingContext.Result != null)
{
// Short-circuited by setting a result.
_logger.PageFilterShortCircuited(filter);
_handlerExecutedContext = new PageHandlerExecutedContext(
_pageContext,
_filters,
_handler,
_instance)
{
Canceled = true,
Result = _handlerExecutingContext.Result,
};
goto case State.PageEnd;
}
var task = InvokeNextPageFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.PageSyncEnd;
return task;
}
goto case State.PageSyncEnd;
}
case State.PageSyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_handlerExecutingContext != null);
Debug.Assert(_handlerExecutedContext != null);
var filter = (IPageFilter)state;
var handlerExecutedContext = _handlerExecutedContext;
_diagnosticSource.BeforeOnPageHandlerExecuted(handlerExecutedContext, filter);
filter.OnPageHandlerExecuted(handlerExecutedContext);
_diagnosticSource.AfterOnPageHandlerExecuted(handlerExecutedContext, filter);
goto case State.PageEnd;
}
case State.PageInside:
{
var task = InvokeHandlerMethodAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.PageEnd;
return task;
}
goto case State.PageEnd;
}
case State.PageEnd:
{
if (scope == Scope.Page)
{
if (_handlerExecutedContext == null)
{
_handlerExecutedContext = new PageHandlerExecutedContext(_pageContext, _filters, _handler, _instance)
{
Result = _result,
};
}
isCompleted = true;
return Task.CompletedTask;
}
var handlerExecutedContext = _handlerExecutedContext;
Rethrow(handlerExecutedContext);
if (handlerExecutedContext != null)
{
_result = handlerExecutedContext.Result;
}
isCompleted = true;
return Task.CompletedTask;
}
default:
throw new InvalidOperationException();
}
}
private async Task InvokeNextPageFilterAsync()
{
try
{
var next = State.PageNext;
var state = (object)null;
var scope = Scope.Page;
var isCompleted = false;
while (!isCompleted)
{
await Next(ref next, ref scope, ref state, ref isCompleted);
}
}
catch (Exception exception)
{
_handlerExecutedContext = new PageHandlerExecutedContext(_pageContext, _filters, _handler, _instance)
{
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
};
}
Debug.Assert(_handlerExecutedContext != null);
}
private async Task<PageHandlerExecutedContext> InvokeNextPageFilterAwaitedAsync()
{
Debug.Assert(_handlerExecutingContext != null);
if (_handlerExecutingContext.Result != null)
{
// If we get here, it means that an async filter set a result AND called next(). This is forbidden.
var message = Resources.FormatAsyncPageFilter_InvalidShortCircuit(
typeof(IAsyncPageFilter).Name,
nameof(PageHandlerExecutingContext.Result),
typeof(PageHandlerExecutingContext).Name,
typeof(PageHandlerExecutionDelegate).Name);
throw new InvalidOperationException(message);
}
await InvokeNextPageFilterAsync();
Debug.Assert(_handlerExecutedContext != null);
return _handlerExecutedContext;
}
private static void Rethrow(PageHandlerExecutedContext context)
{
if (context == null)
{
return;
}
if (context.ExceptionHandled)
{
return;
}
if (context.ExceptionDispatchInfo != null)
{
context.ExceptionDispatchInfo.Throw();
}
if (context.Exception != null)
{
throw context.Exception;
}
}
private enum Scope
@ -319,6 +677,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private enum State
{
PageBegin,
PageSelectHandlerBegin,
PageSelectHandlerNext,
PageSelectHandlerAsyncBegin,
PageSelectHandlerAsyncEnd,
PageSelectHandlerSync,
PageSelectHandlerEnd,
PageNext,
PageAsyncBegin,
PageAsyncEnd,
PageSyncBegin,
PageSyncEnd,
PageInside,
PageEnd,
}
}

View File

@ -149,6 +149,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var pageContext = new PageContext(actionContext)
{
ActionDescriptor = cacheEntry.ActionDescriptor,
ValueProviderFactories = new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories),
ViewData = cacheEntry.ViewDataFactory(_modelMetadataProvider, actionContext.ModelState),
ViewStartFactories = cacheEntry.ViewStartFactories.ToList(),
};
@ -159,7 +160,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_logger,
pageContext,
filters,
new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories),
cacheEntry,
_parameterBinder,
_tempDataFactory,

View File

@ -2,11 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
@ -14,27 +13,23 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
internal static class PageLoggerExtensions
{
private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency;
private static readonly Action<ILogger, string, Exception> _pageExecuting;
private static readonly Action<ILogger, string, double, Exception> _pageExecuted;
private static readonly Action<ILogger, object, Exception> _exceptionFilterShortCircuit;
private static readonly Action<ILogger, string, string[], ModelValidationState, Exception> _handlerMethodExecuting;
private static readonly Action<ILogger, string, string, Exception> _handlerMethodExecuted;
private static readonly Action<ILogger, object, Exception> _pageFilterShortCircuit;
static PageLoggerExtensions()
{
_pageExecuting = LoggerMessage.Define<string>(
LogLevel.Debug,
1,
"Executing page {ActionName}");
// These numbers start at 101 intentionally to avoid conflict with the IDs used by ResourceInvoker.
_pageExecuted = LoggerMessage.Define<string, double>(
_handlerMethodExecuting = LoggerMessage.Define<string, string[], ModelValidationState>(
LogLevel.Information,
2,
"Executed page {ActionName} in {ElapsedMilliseconds}ms");
101,
"Executing handler method {HandlerName} with arguments ({Arguments}) - ModelState is {ValidationState}");
_exceptionFilterShortCircuit = LoggerMessage.Define<object>(
_handlerMethodExecuted = LoggerMessage.Define<string, string>(
LogLevel.Debug,
4,
"Request was short circuited at exception filter '{ExceptionFilter}'.");
102,
"Executed handler method {HandlerName}, returned result {ActionResult}.");
_pageFilterShortCircuit = LoggerMessage.Define<object>(
LogLevel.Debug,
@ -42,36 +37,39 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
"Request was short circuited at page filter '{PageFilter}'.");
}
public static IDisposable PageScope(this ILogger logger, ActionDescriptor actionDescriptor)
public static void ExecutingHandlerMethod(this ILogger logger, PageContext context, HandlerMethodDescriptor handler, object[] arguments)
{
Debug.Assert(logger != null);
Debug.Assert(actionDescriptor != null);
return logger.BeginScope(new PageLogScope(actionDescriptor));
}
public static void ExecutingPage(this ILogger logger, ActionDescriptor action)
{
_pageExecuting(logger, action.DisplayName, null);
}
public static void ExecutedAction(this ILogger logger, ActionDescriptor action, long startTimestamp)
{
// Don't log if logging wasn't enabled at start of request as time will be wildly wrong.
if (logger.IsEnabled(LogLevel.Information) && startTimestamp != 0)
if (logger.IsEnabled(LogLevel.Information))
{
var currentTimestamp = Stopwatch.GetTimestamp();
var elapsed = new TimeSpan((long)(TimestampToTicks * (currentTimestamp - startTimestamp)));
var handlerName = handler.MethodInfo.Name;
_pageExecuted(logger, action.DisplayName, elapsed.TotalMilliseconds, null);
string[] convertedArguments;
if (arguments == null)
{
convertedArguments = null;
}
else
{
convertedArguments = new string[arguments.Length];
for (var i = 0; i < arguments.Length; i++)
{
convertedArguments[i] = Convert.ToString(arguments[i]);
}
}
var validationState = context.ModelState.ValidationState;
_handlerMethodExecuting(logger, handlerName, convertedArguments, validationState, null);
}
}
public static void ExceptionFilterShortCircuited(
this ILogger logger,
IFilterMetadata filter)
public static void ExecutedHandlerMethod(this ILogger logger, PageContext context, HandlerMethodDescriptor handler, IActionResult result)
{
_exceptionFilterShortCircuit(logger, filter, null);
if (logger.IsEnabled(LogLevel.Debug))
{
var handlerName = handler.MethodInfo.Name;
_handlerMethodExecuted(logger, handlerName, Convert.ToString(result), null);
}
}
public static void PageFilterShortCircuited(
@ -80,50 +78,5 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
_pageFilterShortCircuit(logger, filter, null);
}
private class PageLogScope : IReadOnlyList<KeyValuePair<string, object>>
{
private readonly ActionDescriptor _action;
public PageLogScope(ActionDescriptor action)
{
_action = action;
}
public KeyValuePair<string, object> this[int index]
{
get
{
if (index == 0)
{
return new KeyValuePair<string, object>("ActionId", _action.Id);
}
else if (index == 1)
{
return new KeyValuePair<string, object>("PageName", _action.DisplayName);
}
throw new IndexOutOfRangeException(nameof(index));
}
}
public int Count => 2;
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
for (var i = 0; i < Count; ++i)
{
yield return this[i];
}
}
public override string ToString()
{
// We don't include the _action.Id here because it's just an opaque guid, and if
// you have text logging, you can already use the requestId for correlation.
return _action.DisplayName;
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
}
}

View File

@ -18,4 +18,8 @@
<PackageReference Include="Microsoft.Extensions.PropertyHelper.Sources" Version="$(AspNetCoreVersion)" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<Folder Include="Filters\" />
</ItemGroup>
</Project>

View File

@ -1,6 +1,7 @@
// 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.Diagnostics;
using Microsoft.AspNetCore.Mvc.Abstractions;
@ -35,5 +36,29 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// Gets or sets the path relative to the base path for page discovery.
/// </summary>
public string ViewEnginePath { get; set; }
/// <inheritdoc />
public override string DisplayName
{
get
{
if (base.DisplayName == null && ViewEnginePath != null)
{
base.DisplayName = ViewEnginePath;
}
return base.DisplayName;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
base.DisplayName = value;
}
}
}
}

View File

@ -122,6 +122,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
internal static string FormatPathMustBeAnAppRelativePath()
=> GetString("PathMustBeAnAppRelativePath");
/// <summary>
/// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}.
/// </summary>
internal static string AsyncPageFilter_InvalidShortCircuit
{
get => GetString("AsyncPageFilter_InvalidShortCircuit");
}
/// <summary>
/// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}.
/// </summary>
internal static string FormatAsyncPageFilter_InvalidShortCircuit(object p0, object p1, object p2, object p3)
=> string.Format(CultureInfo.CurrentCulture, GetString("AsyncPageFilter_InvalidShortCircuit"), p0, p1, p2, p3);
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);

View File

@ -141,4 +141,7 @@
<data name="PathMustBeAnAppRelativePath" xml:space="preserve">
<value>Path must be an application relative path that starts with a forward slash '/'.</value>
</data>
<data name="AsyncPageFilter_InvalidShortCircuit" xml:space="preserve">
<value>If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}.</value>
</data>
</root>

View File

@ -10,9 +10,11 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.RazorPages\Microsoft.AspNetCore.Mvc.RazorPages.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.TestCommon\Microsoft.AspNetCore.Mvc.TestCommon.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.TestDiagnosticListener\Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj" />
<PackageReference Include="Microsoft.Extensions.Logging.Testing" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.Extensions.DiagnosticAdapter" Version="$(AspNetCoreVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(TestSdkVersion)" />
<PackageReference Include="Moq" Version="$(MoqVersion)" />
<PackageReference Include="xunit" Version="$(XunitVersion)" />