Separate PageContext and ViewContext

This change decouples PageContext and ViewContext completely.
This commit is contained in:
Ryan Nowak 2017-05-16 13:49:24 -07:00
parent 6094c6ff52
commit 2992f8e38a
29 changed files with 567 additions and 376 deletions

View File

@ -1285,7 +1285,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
=> string.Format(CultureInfo.CurrentCulture, GetString("NoRoutesMatchedForPage"), p0); => string.Format(CultureInfo.CurrentCulture, GetString("NoRoutesMatchedForPage"), p0);
/// <summary> /// <summary>
/// The relative page path '{0}' can only can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. /// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.
/// </summary> /// </summary>
internal static string UrlHelper_RelativePagePathIsNotSupported internal static string UrlHelper_RelativePagePathIsNotSupported
{ {
@ -1293,7 +1293,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
} }
/// <summary> /// <summary>
/// The relative page path '{0}' can only can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. /// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.
/// </summary> /// </summary>
internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0) internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0); => string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0);

View File

@ -3,12 +3,12 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Internal; using Microsoft.Extensions.Internal;
@ -16,8 +16,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{ {
public class RazorPagePropertyActivator public class RazorPagePropertyActivator
{ {
private delegate ViewDataDictionary CreateViewDataNestedDelegate(ViewDataDictionary source); private readonly IModelMetadataProvider _metadataProvider;
private delegate ViewDataDictionary CreateViewDataRootDelegate(ModelStateDictionary modelState); private readonly Func<IModelMetadataProvider, ModelStateDictionary, ViewDataDictionary> _rootFactory;
private readonly Func<ViewDataDictionary, ViewDataDictionary> _nestedFactory;
private readonly Type _viewDataDictionaryType;
private readonly PropertyActivator<ViewContext>[] _propertyActivators;
public RazorPagePropertyActivator( public RazorPagePropertyActivator(
Type pageType, Type pageType,
@ -25,26 +28,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
IModelMetadataProvider metadataProvider, IModelMetadataProvider metadataProvider,
PropertyValueAccessors propertyValueAccessors) PropertyValueAccessors propertyValueAccessors)
{ {
var viewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType); _metadataProvider = metadataProvider;
ViewDataDictionaryType = viewDataType;
CreateViewDataNested = GetCreateViewDataNested(viewDataType);
CreateViewDataRoot = GetCreateViewDataRoot(viewDataType, metadataProvider);
PropertyActivators = PropertyActivator<ViewContext>.GetPropertiesToActivate( _viewDataDictionaryType = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
_rootFactory = ViewDataDictionaryFactory.CreateFactory(modelType.GetTypeInfo());
_nestedFactory = ViewDataDictionaryFactory.CreateNestedFactory(modelType.GetTypeInfo());
_propertyActivators = PropertyActivator<ViewContext>.GetPropertiesToActivate(
pageType, pageType,
typeof(RazorInjectAttribute), typeof(RazorInjectAttribute),
propertyInfo => CreateActivateInfo(propertyInfo, propertyValueAccessors), propertyInfo => CreateActivateInfo(propertyInfo, propertyValueAccessors),
includeNonPublic: true); includeNonPublic: true);
} }
private PropertyActivator<ViewContext>[] PropertyActivators { get; }
private Type ViewDataDictionaryType { get; }
private CreateViewDataNestedDelegate CreateViewDataNested { get; }
private CreateViewDataRootDelegate CreateViewDataRoot { get; }
public void Activate(object page, ViewContext context) public void Activate(object page, ViewContext context)
{ {
if (context == null) if (context == null)
@ -54,9 +50,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
context.ViewData = CreateViewDataDictionary(context); context.ViewData = CreateViewDataDictionary(context);
for (var i = 0; i < PropertyActivators.Length; i++) for (var i = 0; i < _propertyActivators.Length; i++)
{ {
var activateInfo = PropertyActivators[i]; var activateInfo = _propertyActivators[i];
activateInfo.Activate(page, context); activateInfo.Activate(page, context);
} }
} }
@ -68,58 +64,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
if (context.ViewData == null) if (context.ViewData == null)
{ {
// Create ViewDataDictionary<TModel>(IModelMetadataProvider, ModelStateDictionary). // Create ViewDataDictionary<TModel>(IModelMetadataProvider, ModelStateDictionary).
return CreateViewDataRoot(context.ModelState); return _rootFactory(_metadataProvider, context.ModelState);
} }
else if (context.ViewData.GetType() != ViewDataDictionaryType) else if (context.ViewData.GetType() != _viewDataDictionaryType)
{ {
// Create ViewDataDictionary<TModel>(ViewDataDictionary). // Create ViewDataDictionary<TModel>(ViewDataDictionary).
return CreateViewDataNested(context.ViewData); return _nestedFactory(context.ViewData);
} }
return context.ViewData; return context.ViewData;
} }
private static CreateViewDataNestedDelegate GetCreateViewDataNested(Type viewDataDictionaryType)
{
var parameterTypes = new Type[] { typeof(ViewDataDictionary) };
var matchingConstructor = viewDataDictionaryType.GetConstructor(parameterTypes);
Debug.Assert(matchingConstructor != null);
var parameters = new ParameterExpression[] { Expression.Parameter(parameterTypes[0]) };
var newExpression = Expression.New(matchingConstructor, parameters);
var castNewCall = Expression.Convert(
newExpression,
typeof(ViewDataDictionary));
var lambda = Expression.Lambda<CreateViewDataNestedDelegate>(castNewCall, parameters);
return lambda.Compile();
}
private static CreateViewDataRootDelegate GetCreateViewDataRoot(
Type viewDataDictionaryType,
IModelMetadataProvider provider)
{
var parameterTypes = new[]
{
typeof(IModelMetadataProvider),
typeof(ModelStateDictionary)
};
var matchingConstructor = viewDataDictionaryType.GetConstructor(parameterTypes);
Debug.Assert(matchingConstructor != null);
var parameterExpression = Expression.Parameter(parameterTypes[1]);
var parameters = new Expression[]
{
Expression.Constant(provider),
parameterExpression
};
var newExpression = Expression.New(matchingConstructor, parameters);
var castNewCall = Expression.Convert(
newExpression,
typeof(ViewDataDictionary));
var lambda = Expression.Lambda<CreateViewDataRootDelegate>(castNewCall, parameterExpression);
return lambda.Compile();
}
private static PropertyActivator<ViewContext> CreateActivateInfo( private static PropertyActivator<ViewContext> CreateActivateInfo(
PropertyInfo property, PropertyInfo property,
PropertyValueAccessors valueAccessors) PropertyValueAccessors valueAccessors)

View File

@ -113,7 +113,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton<IPageModelActivatorProvider, DefaultPageModelActivatorProvider>(); services.TryAddSingleton<IPageModelActivatorProvider, DefaultPageModelActivatorProvider>();
services.TryAddSingleton<IPageModelFactoryProvider, DefaultPageModelFactoryProvider>(); services.TryAddSingleton<IPageModelFactoryProvider, DefaultPageModelFactoryProvider>();
services.TryAddSingleton<IPageActivatorProvider, DefaultPageActivator>(); services.TryAddSingleton<IPageActivatorProvider, DefaultPageActivatorProvider>();
services.TryAddSingleton<IPageFactoryProvider, DefaultPageFactory>(); services.TryAddSingleton<IPageFactoryProvider, DefaultPageFactory>();
services.TryAddSingleton<IPageLoader, DefaultPageLoader>(); services.TryAddSingleton<IPageLoader, DefaultPageLoader>();

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Microsoft.AspNetCore.Mvc.RazorPages namespace Microsoft.AspNetCore.Mvc.RazorPages
{ {
@ -15,13 +16,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </summary> /// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param> /// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <returns>The delegate used to activate the page.</returns> /// <returns>The delegate used to activate the page.</returns>
Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor descriptor); Func<PageContext, ViewContext, object> CreateActivator(CompiledPageActionDescriptor descriptor);
/// <summary> /// <summary>
/// Releases a Razor page. /// Releases a Razor page.
/// </summary> /// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param> /// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <returns>The delegate used to dispose the activated page.</returns> /// <returns>The delegate used to dispose the activated page.</returns>
Action<PageContext, object> CreateReleaser(CompiledPageActionDescriptor descriptor); Action<PageContext, ViewContext, object> CreateReleaser(CompiledPageActionDescriptor descriptor);
} }
} }

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Microsoft.AspNetCore.Mvc.RazorPages namespace Microsoft.AspNetCore.Mvc.RazorPages
{ {
@ -15,13 +16,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </summary> /// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param> /// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <returns>The Razor page factory.</returns> /// <returns>The Razor page factory.</returns>
Func<PageContext, object> CreatePageFactory(CompiledPageActionDescriptor descriptor); Func<PageContext, ViewContext, object> CreatePageFactory(CompiledPageActionDescriptor descriptor);
/// <summary> /// <summary>
/// Releases a Razor page. /// Releases a Razor page.
/// </summary> /// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param> /// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <returns>The delegate used to release the created page.</returns> /// <returns>The delegate used to release the created page.</returns>
Action<PageContext, object> CreatePageDisposer(CompiledPageActionDescriptor descriptor); Action<PageContext, ViewContext, object> CreatePageDisposer(CompiledPageActionDescriptor descriptor);
} }
} }

View File

@ -4,18 +4,19 @@
using System; using System;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
/// <summary> /// <summary>
/// <see cref="IPageActivatorProvider"/> that uses type activation to create Pages. /// <see cref="IPageActivatorProvider"/> that uses type activation to create Pages.
/// </summary> /// </summary>
public class DefaultPageActivator : IPageActivatorProvider public class DefaultPageActivatorProvider : IPageActivatorProvider
{ {
private readonly Action<PageContext, object> _disposer = Dispose; private readonly Action<PageContext, ViewContext, object> _disposer = Dispose;
/// <inheritdoc /> /// <inheritdoc />
public virtual Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor actionDescriptor) public virtual Func<PageContext, ViewContext, object> CreateActivator(CompiledPageActionDescriptor actionDescriptor)
{ {
if (actionDescriptor == null) if (actionDescriptor == null)
{ {
@ -34,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
return CreatePageFactory(pageTypeInfo); return CreatePageFactory(pageTypeInfo);
} }
public virtual Action<PageContext, object> CreateReleaser(CompiledPageActionDescriptor actionDescriptor) public virtual Action<PageContext, ViewContext, object> CreateReleaser(CompiledPageActionDescriptor actionDescriptor)
{ {
if (actionDescriptor == null) if (actionDescriptor == null)
{ {
@ -49,27 +50,33 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
return null; return null;
} }
private static Func<PageContext, object> CreatePageFactory(Type pageTypeInfo) private static Func<PageContext, ViewContext, object> CreatePageFactory(Type pageTypeInfo)
{ {
var parameter = Expression.Parameter(typeof(PageContext), "pageContext"); var parameter1 = Expression.Parameter(typeof(PageContext), "pageContext");
var parameter2 = Expression.Parameter(typeof(ViewContext), "viewContext");
// new Page(); // new Page();
var newExpression = Expression.New(pageTypeInfo); var newExpression = Expression.New(pageTypeInfo);
// () => new Page(); // () => new Page();
var pageFactory = Expression var pageFactory = Expression
.Lambda<Func<PageContext, object>>(newExpression, parameter) .Lambda<Func<PageContext, ViewContext, object>>(newExpression, parameter1, parameter2)
.Compile(); .Compile();
return pageFactory; return pageFactory;
} }
private static void Dispose(PageContext context, object page) private static void Dispose(PageContext context, ViewContext viewContext, object page)
{ {
if (context == null) if (context == null)
{ {
throw new ArgumentNullException(nameof(context)); throw new ArgumentNullException(nameof(context));
} }
if (viewContext == null)
{
throw new ArgumentNullException(nameof(viewContext));
}
if (page == null) if (page == null)
{ {
throw new ArgumentNullException(nameof(page)); throw new ArgumentNullException(nameof(page));
@ -77,20 +84,5 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
((IDisposable)page).Dispose(); ((IDisposable)page).Dispose();
} }
private static void NullDisposer(PageContext context, object page)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (page == null)
{
throw new ArgumentNullException(nameof(page));
}
// No-op
}
} }
} }

View File

@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
}; };
} }
public virtual Func<PageContext, object> CreatePageFactory(CompiledPageActionDescriptor actionDescriptor) public virtual Func<PageContext, ViewContext, object> CreatePageFactory(CompiledPageActionDescriptor actionDescriptor)
{ {
if (!typeof(Page).GetTypeInfo().IsAssignableFrom(actionDescriptor.PageTypeInfo)) if (!typeof(Page).GetTypeInfo().IsAssignableFrom(actionDescriptor.PageTypeInfo))
{ {
@ -57,17 +57,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
_modelMetadataProvider, _modelMetadataProvider,
_propertyAccessors); _propertyAccessors);
return (context) => return (pageContext, viewContext) =>
{ {
var page = (Page)activatorFactory(context); var page = (Page)activatorFactory(pageContext, viewContext);
page.PageContext = context; page.PageContext = pageContext;
page.Path = context.ActionDescriptor.RelativePath; page.Path = pageContext.ActionDescriptor.RelativePath;
propertyActivator.Activate(page, context); page.ViewContext = viewContext;
propertyActivator.Activate(page, viewContext);
return page; return page;
}; };
} }
public virtual Action<PageContext, object> CreatePageDisposer(CompiledPageActionDescriptor descriptor) public virtual Action<PageContext, ViewContext, object> CreatePageDisposer(CompiledPageActionDescriptor descriptor)
{ {
if (descriptor == null) if (descriptor == null)
{ {

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using System.Text.Encodings.Web; using System.Text.Encodings.Web;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
@ -65,9 +66,21 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
pageContext.ViewData.Model = result.Model; pageContext.ViewData.Model = result.Model;
} }
var view = new RazorView(_razorViewEngine, _razorPageActivator, pageContext.ViewStarts, result.Page, _htmlEncoder); var viewStarts = new IRazorPage[pageContext.ViewStartFactories.Count];
pageContext.View = view; for (var i = 0; i < pageContext.ViewStartFactories.Count; i++)
return ExecuteAsync(pageContext, result.ContentType, result.StatusCode); {
viewStarts[i] = pageContext.ViewStartFactories[i]();
}
var viewContext = result.Page.ViewContext;
viewContext.View = new RazorView(
_razorViewEngine,
_razorPageActivator,
viewStarts,
result.Page,
_htmlEncoder);
return ExecuteAsync(viewContext, result.ContentType, result.StatusCode);
} }
} }
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -13,8 +14,11 @@ using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{ {
@ -23,9 +27,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private readonly IPageHandlerMethodSelector _selector; private readonly IPageHandlerMethodSelector _selector;
private readonly PageContext _pageContext; private readonly PageContext _pageContext;
private readonly ParameterBinder _parameterBinder; private readonly ParameterBinder _parameterBinder;
private readonly ITempDataDictionaryFactory _tempDataFactory;
private readonly HtmlHelperOptions _htmlHelperOptions;
private CompiledPageActionDescriptor _actionDescriptor;
private Page _page; private Page _page;
private object _model; private object _model;
private ViewContext _viewContext;
private ExceptionContext _exceptionContext; private ExceptionContext _exceptionContext;
public PageActionInvoker( public PageActionInvoker(
@ -36,7 +44,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
IFilterMetadata[] filterMetadata, IFilterMetadata[] filterMetadata,
IList<IValueProviderFactory> valueProviderFactories, IList<IValueProviderFactory> valueProviderFactories,
PageActionInvokerCacheEntry cacheEntry, PageActionInvokerCacheEntry cacheEntry,
ParameterBinder parameterBinder) ParameterBinder parameterBinder,
ITempDataDictionaryFactory tempDataFactory,
HtmlHelperOptions htmlHelperOptions)
: base( : base(
diagnosticSource, diagnosticSource,
logger, logger,
@ -48,9 +58,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_pageContext = pageContext; _pageContext = pageContext;
CacheEntry = cacheEntry; CacheEntry = cacheEntry;
_parameterBinder = parameterBinder; _parameterBinder = parameterBinder;
_tempDataFactory = tempDataFactory;
_htmlHelperOptions = htmlHelperOptions;
_actionDescriptor = pageContext.ActionDescriptor;
} }
public PageActionInvokerCacheEntry CacheEntry { get; } // Internal for testing
internal PageActionInvokerCacheEntry CacheEntry { get; }
// Internal for testing
internal PageContext PageContext => _pageContext;
/// <remarks> /// <remarks>
/// <see cref="ResourceInvoker"/> for details on what the variables in this method represent. /// <see cref="ResourceInvoker"/> for details on what the variables in this method represent.
@ -77,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
if (_page != null && CacheEntry.ReleasePage != null) if (_page != null && CacheEntry.ReleasePage != null)
{ {
CacheEntry.ReleasePage(_pageContext, _page); CacheEntry.ReleasePage(_pageContext, _viewContext, _page);
} }
} }
@ -303,47 +321,36 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
} }
} }
private async Task ExecutePageAsync() private Task ExecutePageAsync()
{ {
var actionDescriptor = _pageContext.ActionDescriptor;
_page = (Page)CacheEntry.PageFactory(_pageContext);
_pageContext.Page = _page;
_pageContext.ValueProviderFactories = _valueProviderFactories; _pageContext.ValueProviderFactories = _valueProviderFactories;
IRazorPage[] viewStarts; // 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
if (CacheEntry.ViewStartFactories == null || CacheEntry.ViewStartFactories.Count == 0) // on the PageModel without instantiating the Page or ViewContext.
var hasPageModel = _actionDescriptor.HandlerTypeInfo != _actionDescriptor.PageTypeInfo;
if (hasPageModel)
{ {
viewStarts = Array.Empty<IRazorPage>(); return ExecutePageWithPageModelAsync();
} }
else else
{ {
viewStarts = new IRazorPage[CacheEntry.ViewStartFactories.Count]; return ExecutePageWithoutPageModelAsync();
for (var i = 0; i < viewStarts.Length; i++)
{
var pageFactory = CacheEntry.ViewStartFactories[i];
viewStarts[i] = pageFactory();
}
} }
_pageContext.ViewStarts = viewStarts; }
if (actionDescriptor.ModelTypeInfo == actionDescriptor.PageTypeInfo) private async Task ExecutePageWithPageModelAsync()
{ {
_model = _page; // Since this is a PageModel, we need to activate it, and then run a handler method on the model.
} //
else // We also know that the model is the pagemodel at this point.
{ Debug.Assert(_actionDescriptor.ModelTypeInfo == _actionDescriptor.HandlerTypeInfo);
_model = CacheEntry.ModelFactory(_pageContext); _model = CacheEntry.ModelFactory(_pageContext);
} _pageContext.ViewData.Model = _model;
if (_model != null)
{
_pageContext.ViewData.Model = _model;
}
if (CacheEntry.PropertyBinder != null) if (CacheEntry.PropertyBinder != null)
{ {
await CacheEntry.PropertyBinder(_page, _model); await CacheEntry.PropertyBinder(_pageContext, _model);
} }
// This is a workaround for not yet having proper filter for Pages. // This is a workaround for not yet having proper filter for Pages.
@ -359,44 +366,82 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
if (propertyFilter != null) if (propertyFilter != null)
{ {
object subject = _page; propertyFilter.Subject = _model;
if (_model != null)
{
subject = _model;
}
propertyFilter.Subject = subject;
propertyFilter.ApplyTempDataChanges(_pageContext.HttpContext); propertyFilter.ApplyTempDataChanges(_pageContext.HttpContext);
} }
IActionResult result = null; _result = await ExecuteHandlerMethod(_model);
if (_result is PageResult pageResult)
var handler = _selector.Select(_pageContext);
if (handler != null)
{ {
var arguments = await GetArguments(handler); // 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.
_viewContext = new ViewContext(
_pageContext,
NullView.Instance,
_pageContext.ViewData,
_tempDataFactory.GetTempData(_pageContext.HttpContext),
TextWriter.Null,
_htmlHelperOptions);
Func<object, object[], Task<IActionResult>> executor = null; _page = (Page)CacheEntry.PageFactory(_pageContext, _viewContext);
for (var i = 0; i < actionDescriptor.HandlerMethods.Count; i++)
pageResult.Page = _page;
pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData;
}
await _result.ExecuteResultAsync(_pageContext);
}
private async Task ExecutePageWithoutPageModelAsync()
{
// 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);
_page = (Page)CacheEntry.PageFactory(_pageContext, _viewContext);
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.
PageSaveTempDataPropertyFilter propertyFilter = null;
for (var i = 0; i < _filters.Length; i++)
{
propertyFilter = _filters[i] as PageSaveTempDataPropertyFilter;
if (propertyFilter != null)
{ {
if (object.ReferenceEquals(handler, actionDescriptor.HandlerMethods[i])) break;
{
executor = CacheEntry.Executors[i];
break;
}
} }
var instance = actionDescriptor.ModelTypeInfo == actionDescriptor.HandlerTypeInfo ? _model : _page;
result = await executor(instance, arguments);
} }
if (result == null) if (propertyFilter != null)
{ {
result = new PageResult(_page); propertyFilter.Subject = _model;
propertyFilter.ApplyTempDataChanges(_pageContext.HttpContext);
} }
await result.ExecuteResultAsync(_pageContext); _result = await ExecuteHandlerMethod(_model);
if (_result is PageResult pageResult)
{
// 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;
}
await _result.ExecuteResultAsync(_pageContext);
} }
private async Task<object[]> GetArguments(HandlerMethodDescriptor handler) private async Task<object[]> GetArguments(HandlerMethodDescriptor handler)
@ -409,7 +454,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var parameter = handler.Parameters[i]; var parameter = handler.Parameters[i];
var result = await _parameterBinder.BindModelAsync( var result = await _parameterBinder.BindModelAsync(
_page.PageContext, _pageContext,
valueProvider, valueProvider,
parameter, parameter,
value: null); value: null);
@ -431,6 +476,36 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
return arguments; return arguments;
} }
private async Task<IActionResult> ExecuteHandlerMethod(object instance)
{
IActionResult result = null;
var handler = _selector.Select(_pageContext);
if (handler != null)
{
var arguments = await GetArguments(handler);
Func<object, object[], Task<IActionResult>> executor = null;
for (var i = 0; i < _actionDescriptor.HandlerMethods.Count; i++)
{
if (object.ReferenceEquals(handler, _actionDescriptor.HandlerMethods[i]))
{
executor = CacheEntry.Executors[i];
break;
}
}
result = await executor(instance, arguments);
}
if (result == null)
{
result = new PageResult();
}
return result;
}
private async Task InvokeNextExceptionFilterAsync() private async Task InvokeNextExceptionFilterAsync()
{ {
try try

View File

@ -5,7 +5,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{ {
@ -13,16 +16,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{ {
public PageActionInvokerCacheEntry( public PageActionInvokerCacheEntry(
CompiledPageActionDescriptor actionDescriptor, CompiledPageActionDescriptor actionDescriptor,
Func<PageContext, object> pageFactory, Func<IModelMetadataProvider, ModelStateDictionary, ViewDataDictionary> viewDataFactory,
Action<PageContext, object> releasePage, Func<PageContext, ViewContext, object> pageFactory,
Action<PageContext, ViewContext, object> releasePage,
Func<PageContext, object> modelFactory, Func<PageContext, object> modelFactory,
Action<PageContext, object> releaseModel, Action<PageContext, object> releaseModel,
Func<Page, object, Task> propertyBinder, Func<PageContext, object, Task> propertyBinder,
Func<object, object[], Task<IActionResult>>[] executors, Func<object, object[], Task<IActionResult>>[] executors,
IReadOnlyList<Func<IRazorPage>> viewStartFactories, IReadOnlyList<Func<IRazorPage>> viewStartFactories,
FilterItem[] cacheableFilters) FilterItem[] cacheableFilters)
{ {
ActionDescriptor = actionDescriptor; ActionDescriptor = actionDescriptor;
ViewDataFactory = viewDataFactory;
PageFactory = pageFactory; PageFactory = pageFactory;
ReleasePage = releasePage; ReleasePage = releasePage;
ModelFactory = modelFactory; ModelFactory = modelFactory;
@ -35,12 +40,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public CompiledPageActionDescriptor ActionDescriptor { get; } public CompiledPageActionDescriptor ActionDescriptor { get; }
public Func<PageContext, object> PageFactory { get; } public Func<PageContext, ViewContext, object> PageFactory { get; }
/// <summary> /// <summary>
/// The action invoked to release a page. This may be <c>null</c>. /// The action invoked to release a page. This may be <c>null</c>.
/// </summary> /// </summary>
public Action<PageContext, object> ReleasePage { get; } public Action<PageContext, ViewContext, object> ReleasePage { get; }
public Func<PageContext, object> ModelFactory { get; } public Func<PageContext, object> ModelFactory { get; }
@ -53,10 +58,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
/// The delegate invoked to bind either the handler type (page or model). /// The delegate invoked to bind either the handler type (page or model).
/// This may be <c>null</c>. /// This may be <c>null</c>.
/// </summary> /// </summary>
public Func<Page, object, Task> PropertyBinder { get; } public Func<PageContext, object, Task> PropertyBinder { get; }
public Func<object, object[], Task<IActionResult>>[] Executors { get; } public Func<object, object[], Task<IActionResult>>[] Executors { get; }
public Func<IModelMetadataProvider, ModelStateDictionary, ViewDataDictionary> ViewDataFactory { get; }
/// <summary> /// <summary>
/// Gets the applicable ViewStart pages. /// Gets the applicable ViewStart pages.
/// </summary> /// </summary>

View File

@ -16,10 +16,10 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{ {
@ -146,14 +146,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
PageActionInvokerCacheEntry cacheEntry, PageActionInvokerCacheEntry cacheEntry,
IFilterMetadata[] filters) IFilterMetadata[] filters)
{ {
var tempData = _tempDataFactory.GetTempData(actionContext.HttpContext); var pageContext = new PageContext(actionContext)
var pageContext = new PageContext( {
actionContext, ActionDescriptor = cacheEntry.ActionDescriptor,
new ViewDataDictionary(_modelMetadataProvider, actionContext.ModelState), ViewData = cacheEntry.ViewDataFactory(_modelMetadataProvider, actionContext.ModelState),
tempData, ViewStartFactories = cacheEntry.ViewStartFactories.ToList(),
_htmlHelperOptions); };
pageContext.ActionDescriptor = cacheEntry.ActionDescriptor;
return new PageActionInvoker( return new PageActionInvoker(
_selector, _selector,
@ -163,7 +161,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
filters, filters,
new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories), new CopyOnWriteList<IValueProviderFactory>(_valueProviderFactories),
cacheEntry, cacheEntry,
_parameterBinder); _parameterBinder,
_tempDataFactory,
_htmlHelperOptions);
} }
private PageActionInvokerCacheEntry CreateCacheEntry( private PageActionInvokerCacheEntry CreateCacheEntry(
@ -173,6 +173,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var actionDescriptor = (PageActionDescriptor)context.ActionContext.ActionDescriptor; var actionDescriptor = (PageActionDescriptor)context.ActionContext.ActionDescriptor;
var compiledActionDescriptor = _loader.Load(actionDescriptor); var compiledActionDescriptor = _loader.Load(actionDescriptor);
var viewDataFactory = ViewDataDictionaryFactory.CreateFactory(compiledActionDescriptor.ModelTypeInfo);
var pageFactory = _pageFactoryProvider.CreatePageFactory(compiledActionDescriptor); var pageFactory = _pageFactoryProvider.CreatePageFactory(compiledActionDescriptor);
var pageDisposer = _pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor); var pageDisposer = _pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor);
var propertyBinder = PagePropertyBinderFactory.CreateBinder( var propertyBinder = PagePropertyBinderFactory.CreateBinder(
@ -194,6 +196,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
return new PageActionInvokerCacheEntry( return new PageActionInvokerCacheEntry(
compiledActionDescriptor, compiledActionDescriptor,
viewDataFactory,
pageFactory, pageFactory,
pageDisposer, pageDisposer,
modelFactory, modelFactory,

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{ {
public static class PagePropertyBinderFactory public static class PagePropertyBinderFactory
{ {
public static Func<Page, object, Task> CreateBinder( public static Func<PageContext, object, Task> CreateBinder(
ParameterBinder parameterBinder, ParameterBinder parameterBinder,
IModelMetadataProvider modelMetadataProvider, IModelMetadataProvider modelMetadataProvider,
CompiledPageActionDescriptor actionDescriptor) CompiledPageActionDescriptor actionDescriptor)
@ -45,20 +45,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
return Bind; return Bind;
Task Bind(Page page, object model) Task Bind(PageContext pageContext, object instance)
{ {
if (page == null)
{
throw new ArgumentNullException(nameof(page));
}
if (!isHandlerThePage && model == null)
{
throw new ArgumentNullException(nameof(model));
}
var pageContext = page.PageContext;
var instance = isHandlerThePage ? page : model;
return BindPropertiesAsync(parameterBinder, pageContext, instance, properties, metadata); return BindPropertiesAsync(parameterBinder, pageContext, instance, properties, metadata);
} }
} }

View File

@ -22,12 +22,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var razorView = (RazorView)context.View; var razorView = (RazorView)context.View;
if (ReferenceEquals(page, razorView.RazorPage)) if (ReferenceEquals(page, razorView.RazorPage))
{ {
var pageContext = (PageContext)context; var actionDescriptor = (CompiledPageActionDescriptor)context.ActionDescriptor;
var vddType = typeof(ViewDataDictionary<>); var vddType = typeof(ViewDataDictionary<>);
var modelTypeInfo = pageContext.ActionDescriptor.ModelTypeInfo ?? pageContext.ActionDescriptor.PageTypeInfo; var modelTypeInfo = actionDescriptor.ModelTypeInfo ?? actionDescriptor.PageTypeInfo;
vddType = vddType.MakeGenericType(modelTypeInfo.AsType()); vddType = vddType.MakeGenericType(modelTypeInfo.AsType());
context.ViewData = (ViewDataDictionary)Activator.CreateInstance(vddType, context.ViewData); context.ViewData = (ViewDataDictionary)Activator.CreateInstance(vddType, context.ViewData);

View File

@ -37,14 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
public PageContext PageContext { get; set; } public PageContext PageContext { get; set; }
/// <inheritdoc /> /// <inheritdoc />
public override ViewContext ViewContext public override ViewContext ViewContext { get; set; }
{
get => PageContext;
set
{
PageContext = (PageContext)value;
}
}
/// <summary> /// <summary>
/// Gets the <see cref="Http.HttpContext"/>. /// Gets the <see cref="Http.HttpContext"/>.
@ -502,7 +495,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// Returning a <see cref="PageResult"/> from a page handler method is equivalent to returning void. /// Returning a <see cref="PageResult"/> from a page handler method is equivalent to returning void.
/// The view associated with the page will be executed. /// The view associated with the page will be executed.
/// </remarks> /// </remarks>
public virtual PageResult Page() => new PageResult(this); public virtual PageResult Page() => new PageResult();
/// <summary> /// <summary>
/// Creates a <see cref="RedirectResult"/> object that redirects to the specified <paramref name="url"/>. /// Creates a <see cref="RedirectResult"/> object that redirects to the specified <paramref name="url"/>.

View File

@ -2,25 +2,22 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
namespace Microsoft.AspNetCore.Mvc.RazorPages namespace Microsoft.AspNetCore.Mvc.RazorPages
{ {
/// <summary> /// <summary>
/// The context associated with the current request for a Razor page. /// The context associated with the current request for a Razor page.
/// </summary> /// </summary>
public class PageContext : ViewContext public class PageContext : ActionContext
{ {
private CompiledPageActionDescriptor _actionDescriptor; private CompiledPageActionDescriptor _actionDescriptor;
private Page _page;
private IList<IValueProviderFactory> _valueProviderFactories; private IList<IValueProviderFactory> _valueProviderFactories;
private ViewDataDictionary _viewData;
private IList<Func<IRazorPage>> _viewStartFactories;
/// <summary> /// <summary>
/// Creates an empty <see cref="PageContext"/>. /// Creates an empty <see cref="PageContext"/>.
@ -36,53 +33,32 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// Initializes a new instance of <see cref="PageContext"/>. /// Initializes a new instance of <see cref="PageContext"/>.
/// </summary> /// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/>.</param> /// <param name="actionContext">The <see cref="ActionContext"/>.</param>
/// <param name="viewData">The <see cref="ViewDataDictionary"/>.</param> public PageContext(ActionContext actionContext)
/// <param name="tempDataDictionary">The <see cref="ITempDataDictionary"/>.</param> : base(actionContext)
/// <param name="htmlHelperOptions">The <see cref="HtmlHelperOptions"/> to apply to this instance.</param>
public PageContext(
ActionContext actionContext,
ViewDataDictionary viewData,
ITempDataDictionary tempDataDictionary,
HtmlHelperOptions htmlHelperOptions)
: base(actionContext, NullView.Instance, viewData, tempDataDictionary, TextWriter.Null, htmlHelperOptions)
{ {
} }
/// <summary> /// <summary>
/// Gets or sets the <see cref="PageActionDescriptor"/>. /// Gets or sets the <see cref="PageActionDescriptor"/>.
/// </summary> /// </summary>
public new CompiledPageActionDescriptor ActionDescriptor public virtual new CompiledPageActionDescriptor ActionDescriptor
{ {
get get
{ {
return _actionDescriptor; return _actionDescriptor;
} }
set set
{
_actionDescriptor = value;
base.ActionDescriptor = value;
}
}
public Page Page
{
get { return _page; }
set
{ {
if (value == null) if (value == null)
{ {
throw new ArgumentNullException(nameof(value)); throw new ArgumentNullException(nameof(value));
} }
_page = value; _actionDescriptor = value;
base.ActionDescriptor = value;
} }
} }
/// <summary>
/// Gets or sets the applicable _ViewStart instances.
/// </summary>
public IReadOnlyList<IRazorPage> ViewStarts { get; set; }
/// <summary> /// <summary>
/// Gets or sets the list of <see cref="IValueProviderFactory"/> instances for the current request. /// Gets or sets the list of <see cref="IValueProviderFactory"/> instances for the current request.
/// </summary> /// </summary>
@ -107,5 +83,45 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
_valueProviderFactories = value; _valueProviderFactories = value;
} }
} }
/// <summary>
/// Gets or sets <see cref="ViewDataDictionary"/>.
/// </summary>
public virtual ViewDataDictionary ViewData
{
get
{
return _viewData;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_viewData = value;
}
}
/// <summary>
/// Gets or sets the applicable _ViewStart instances.
/// </summary>
public virtual IList<Func<IRazorPage>> ViewStartFactories
{
get
{
return _viewStartFactories;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_viewStartFactories = value;
}
}
} }
} }

View File

@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
@ -25,48 +24,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
[PagesBaseClass] [PagesBaseClass]
public abstract class PageModel public abstract class PageModel
{ {
private IObjectModelValidator _objectValidator;
private IModelMetadataProvider _metadataProvider; private IModelMetadataProvider _metadataProvider;
private IModelBinderFactory _modelBinderFactory; private IModelBinderFactory _modelBinderFactory;
private IObjectModelValidator _objectValidator;
private ITempDataDictionary _tempData;
private IUrlHelper _urlHelper; private IUrlHelper _urlHelper;
/// <summary>
/// Gets or sets the <see cref="IUrlHelper"/>.
/// </summary>
public IUrlHelper Url
{
get
{
if (_urlHelper == null)
{
var factory = HttpContext?.RequestServices?.GetRequiredService<IUrlHelperFactory>();
_urlHelper = factory?.GetUrlHelper(PageContext);
}
return _urlHelper;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_urlHelper = value;
}
}
/// <summary> /// <summary>
/// Gets the <see cref="RazorPages.PageContext"/>. /// Gets the <see cref="RazorPages.PageContext"/>.
/// </summary> /// </summary>
[PageContext] [PageContext]
public PageContext PageContext { get; set; } public PageContext PageContext { get; set; }
/// <summary>
/// Gets the <see cref="ViewContext"/>.
/// </summary>
public ViewContext ViewContext => PageContext;
/// <summary> /// <summary>
/// Gets the <see cref="Http.HttpContext"/>. /// Gets the <see cref="Http.HttpContext"/>.
/// </summary> /// </summary>
@ -98,10 +67,61 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
public ClaimsPrincipal User => HttpContext?.User; public ClaimsPrincipal User => HttpContext?.User;
/// <summary> /// <summary>
/// Gets the <see cref="ITempDataDictionary"/> from the <see cref="PageContext"/>. /// Gets or sets <see cref="ITempDataDictionary"/> used by <see cref="PageResult"/>.
/// </summary> /// </summary>
/// <remarks>Returns null if <see cref="PageContext"/> is null.</remarks> public ITempDataDictionary TempData
public ITempDataDictionary TempData => PageContext?.TempData; {
get
{
if (_tempData == null)
{
var factory = HttpContext?.RequestServices?.GetRequiredService<ITempDataDictionaryFactory>();
_tempData = factory?.GetTempData(HttpContext);
}
return _tempData;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_tempData = value;
}
}
/// <summary>
/// Gets or sets the <see cref="IUrlHelper"/>.
/// </summary>
public IUrlHelper Url
{
get
{
if (_urlHelper == null)
{
var factory = HttpContext?.RequestServices?.GetRequiredService<IUrlHelperFactory>();
_urlHelper = factory?.GetUrlHelper(PageContext);
}
return _urlHelper;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_urlHelper = value;
}
}
/// <summary>
/// Gets or sets <see cref="ViewDataDictionary"/> used by <see cref="PageResult"/>.
/// </summary>
public ViewDataDictionary ViewData => PageContext?.ViewData;
private IObjectModelValidator ObjectValidator private IObjectModelValidator ObjectValidator
{ {
@ -142,11 +162,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
} }
} }
/// <summary>
/// Gets the <see cref="ViewDataDictionary"/>.
/// </summary>
public ViewDataDictionary ViewData => PageContext?.ViewData;
/// <summary> /// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="PageModel"/>'s current /// Updates the specified <paramref name="model"/> instance using values from the <see cref="PageModel"/>'s current
/// <see cref="IValueProvider"/>. /// <see cref="IValueProvider"/>.
@ -797,7 +812,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// Creates a <see cref="PageResult"/> object that renders the page. /// Creates a <see cref="PageResult"/> object that renders the page.
/// </summary> /// </summary>
/// <returns>The <see cref="PageResult"/>.</returns> /// <returns>The <see cref="PageResult"/>.</returns>
public virtual PageResult Page() => new PageResult(PageContext.Page, this); public virtual PageResult Page() => new PageResult();
/// <summary> /// <summary>
/// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>) with the /// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>) with the

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.RazorPages namespace Microsoft.AspNetCore.Mvc.RazorPages
@ -13,26 +14,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </summary> /// </summary>
public class PageResult : ActionResult public class PageResult : ActionResult
{ {
/// <summary>
/// Initializes a new instance of <see cref="PageResult"/>.
/// </summary>
/// <param name="page">The <see cref="RazorPages.PageBase"/> to render.</param>
public PageResult(PageBase page)
{
Page = page;
}
/// <summary>
/// Initializes a new instance of <see cref="PageResult"/> with the specified <paramref name="model"/>.
/// </summary>
/// <param name="page">The <see cref="RazorPages.PageBase"/> to render.</param>
/// <param name="model">The page model.</param>
public PageResult(PageBase page, object model)
{
Page = page;
Model = model;
}
/// <summary> /// <summary>
/// Gets or sets the Content-Type header for the response. /// Gets or sets the Content-Type header for the response.
/// </summary> /// </summary>
@ -41,12 +22,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// <summary> /// <summary>
/// Gets the page model. /// Gets the page model.
/// </summary> /// </summary>
public object Model { get; } public object Model => ViewData?.Model;
/// <summary> /// <summary>
/// Gets the <see cref="RazorPages.Page"/> to execute. /// Gets or sets the <see cref="PageBase"/> to be executed.
/// </summary> /// </summary>
public PageBase Page { get; } public PageBase Page { get; set; }
/// <summary>
/// Gets or sets the <see cref="ViewDataDictionary"/> for the page to be executed.
/// </summary>
public ViewDataDictionary ViewData { get; set; }
/// <summary> /// <summary>
/// Gets or sets the HTTP status code. /// Gets or sets the HTTP status code.
@ -56,14 +42,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// <inheritdoc /> /// <inheritdoc />
public override Task ExecuteResultAsync(ActionContext context) public override Task ExecuteResultAsync(ActionContext context)
{ {
if (!object.ReferenceEquals(context, Page.PageContext)) if (!(context is PageContext pageContext))
{ {
throw new ArgumentException( throw new ArgumentException(Resources.FormatPageViewResult_ContextIsInvalid(
Resources.FormatPageViewResult_ContextIsInvalid(nameof(context), nameof(Page))); nameof(context),
nameof(Page),
nameof(PageResult)));
} }
var executor = context.HttpContext.RequestServices.GetRequiredService<PageResultExecutor>(); var executor = context.HttpContext.RequestServices.GetRequiredService<PageResultExecutor>();
return executor.ExecuteAsync(Page.PageContext, this); return executor.ExecuteAsync(pageContext, this);
} }
} }
} }

View File

@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
=> string.Format(CultureInfo.CurrentCulture, GetString("ActivatedInstance_MustBeAnInstanceOf"), p0, p1); => string.Format(CultureInfo.CurrentCulture, GetString("ActivatedInstance_MustBeAnInstanceOf"), p0, p1);
/// <summary> /// <summary>
/// Argument '{0}' is not the same instance used to create '{1}'. /// The context used to execute '{0}' must be an instance of '{1}'. Returning a '{2}' from a controller is a not supported.
/// </summary> /// </summary>
internal static string PageViewResult_ContextIsInvalid internal static string PageViewResult_ContextIsInvalid
{ {
@ -61,10 +61,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
} }
/// <summary> /// <summary>
/// Argument '{0}' is not the same instance used to create '{1}'. /// The context used to execute '{0}' must be an instance of '{1}'. Returning a '{2}' from a controller is a not supported.
/// </summary> /// </summary>
internal static string FormatPageViewResult_ContextIsInvalid(object p0, object p1) internal static string FormatPageViewResult_ContextIsInvalid(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("PageViewResult_ContextIsInvalid"), p0, p1); => string.Format(CultureInfo.CurrentCulture, GetString("PageViewResult_ContextIsInvalid"), p0, p1, p2);
/// <summary> /// <summary>
/// Value cannot be null or empty. /// Value cannot be null or empty.

View File

@ -127,7 +127,7 @@
<value>Page created by '{0}' must be an instance of '{1}'.</value> <value>Page created by '{0}' must be an instance of '{1}'.</value>
</data> </data>
<data name="PageViewResult_ContextIsInvalid" xml:space="preserve"> <data name="PageViewResult_ContextIsInvalid" xml:space="preserve">
<value>Argument '{0}' is not the same instance used to create '{1}'.</value> <value>The context used to execute '{0}' must be an instance of '{1}'. Returning a '{2}' from a controller is a not supported.</value>
</data> </data>
<data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve"> <data name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
<value>Value cannot be null or empty.</value> <value>Value cannot be null or empty.</value>

View File

@ -0,0 +1,60 @@
// 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 System.Linq.Expressions;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
{
public static class ViewDataDictionaryFactory
{
public static Func<IModelMetadataProvider, ModelStateDictionary, ViewDataDictionary> CreateFactory(TypeInfo modelType)
{
if (modelType == null)
{
throw new ArgumentNullException(nameof(modelType));
}
var type = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
var constructor = type.GetConstructor(new Type[] { typeof(IModelMetadataProvider), typeof(ModelStateDictionary) });
Debug.Assert(constructor != null);
var parameter1 = Expression.Parameter(typeof(IModelMetadataProvider), "metadataProvider");
var parameter2 = Expression.Parameter(typeof(ModelStateDictionary), "modelState");
return
Expression.Lambda<Func<IModelMetadataProvider, ModelStateDictionary, ViewDataDictionary>>(
Expression.Convert(
Expression.New(constructor, parameter1, parameter2),
typeof(ViewDataDictionary)),
parameter1,
parameter2)
.Compile();
}
public static Func<ViewDataDictionary, ViewDataDictionary> CreateNestedFactory(TypeInfo modelType)
{
if (modelType == null)
{
throw new ArgumentNullException(nameof(modelType));
}
var type = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
var constructor = type.GetConstructor(new Type[] { typeof(ViewDataDictionary) });
Debug.Assert(constructor != null);
var parameter = Expression.Parameter(typeof(ViewDataDictionary), "viewDataDictionary");
return
Expression.Lambda<Func<ViewDataDictionary, ViewDataDictionary>>(
Expression.Convert(
Expression.New(constructor, parameter),
typeof(ViewDataDictionary)),
parameter)
.Compile();
}
}
}

View File

@ -4,20 +4,21 @@
using System; using System;
using System.Reflection; using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
public class DefaultPageActivatorTest public class DefaultPageActivatorProviderTest
{ {
[Fact] [Fact]
public void CreateActivator_ThrowsIfPageTypeInfoIsNull() public void CreateActivator_ThrowsIfPageTypeInfoIsNull()
{ {
// Arrange // Arrange
var descriptor = new CompiledPageActionDescriptor(); var descriptor = new CompiledPageActionDescriptor();
var activator = new DefaultPageActivator(); var activator = new DefaultPageActivatorProvider();
// Act & Assert // Act & Assert
ExceptionAssert.ThrowsArgument( ExceptionAssert.ThrowsArgument(
@ -33,16 +34,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
// Arrange // Arrange
var pageContext = new PageContext(); var pageContext = new PageContext();
var viewContext = new ViewContext();
var descriptor = new CompiledPageActionDescriptor var descriptor = new CompiledPageActionDescriptor
{ {
PageTypeInfo = type.GetTypeInfo(), PageTypeInfo = type.GetTypeInfo(),
}; };
var activator = new DefaultPageActivator();
var activator = new DefaultPageActivatorProvider();
// Act // Act
var factory = activator.CreateActivator(descriptor); var factory = activator.CreateActivator(descriptor);
var instance = factory(pageContext); var instance = factory(pageContext, viewContext);
// Assert // Assert
Assert.NotNull(instance); Assert.NotNull(instance);
@ -58,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
PageTypeInfo = typeof(PageWithoutParameterlessConstructor).GetTypeInfo(), PageTypeInfo = typeof(PageWithoutParameterlessConstructor).GetTypeInfo(),
}; };
var pageContext = new PageContext(); var pageContext = new PageContext();
var activator = new DefaultPageActivator(); var activator = new DefaultPageActivatorProvider();
// Act & Assert // Act & Assert
Assert.Throws<ArgumentException>(() => activator.CreateActivator(descriptor)); Assert.Throws<ArgumentException>(() => activator.CreateActivator(descriptor));
@ -71,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
// Arrange // Arrange
var context = new PageContext(); var context = new PageContext();
var activator = new DefaultPageActivator(); var activator = new DefaultPageActivatorProvider();
var page = new TestPage(); var page = new TestPage();
// Act // Act
@ -89,7 +92,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
// Arrange // Arrange
var context = new PageContext(); var context = new PageContext();
var activator = new DefaultPageActivator(); var viewContext = new ViewContext();
var activator = new DefaultPageActivatorProvider();
var page = new DisposablePage(); var page = new DisposablePage();
// Act & Assert // Act & Assert
@ -98,7 +102,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
PageTypeInfo = page.GetType().GetTypeInfo() PageTypeInfo = page.GetType().GetTypeInfo()
}); });
Assert.NotNull(disposer); Assert.NotNull(disposer);
disposer(context, page); disposer(context, viewContext, page);
// Assert // Assert
Assert.True(page.Disposed); Assert.True(page.Disposed);

View File

@ -53,11 +53,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
ActionDescriptor = descriptor ActionDescriptor = descriptor
}; };
var viewContext = new ViewContext();
var factoryProvider = CreatePageFactory(); var factoryProvider = CreatePageFactory();
// Act // Act
var factory = factoryProvider.CreatePageFactory(descriptor); var factory = factoryProvider.CreatePageFactory(descriptor);
var instance = factory(pageContext); var instance = factory(pageContext, viewContext);
// Assert // Assert
var testPage = Assert.IsType<TestPage>(instance); var testPage = Assert.IsType<TestPage>(instance);
@ -75,11 +76,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
PageTypeInfo = typeof(TestPage).GetTypeInfo(), PageTypeInfo = typeof(TestPage).GetTypeInfo(),
} }
}; };
var viewContext = new ViewContext();
var urlHelperFactory = new Mock<IUrlHelperFactory>(); var urlHelperFactory = new Mock<IUrlHelperFactory>();
var urlHelper = Mock.Of<IUrlHelper>(); var urlHelper = Mock.Of<IUrlHelper>();
urlHelperFactory.Setup(f => f.GetUrlHelper(pageContext)) urlHelperFactory
.Setup(f => f.GetUrlHelper(viewContext))
.Returns(urlHelper) .Returns(urlHelper)
.Verifiable(); .Verifiable();
var htmlEncoder = HtmlEncoder.Create(); var htmlEncoder = HtmlEncoder.Create();
var factoryProvider = CreatePageFactory( var factoryProvider = CreatePageFactory(
@ -88,7 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
// Act // Act
var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor); var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor);
var instance = factory(pageContext); var instance = factory(pageContext, viewContext);
// Assert // Assert
var testPage = Assert.IsType<TestPage>(instance); var testPage = Assert.IsType<TestPage>(instance);
@ -113,9 +119,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
ActionDescriptor = descriptor ActionDescriptor = descriptor
}; };
var viewContext = new ViewContext();
// Act // Act
var factory = CreatePageFactory().CreatePageFactory(descriptor); var factory = CreatePageFactory().CreatePageFactory(descriptor);
var instance = factory(pageContext); var instance = factory(pageContext, viewContext);
// Assert // Assert
var testPage = Assert.IsType<ViewDataTestPage>(instance); var testPage = Assert.IsType<ViewDataTestPage>(instance);
@ -135,11 +143,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
}, },
}; };
var viewContext = new ViewContext();
var factoryProvider = CreatePageFactory(); var factoryProvider = CreatePageFactory();
// Act // Act
var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor); var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor);
var instance = factory(pageContext); var instance = factory(pageContext, viewContext);
// Assert // Assert
var testPage = Assert.IsType<ViewDataTestPage>(instance); var testPage = Assert.IsType<ViewDataTestPage>(instance);
@ -158,11 +168,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
}, },
}; };
var viewContext = new ViewContext();
var factoryProvider = CreatePageFactory(); var factoryProvider = CreatePageFactory();
// Act // Act
var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor); var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor);
var instance = factory(pageContext); var instance = factory(pageContext, viewContext);
// Assert // Assert
var testPage = Assert.IsType<NonGenericViewDataTestPage>(instance); var testPage = Assert.IsType<NonGenericViewDataTestPage>(instance);
@ -170,7 +182,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
} }
[Fact] [Fact]
public void PageFactorySetsNestedVidewDataDictionaryWhenContextHasANonNullDictionary() public void PageFactory_SetsViewDataOnPage_FromPageContext()
{ {
// Arrange // Arrange
var modelMetadataProvider = new EmptyModelMetadataProvider(); var modelMetadataProvider = new EmptyModelMetadataProvider();
@ -180,21 +192,28 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
PageTypeInfo = typeof(TestPage).GetTypeInfo() PageTypeInfo = typeof(TestPage).GetTypeInfo()
}, },
ViewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary()) ViewData = new ViewDataDictionary<TestPage>(modelMetadataProvider, new ModelStateDictionary())
{ {
{ "test-key", "test-value" }, { "test-key", "test-value" },
} }
}; };
var viewContext = new ViewContext()
{
HttpContext = pageContext.HttpContext,
ViewData = pageContext.ViewData,
};
var factoryProvider = CreatePageFactory(); var factoryProvider = CreatePageFactory();
// Act // Act
var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor); var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor);
var instance = factory(pageContext); var instance = factory(pageContext, viewContext);
// Assert // Assert
var testPage = Assert.IsType<TestPage>(instance); var testPage = Assert.IsType<TestPage>(instance);
Assert.NotNull(testPage.ViewData); Assert.NotNull(testPage.ViewData);
Assert.Same(pageContext.ViewData, testPage.ViewData);
Assert.Equal("test-value", testPage.ViewData["test-key"]); Assert.Equal("test-value", testPage.ViewData["test-key"]);
} }
@ -205,6 +224,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
var serviceProvider = new ServiceCollection() var serviceProvider = new ServiceCollection()
.AddSingleton<ILogger>(NullLogger.Instance) .AddSingleton<ILogger>(NullLogger.Instance)
.BuildServiceProvider(); .BuildServiceProvider();
var pageContext = new PageContext var pageContext = new PageContext
{ {
ActionDescriptor = new CompiledPageActionDescriptor ActionDescriptor = new CompiledPageActionDescriptor
@ -215,14 +235,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
RequestServices = serviceProvider, RequestServices = serviceProvider,
}, },
};
var viewContext = new ViewContext()
{
HttpContext = pageContext.HttpContext,
}; };
var factoryProvider = CreatePageFactory(); var factoryProvider = CreatePageFactory();
// Act // Act
var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor); var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor);
var instance = factory(pageContext); var instance = factory(pageContext, viewContext);
// Assert // Assert
var testPage = Assert.IsType<PropertiesWithoutRazorInject>(instance); var testPage = Assert.IsType<PropertiesWithoutRazorInject>(instance);
@ -258,10 +282,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
private static IPageActivatorProvider CreateActivator() private static IPageActivatorProvider CreateActivator()
{ {
var activator = new Mock<IPageActivatorProvider>(); var activator = new Mock<IPageActivatorProvider>();
activator.Setup(a => a.CreateActivator(It.IsAny<CompiledPageActionDescriptor>())) activator
.Setup(a => a.CreateActivator(It.IsAny<CompiledPageActionDescriptor>()))
.Returns((CompiledPageActionDescriptor descriptor) => .Returns((CompiledPageActionDescriptor descriptor) =>
{ {
return (context) => Activator.CreateInstance(descriptor.PageTypeInfo.AsType()); return (context, viewContext) => Activator.CreateInstance(descriptor.PageTypeInfo.AsType());
}); });
return activator.Object; return activator.Object;
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Reflection; using System.Reflection;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -95,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
// Arrange // Arrange
var context = new PageContext(); var context = new PageContext();
var activator = new DefaultPageActivator(); var activator = new DefaultPageModelActivatorProvider();
var actionDescriptor = new CompiledPageActionDescriptor var actionDescriptor = new CompiledPageActionDescriptor
{ {
PageTypeInfo = pageType.GetTypeInfo(), PageTypeInfo = pageType.GetTypeInfo(),
@ -113,11 +114,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{ {
// Arrange // Arrange
var context = new PageContext(); var context = new PageContext();
var activator = new DefaultPageActivator();
var activator = new DefaultPageModelActivatorProvider();
var actionDescriptor = new CompiledPageActionDescriptor var actionDescriptor = new CompiledPageActionDescriptor
{ {
PageTypeInfo = typeof(DisposableModel).GetTypeInfo(), ModelTypeInfo = typeof(DisposableModel).GetTypeInfo(),
}; };
var model = new DisposableModel(); var model = new DisposableModel();
// Act & Assert // Act & Assert

View File

@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
@ -38,8 +39,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
FilterDescriptors = new FilterDescriptor[0], FilterDescriptors = new FilterDescriptor[0],
}; };
Func<PageContext, object> factory = _ => null; Func<PageContext, ViewContext, object> factory = (a, b) => null;
Action<PageContext, object> releaser = (_, __) => { }; Action<PageContext, ViewContext, object> releaser = (a, b, c) => { };
var loader = new Mock<IPageLoader>(); var loader = new Mock<IPageLoader>();
loader loader
@ -78,6 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Same(releaser, entry.ReleasePage); Assert.Same(releaser, entry.ReleasePage);
Assert.Null(entry.ModelFactory); Assert.Null(entry.ModelFactory);
Assert.Null(entry.ReleaseModel); Assert.Null(entry.ReleaseModel);
Assert.NotNull(entry.ViewDataFactory);
} }
[Fact] [Fact]
@ -90,8 +92,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
FilterDescriptors = new FilterDescriptor[0], FilterDescriptors = new FilterDescriptor[0],
}; };
Func<PageContext, object> factory = _ => null; Func<PageContext, ViewContext, object> factory = (a, b) => null;
Action<PageContext, object> releaser = (_, __) => { }; Action<PageContext, ViewContext, object> releaser = (a, b, c) => { };
Func<PageContext, object> modelFactory = _ => null; Func<PageContext, object> modelFactory = _ => null;
Action<PageContext, object> modelDisposer = (_, __) => { }; Action<PageContext, object> modelDisposer = (_, __) => { };
@ -134,7 +136,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Assert // Assert
Assert.NotNull(context.Result); Assert.NotNull(context.Result);
var actionInvoker = Assert.IsType<PageActionInvoker>(context.Result); var actionInvoker = Assert.IsType<PageActionInvoker>(context.Result);
var entry = actionInvoker.CacheEntry; var entry = actionInvoker.CacheEntry;
var compiledPageActionDescriptor = Assert.IsType<CompiledPageActionDescriptor>(entry.ActionDescriptor); var compiledPageActionDescriptor = Assert.IsType<CompiledPageActionDescriptor>(entry.ActionDescriptor);
Assert.Equal(descriptor.RelativePath, compiledPageActionDescriptor.RelativePath); Assert.Equal(descriptor.RelativePath, compiledPageActionDescriptor.RelativePath);
@ -142,6 +146,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Same(releaser, entry.ReleasePage); Assert.Same(releaser, entry.ReleasePage);
Assert.Same(modelFactory, entry.ModelFactory); Assert.Same(modelFactory, entry.ModelFactory);
Assert.Same(modelDisposer, entry.ReleaseModel); Assert.Same(modelDisposer, entry.ReleaseModel);
Assert.NotNull(entry.ViewDataFactory);
var pageContext = actionInvoker.PageContext;
Assert.Same(compiledPageActionDescriptor, pageContext.ActionDescriptor);
Assert.Same(context.ActionContext.HttpContext, pageContext.HttpContext);
Assert.Same(context.ActionContext.ModelState, pageContext.ModelState);
Assert.Same(context.ActionContext.RouteData, pageContext.RouteData);
Assert.Empty(pageContext.ValueProviderFactories);
Assert.NotNull(Assert.IsType<ViewDataDictionary<TestPageModel>>(pageContext.ViewData));
Assert.Empty(pageContext.ViewStartFactories);
} }
[Fact] [Fact]
@ -476,16 +490,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
PageActionDescriptor descriptor, PageActionDescriptor descriptor,
Type pageType = null) Type pageType = null)
{ {
pageType = pageType ?? typeof(object);
var pageTypeInfo = pageType.GetTypeInfo();
TypeInfo modelTypeInfo = null; TypeInfo modelTypeInfo = null;
if (pageType != null) if (pageType != null)
{ {
modelTypeInfo = pageType.GetTypeInfo().GetProperty("Model")?.PropertyType.GetTypeInfo(); modelTypeInfo = pageTypeInfo.GetProperty("Model")?.PropertyType.GetTypeInfo();
} }
return new CompiledPageActionDescriptor(descriptor) return new CompiledPageActionDescriptor(descriptor)
{ {
ModelTypeInfo = modelTypeInfo, HandlerTypeInfo = modelTypeInfo ?? pageTypeInfo,
PageTypeInfo = (pageType ?? typeof(object)).GetTypeInfo() ModelTypeInfo = modelTypeInfo ?? pageTypeInfo,
PageTypeInfo = pageTypeInfo,
}; };
} }

View File

@ -14,8 +14,10 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewEngines;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -344,10 +346,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
.Verifiable(); .Verifiable();
var filter3 = new Mock<IAuthorizationFilter>(MockBehavior.Strict); var filter3 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
var actionDescriptor = new CompiledPageActionDescriptor();
var actionDescriptor = new CompiledPageActionDescriptor()
{
HandlerTypeInfo = typeof(TestPage).GetTypeInfo(),
ModelTypeInfo = typeof(TestPage).GetTypeInfo(),
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
};
var cacheEntry = new PageActionInvokerCacheEntry( var cacheEntry = new PageActionInvokerCacheEntry(
actionDescriptor, actionDescriptor,
(context) => createCalled = true, null,
(context, viewContext) => createCalled = true,
null, null,
(context) => null, (context) => null,
null, null,
@ -400,10 +410,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var filter3 = new Mock<IAuthorizationFilter>(MockBehavior.Strict); var filter3 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
var actionDescriptor = new CompiledPageActionDescriptor(); var actionDescriptor = new CompiledPageActionDescriptor()
{
HandlerTypeInfo = typeof(TestPage).GetTypeInfo(),
ModelTypeInfo = typeof(TestPage).GetTypeInfo(),
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
};
var cacheEntry = new PageActionInvokerCacheEntry( var cacheEntry = new PageActionInvokerCacheEntry(
actionDescriptor, actionDescriptor,
(context) => createCalled = true, null,
(context, viewContext) => createCalled = true,
null, null,
(context) => null, (context) => null,
null, null,
@ -540,6 +557,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
PageResultExecutor executor = null, PageResultExecutor executor = null,
IPageHandlerMethodSelector selector = null, IPageHandlerMethodSelector selector = null,
PageActionInvokerCacheEntry cacheEntry = null, PageActionInvokerCacheEntry cacheEntry = null,
ITempDataDictionaryFactory tempDataFactory = null,
int maxAllowedErrorsInModelState = 200, int maxAllowedErrorsInModelState = 200,
List<IValueProviderFactory> valueProviderFactories = null, List<IValueProviderFactory> valueProviderFactories = null,
RouteData routeData = null, RouteData routeData = null,
@ -572,15 +590,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
httpContext: httpContext, httpContext: httpContext,
routeData: routeData, routeData: routeData,
actionDescriptor: actionDescriptor); actionDescriptor: actionDescriptor);
var pageContext = new PageContext( var pageContext = new PageContext(actionContext)
actionContext,
new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()),
Mock.Of<ITempDataDictionary>(),
new HtmlHelperOptions())
{ {
ActionDescriptor = actionDescriptor ActionDescriptor = actionDescriptor,
}; };
var viewDataFactory = ViewDataDictionaryFactory.CreateFactory(actionDescriptor.ModelTypeInfo);
pageContext.ViewData = viewDataFactory(new EmptyModelMetadataProvider(), pageContext.ModelState);
if (selector == null) if (selector == null)
{ {
selector = Mock.Of<IPageHandlerMethodSelector>(); selector = Mock.Of<IPageHandlerMethodSelector>();
@ -596,7 +613,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
logger = NullLogger.Instance; logger = NullLogger.Instance;
} }
Func<PageContext, object> pageFactory = (context) => if (tempDataFactory == null)
{
tempDataFactory = Mock.Of<ITempDataDictionaryFactory>(m => m.GetTempData(It.IsAny<HttpContext>()) == Mock.Of<ITempDataDictionary>());
}
Func<PageContext, ViewContext, object> pageFactory = (context, viewContext) =>
{ {
var instance = (Page)Activator.CreateInstance(actionDescriptor.PageTypeInfo.AsType()); var instance = (Page)Activator.CreateInstance(actionDescriptor.PageTypeInfo.AsType());
instance.PageContext = context; instance.PageContext = context;
@ -605,8 +627,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
cacheEntry = new PageActionInvokerCacheEntry( cacheEntry = new PageActionInvokerCacheEntry(
actionDescriptor, actionDescriptor,
viewDataFactory,
pageFactory, pageFactory,
(c, page) => { (page as IDisposable)?.Dispose(); }, (c, viewContext, page) => { (page as IDisposable)?.Dispose(); },
_ => Activator.CreateInstance(actionDescriptor.ModelTypeInfo.AsType()), _ => Activator.CreateInstance(actionDescriptor.ModelTypeInfo.AsType()),
(c, model) => { (model as IDisposable)?.Dispose(); }, (c, model) => { (model as IDisposable)?.Dispose(); },
null, null,
@ -622,7 +645,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
filters, filters,
valueProviderFactories.AsReadOnly(), valueProviderFactories.AsReadOnly(),
cacheEntry, cacheEntry,
GetParameterBinder()); GetParameterBinder(),
tempDataFactory,
new HtmlHelperOptions());
return invoker; return invoker;
} }

View File

@ -199,7 +199,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}; };
// Act // Act
await factory(page, null); await factory(page.PageContext, page);
// Assert // Assert
Assert.Equal(10, page.Id); Assert.Equal(10, page.Id);
@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var model = new PageModelWithProperty(); var model = new PageModelWithProperty();
// Act // Act
await factory(page, model); await factory(page.PageContext, model);
// Assert // Assert
// Verify that the page properties were not bound. // Verify that the page properties were not bound.
@ -314,7 +314,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var defaultValue = model.PropertyWithDefaultValue; var defaultValue = model.PropertyWithDefaultValue;
// Act // Act
await factory(page, model); await factory(page.PageContext, model);
// Assert // Assert
Assert.Equal(defaultValue, model.PropertyWithDefaultValue); Assert.Equal(defaultValue, model.PropertyWithDefaultValue);
@ -375,7 +375,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var model = new PageModelWithSupportsGetProperty(); var model = new PageModelWithSupportsGetProperty();
// Act // Act
await factory(page, model); await factory(page.PageContext, model);
// Assert // Assert
Assert.Equal("value", model.SupportsGet); Assert.Equal("value", model.SupportsGet);
@ -434,7 +434,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var model = new PageModelWithSupportsGetProperty(); var model = new PageModelWithSupportsGetProperty();
// Act // Act
await factory(page, model); await factory(page.PageContext, model);
// Assert // Assert
Assert.Equal("value", model.SupportsGet); Assert.Equal("value", model.SupportsGet);

View File

@ -1406,15 +1406,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState); var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider(); var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewDataDictionary = new ViewDataDictionary(modelMetadataProvider, modelState); var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = Mock.Of<ITempDataDictionary>(); var pageContext = new PageContext(actionContext)
var pageContext = new PageContext(actionContext, viewDataDictionary, tempData, new HtmlHelperOptions()); {
ViewData = viewData,
};
var page = new TestPage var page = new TestPage
{ {
PageContext = pageContext, PageContext = pageContext,
}; };
pageContext.Page = page;
var pageModel = new TestPageModel var pageModel = new TestPageModel
{ {
@ -1423,13 +1424,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
// Act & Assert // Act & Assert
Assert.Same(pageContext, pageModel.PageContext); Assert.Same(pageContext, pageModel.PageContext);
Assert.Same(pageContext, pageModel.ViewContext);
Assert.Same(httpContext, pageModel.HttpContext); Assert.Same(httpContext, pageModel.HttpContext);
Assert.Same(httpContext.Request, pageModel.Request); Assert.Same(httpContext.Request, pageModel.Request);
Assert.Same(httpContext.Response, pageModel.Response); Assert.Same(httpContext.Response, pageModel.Response);
Assert.Same(modelState, pageModel.ModelState); Assert.Same(modelState, pageModel.ModelState);
Assert.Same(viewDataDictionary, pageModel.ViewData); Assert.Same(viewData, pageModel.ViewData);
Assert.Same(tempData, pageModel.TempData);
} }
[Fact] [Fact]
@ -1483,10 +1482,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
var page = new TestPage(); var page = new TestPage();
var pageModel = new TestPageModel var pageModel = new TestPageModel
{ {
PageContext = new PageContext PageContext = new PageContext()
{
Page = page,
}
}; };
// Act // Act
@ -1494,7 +1490,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
// Assert // Assert
var pageResult = Assert.IsType<PageResult>(result); var pageResult = Assert.IsType<PageResult>(result);
Assert.Same(page, pageResult.Page); Assert.Null(pageResult.Page); // This is set by the invoker
} }
private class ContentPageModel : PageModel private class ContentPageModel : PageModel

View File

@ -9,8 +9,10 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.TestCommon;
using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Moq; using Moq;
@ -28,17 +30,24 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
var modelState = new ModelStateDictionary(); var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState); var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider(); var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewDataDictionary = new ViewDataDictionary(modelMetadataProvider, modelState); var viewData = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = Mock.Of<ITempDataDictionary>(); var tempData = Mock.Of<ITempDataDictionary>();
var pageContext = new PageContext(actionContext, viewDataDictionary, tempData, new HtmlHelperOptions());
var pageContext = new PageContext(actionContext)
{
ViewData = viewData,
};
var viewContext = new ViewContext(pageContext, NullView.Instance, viewData, tempData, TextWriter.Null, new HtmlHelperOptions());
var page = new TestPage var page = new TestPage
{ {
PageContext = pageContext, PageContext = pageContext,
ViewContext = viewContext,
}; };
// Act & Assert // Act & Assert
Assert.Same(pageContext, page.ViewContext); Assert.Same(pageContext, page.PageContext);
Assert.Same(viewContext, page.ViewContext);
Assert.Same(httpContext, page.HttpContext); Assert.Same(httpContext, page.HttpContext);
Assert.Same(httpContext.Request, page.Request); Assert.Same(httpContext.Request, page.Request);
Assert.Same(httpContext.Response, page.Response); Assert.Same(httpContext.Response, page.Response);

View File

@ -3,11 +3,13 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Moq; using Moq;
using Xunit; using Xunit;
@ -164,7 +166,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
var testProp = pageType.GetProperty(nameof(TestPageString.Test)); var testProp = pageType.GetProperty(nameof(TestPageString.Test));
var test2Prop = pageType.GetProperty(nameof(TestPageString.Test2)); var test2Prop = pageType.GetProperty(nameof(TestPageString.Test2));
provider.TempDataProperties = new List<TempDataProperty> { provider.TempDataProperties = new List<TempDataProperty>
{
new TempDataProperty(testProp, testProp.GetValue, testProp.SetValue), new TempDataProperty(testProp, testProp.GetValue, testProp.SetValue),
new TempDataProperty(test2Prop, test2Prop.GetValue, test2Prop.SetValue) new TempDataProperty(test2Prop, test2Prop.GetValue, test2Prop.SetValue)
}; };
@ -177,15 +180,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal
Assert.Null(page.Test2); Assert.Null(page.Test2);
} }
private static PageContext CreateViewContext(HttpContext httpContext, ITempDataDictionary tempData) private static ViewContext CreateViewContext(HttpContext httpContext, ITempDataDictionary tempData)
{ {
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var metadataProvider = new EmptyModelMetadataProvider(); var metadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary()); var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary());
var viewContext = new PageContext( var viewContext = new ViewContext(
actionContext, actionContext,
NullView.Instance,
viewData, viewData,
tempData, tempData,
TextWriter.Null,
new HtmlHelperOptions()); new HtmlHelperOptions());
return viewContext; return viewContext;