From 2992f8e38ae338fdbcd3de99baa40033d1be8cb4 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 16 May 2017 13:49:24 -0700 Subject: [PATCH] Separate PageContext and ViewContext This change decouples PageContext and ViewContext completely. --- .../Properties/Resources.Designer.cs | 4 +- .../Internal/RazorPagePropertyActivator.cs | 79 ++----- .../MvcRazorPagesMvcCoreBuilderExtensions.cs | 2 +- .../IPageActivatorProvider.cs | 5 +- .../IPageFactoryProvider.cs | 5 +- ...tor.cs => DefaultPageActivatorProvider.cs} | 38 ++-- .../Infrastructure/DefaultPageFactory.cs | 15 +- .../Infrastructure/PageResultExecutor.cs | 19 +- .../Internal/PageActionInvoker.cs | 193 ++++++++++++------ .../Internal/PageActionInvokerCacheEntry.cs | 19 +- .../Internal/PageActionInvokerProvider.cs | 23 ++- .../Internal/PagePropertyBinderFactory.cs | 16 +- .../Internal/PassThruRazorPageActivator.cs | 6 +- .../PageBase.cs | 11 +- .../PageContext.cs | 80 +++++--- .../PageModel.cs | 99 +++++---- .../PageResult.cs | 42 ++-- .../Properties/Resources.Designer.cs | 8 +- .../Resources.resx | 2 +- .../Internal/ViewDataDictionaryFactory.cs | 60 ++++++ ...cs => DefaultPageActivatorProviderTest.cs} | 20 +- ...t.cs => DefaultPageFactoryProviderTest.cs} | 49 +++-- .../DefaultPageModelActivatorProviderTest.cs | 9 +- .../Internal/PageActionInvokerProviderTest.cs | 32 ++- .../Internal/PageActionInvokerTest.cs | 51 +++-- .../Internal/PagePropertyBinderFactoryTest.cs | 10 +- .../PageModelTest.cs | 20 +- .../PageTest.cs | 15 +- .../PageSaveTempDataPropertyFilterTest.cs | 11 +- 29 files changed, 567 insertions(+), 376 deletions(-) rename src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/{DefaultPageActivator.cs => DefaultPageActivatorProvider.cs} (66%) create mode 100644 src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataDictionaryFactory.cs rename test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/{DefaultPageActivatorTest.cs => DefaultPageActivatorProviderTest.cs} (86%) rename test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/{DefaultPageFactoryTest.cs => DefaultPageFactoryProviderTest.cs} (87%) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index ce3db79de4..720dbacd87 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1285,7 +1285,7 @@ namespace Microsoft.AspNetCore.Mvc.Core => string.Format(CultureInfo.CurrentCulture, GetString("NoRoutesMatchedForPage"), p0); /// - /// 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. /// internal static string UrlHelper_RelativePagePathIsNotSupported { @@ -1293,7 +1293,7 @@ namespace Microsoft.AspNetCore.Mvc.Core } /// - /// 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. /// internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0) => string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorPagePropertyActivator.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorPagePropertyActivator.cs index 67b90cc6a4..8453d1e006 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorPagePropertyActivator.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorPagePropertyActivator.cs @@ -3,12 +3,12 @@ using System; using System.Diagnostics; -using System.Linq.Expressions; using System.Reflection; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Internal; @@ -16,8 +16,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { public class RazorPagePropertyActivator { - private delegate ViewDataDictionary CreateViewDataNestedDelegate(ViewDataDictionary source); - private delegate ViewDataDictionary CreateViewDataRootDelegate(ModelStateDictionary modelState); + private readonly IModelMetadataProvider _metadataProvider; + private readonly Func _rootFactory; + private readonly Func _nestedFactory; + private readonly Type _viewDataDictionaryType; + private readonly PropertyActivator[] _propertyActivators; public RazorPagePropertyActivator( Type pageType, @@ -25,26 +28,19 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal IModelMetadataProvider metadataProvider, PropertyValueAccessors propertyValueAccessors) { - var viewDataType = typeof(ViewDataDictionary<>).MakeGenericType(modelType); - ViewDataDictionaryType = viewDataType; - CreateViewDataNested = GetCreateViewDataNested(viewDataType); - CreateViewDataRoot = GetCreateViewDataRoot(viewDataType, metadataProvider); + _metadataProvider = metadataProvider; - PropertyActivators = PropertyActivator.GetPropertiesToActivate( + _viewDataDictionaryType = typeof(ViewDataDictionary<>).MakeGenericType(modelType); + _rootFactory = ViewDataDictionaryFactory.CreateFactory(modelType.GetTypeInfo()); + _nestedFactory = ViewDataDictionaryFactory.CreateNestedFactory(modelType.GetTypeInfo()); + + _propertyActivators = PropertyActivator.GetPropertiesToActivate( pageType, typeof(RazorInjectAttribute), propertyInfo => CreateActivateInfo(propertyInfo, propertyValueAccessors), includeNonPublic: true); } - private PropertyActivator[] PropertyActivators { get; } - - private Type ViewDataDictionaryType { get; } - - private CreateViewDataNestedDelegate CreateViewDataNested { get; } - - private CreateViewDataRootDelegate CreateViewDataRoot { get; } - public void Activate(object page, ViewContext context) { if (context == null) @@ -54,9 +50,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal 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); } } @@ -68,58 +64,17 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal if (context.ViewData == null) { // Create ViewDataDictionary(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(ViewDataDictionary). - return CreateViewDataNested(context.ViewData); + return _nestedFactory(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(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(castNewCall, parameterExpression); - return lambda.Compile(); - } - private static PropertyActivator CreateActivateInfo( PropertyInfo property, PropertyValueAccessors valueAccessors) diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs index 5e194b0d28..7cf4d35c01 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs @@ -113,7 +113,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageActivatorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageActivatorProvider.cs index 98ebb5a915..80578d5f17 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageActivatorProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageActivatorProvider.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Mvc.Rendering; namespace Microsoft.AspNetCore.Mvc.RazorPages { @@ -15,13 +16,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// /// The . /// The delegate used to activate the page. - Func CreateActivator(CompiledPageActionDescriptor descriptor); + Func CreateActivator(CompiledPageActionDescriptor descriptor); /// /// Releases a Razor page. /// /// The . /// The delegate used to dispose the activated page. - Action CreateReleaser(CompiledPageActionDescriptor descriptor); + Action CreateReleaser(CompiledPageActionDescriptor descriptor); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageFactoryProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageFactoryProvider.cs index dff6addb1a..a3b366784c 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageFactoryProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageFactoryProvider.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Mvc.Rendering; namespace Microsoft.AspNetCore.Mvc.RazorPages { @@ -15,13 +16,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// /// The . /// The Razor page factory. - Func CreatePageFactory(CompiledPageActionDescriptor descriptor); + Func CreatePageFactory(CompiledPageActionDescriptor descriptor); /// /// Releases a Razor page. /// /// The . /// The delegate used to release the created page. - Action CreatePageDisposer(CompiledPageActionDescriptor descriptor); + Action CreatePageDisposer(CompiledPageActionDescriptor descriptor); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivator.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivatorProvider.cs similarity index 66% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivator.cs rename to src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivatorProvider.cs index 5b06a51115..f0ec8404a9 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivator.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivatorProvider.cs @@ -4,18 +4,19 @@ using System; using System.Linq.Expressions; using System.Reflection; +using Microsoft.AspNetCore.Mvc.Rendering; namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { /// /// that uses type activation to create Pages. /// - public class DefaultPageActivator : IPageActivatorProvider + public class DefaultPageActivatorProvider : IPageActivatorProvider { - private readonly Action _disposer = Dispose; + private readonly Action _disposer = Dispose; /// - public virtual Func CreateActivator(CompiledPageActionDescriptor actionDescriptor) + public virtual Func CreateActivator(CompiledPageActionDescriptor actionDescriptor) { if (actionDescriptor == null) { @@ -34,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure return CreatePageFactory(pageTypeInfo); } - public virtual Action CreateReleaser(CompiledPageActionDescriptor actionDescriptor) + public virtual Action CreateReleaser(CompiledPageActionDescriptor actionDescriptor) { if (actionDescriptor == null) { @@ -49,27 +50,33 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure return null; } - private static Func CreatePageFactory(Type pageTypeInfo) + private static Func 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(); var newExpression = Expression.New(pageTypeInfo); // () => new Page(); var pageFactory = Expression - .Lambda>(newExpression, parameter) + .Lambda>(newExpression, parameter1, parameter2) .Compile(); return pageFactory; } - private static void Dispose(PageContext context, object page) + private static void Dispose(PageContext context, ViewContext viewContext, object page) { if (context == null) { throw new ArgumentNullException(nameof(context)); } + if (viewContext == null) + { + throw new ArgumentNullException(nameof(viewContext)); + } + if (page == null) { throw new ArgumentNullException(nameof(page)); @@ -77,20 +84,5 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure ((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 - } } } diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactory.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactory.cs index f815b6d4ff..e4b21d0938 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactory.cs @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure }; } - public virtual Func CreatePageFactory(CompiledPageActionDescriptor actionDescriptor) + public virtual Func CreatePageFactory(CompiledPageActionDescriptor actionDescriptor) { if (!typeof(Page).GetTypeInfo().IsAssignableFrom(actionDescriptor.PageTypeInfo)) { @@ -57,17 +57,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure _modelMetadataProvider, _propertyAccessors); - return (context) => + return (pageContext, viewContext) => { - var page = (Page)activatorFactory(context); - page.PageContext = context; - page.Path = context.ActionDescriptor.RelativePath; - propertyActivator.Activate(page, context); + var page = (Page)activatorFactory(pageContext, viewContext); + page.PageContext = pageContext; + page.Path = pageContext.ActionDescriptor.RelativePath; + page.ViewContext = viewContext; + propertyActivator.Activate(page, viewContext); return page; }; } - public virtual Action CreatePageDisposer(CompiledPageActionDescriptor descriptor) + public virtual Action CreatePageDisposer(CompiledPageActionDescriptor descriptor) { if (descriptor == null) { diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs index 629a2b1f3e..93e4ec574d 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Linq; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Internal; @@ -65,9 +66,21 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure pageContext.ViewData.Model = result.Model; } - var view = new RazorView(_razorViewEngine, _razorPageActivator, pageContext.ViewStarts, result.Page, _htmlEncoder); - pageContext.View = view; - return ExecuteAsync(pageContext, result.ContentType, result.StatusCode); + var viewStarts = new IRazorPage[pageContext.ViewStartFactories.Count]; + for (var i = 0; i < pageContext.ViewStartFactories.Count; i++) + { + 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); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs index b9443fa298..e9f6642963 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Reflection; using System.Runtime.ExceptionServices; using System.Threading.Tasks; @@ -13,8 +14,11 @@ using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { @@ -23,9 +27,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private readonly IPageHandlerMethodSelector _selector; private readonly PageContext _pageContext; private readonly ParameterBinder _parameterBinder; + private readonly ITempDataDictionaryFactory _tempDataFactory; + private readonly HtmlHelperOptions _htmlHelperOptions; + private CompiledPageActionDescriptor _actionDescriptor; private Page _page; private object _model; + private ViewContext _viewContext; private ExceptionContext _exceptionContext; public PageActionInvoker( @@ -36,7 +44,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal IFilterMetadata[] filterMetadata, IList valueProviderFactories, PageActionInvokerCacheEntry cacheEntry, - ParameterBinder parameterBinder) + ParameterBinder parameterBinder, + ITempDataDictionaryFactory tempDataFactory, + HtmlHelperOptions htmlHelperOptions) : base( diagnosticSource, logger, @@ -48,9 +58,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal _pageContext = pageContext; CacheEntry = cacheEntry; _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; /// /// 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) { - 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; - IRazorPage[] viewStarts; - - if (CacheEntry.ViewStartFactories == null || CacheEntry.ViewStartFactories.Count == 0) + // 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) { - viewStarts = Array.Empty(); + return ExecutePageWithPageModelAsync(); } else { - viewStarts = new IRazorPage[CacheEntry.ViewStartFactories.Count]; - for (var i = 0; i < viewStarts.Length; i++) - { - var pageFactory = CacheEntry.ViewStartFactories[i]; - viewStarts[i] = pageFactory(); - } + return ExecutePageWithoutPageModelAsync(); } - _pageContext.ViewStarts = viewStarts; + } - if (actionDescriptor.ModelTypeInfo == actionDescriptor.PageTypeInfo) - { - _model = _page; - } - else - { - _model = CacheEntry.ModelFactory(_pageContext); - } - - if (_model != null) - { - _pageContext.ViewData.Model = _model; - } + 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; 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. @@ -359,44 +366,82 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal if (propertyFilter != null) { - object subject = _page; - - if (_model != null) - { - subject = _model; - } - - propertyFilter.Subject = subject; + propertyFilter.Subject = _model; propertyFilter.ApplyTempDataChanges(_pageContext.HttpContext); } - IActionResult result = null; - - var handler = _selector.Select(_pageContext); - if (handler != null) + _result = await ExecuteHandlerMethod(_model); + if (_result is PageResult pageResult) { - 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> executor = null; - for (var i = 0; i < actionDescriptor.HandlerMethods.Count; i++) + _page = (Page)CacheEntry.PageFactory(_pageContext, _viewContext); + + 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])) - { - executor = CacheEntry.Executors[i]; - break; - } + 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 GetArguments(HandlerMethodDescriptor handler) @@ -409,7 +454,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal var parameter = handler.Parameters[i]; var result = await _parameterBinder.BindModelAsync( - _page.PageContext, + _pageContext, valueProvider, parameter, value: null); @@ -431,6 +476,36 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal return arguments; } + private async Task ExecuteHandlerMethod(object instance) + { + IActionResult result = null; + + var handler = _selector.Select(_pageContext); + if (handler != null) + { + var arguments = await GetArguments(handler); + + Func> 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() { try diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs index 0894825fad..e3a49385fe 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs @@ -5,7 +5,10 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { @@ -13,16 +16,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { public PageActionInvokerCacheEntry( CompiledPageActionDescriptor actionDescriptor, - Func pageFactory, - Action releasePage, + Func viewDataFactory, + Func pageFactory, + Action releasePage, Func modelFactory, Action releaseModel, - Func propertyBinder, + Func propertyBinder, Func>[] executors, IReadOnlyList> viewStartFactories, FilterItem[] cacheableFilters) { ActionDescriptor = actionDescriptor; + ViewDataFactory = viewDataFactory; PageFactory = pageFactory; ReleasePage = releasePage; ModelFactory = modelFactory; @@ -35,12 +40,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public CompiledPageActionDescriptor ActionDescriptor { get; } - public Func PageFactory { get; } + public Func PageFactory { get; } /// /// The action invoked to release a page. This may be null. /// - public Action ReleasePage { get; } + public Action ReleasePage { get; } public Func ModelFactory { get; } @@ -53,10 +58,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal /// The delegate invoked to bind either the handler type (page or model). /// This may be null. /// - public Func PropertyBinder { get; } + public Func PropertyBinder { get; } public Func>[] Executors { get; } + public Func ViewDataFactory { get; } + /// /// Gets the applicable ViewStart pages. /// diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs index a11933d492..c320aedf1a 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs @@ -16,10 +16,10 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.AspNetCore.Razor.Language; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { @@ -146,14 +146,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal PageActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) { - var tempData = _tempDataFactory.GetTempData(actionContext.HttpContext); - var pageContext = new PageContext( - actionContext, - new ViewDataDictionary(_modelMetadataProvider, actionContext.ModelState), - tempData, - _htmlHelperOptions); - - pageContext.ActionDescriptor = cacheEntry.ActionDescriptor; + var pageContext = new PageContext(actionContext) + { + ActionDescriptor = cacheEntry.ActionDescriptor, + ViewData = cacheEntry.ViewDataFactory(_modelMetadataProvider, actionContext.ModelState), + ViewStartFactories = cacheEntry.ViewStartFactories.ToList(), + }; return new PageActionInvoker( _selector, @@ -163,7 +161,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal filters, new CopyOnWriteList(_valueProviderFactories), cacheEntry, - _parameterBinder); + _parameterBinder, + _tempDataFactory, + _htmlHelperOptions); } private PageActionInvokerCacheEntry CreateCacheEntry( @@ -173,6 +173,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal var actionDescriptor = (PageActionDescriptor)context.ActionContext.ActionDescriptor; var compiledActionDescriptor = _loader.Load(actionDescriptor); + var viewDataFactory = ViewDataDictionaryFactory.CreateFactory(compiledActionDescriptor.ModelTypeInfo); + var pageFactory = _pageFactoryProvider.CreatePageFactory(compiledActionDescriptor); var pageDisposer = _pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor); var propertyBinder = PagePropertyBinderFactory.CreateBinder( @@ -194,6 +196,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal return new PageActionInvokerCacheEntry( compiledActionDescriptor, + viewDataFactory, pageFactory, pageDisposer, modelFactory, diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PagePropertyBinderFactory.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PagePropertyBinderFactory.cs index 15ec5e1684..14419208a4 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PagePropertyBinderFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PagePropertyBinderFactory.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { public static class PagePropertyBinderFactory { - public static Func CreateBinder( + public static Func CreateBinder( ParameterBinder parameterBinder, IModelMetadataProvider modelMetadataProvider, CompiledPageActionDescriptor actionDescriptor) @@ -45,20 +45,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal 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); } } diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PassThruRazorPageActivator.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PassThruRazorPageActivator.cs index aec0c3873f..432367a6e4 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PassThruRazorPageActivator.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PassThruRazorPageActivator.cs @@ -22,12 +22,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal var razorView = (RazorView)context.View; if (ReferenceEquals(page, razorView.RazorPage)) { - var pageContext = (PageContext)context; + var actionDescriptor = (CompiledPageActionDescriptor)context.ActionDescriptor; var vddType = typeof(ViewDataDictionary<>); - var modelTypeInfo = pageContext.ActionDescriptor.ModelTypeInfo ?? pageContext.ActionDescriptor.PageTypeInfo; - - + var modelTypeInfo = actionDescriptor.ModelTypeInfo ?? actionDescriptor.PageTypeInfo; vddType = vddType.MakeGenericType(modelTypeInfo.AsType()); context.ViewData = (ViewDataDictionary)Activator.CreateInstance(vddType, context.ViewData); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs index d1616ee965..9a7ac319b3 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs @@ -37,14 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages public PageContext PageContext { get; set; } /// - public override ViewContext ViewContext - { - get => PageContext; - set - { - PageContext = (PageContext)value; - } - } + public override ViewContext ViewContext { get; set; } /// /// Gets the . @@ -502,7 +495,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// Returning a from a page handler method is equivalent to returning void. /// The view associated with the page will be executed. /// - public virtual PageResult Page() => new PageResult(this); + public virtual PageResult Page() => new PageResult(); /// /// Creates a object that redirects to the specified . diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs index c95a901ec3..a1cdcef1a8 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs @@ -2,25 +2,22 @@ // 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.IO; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; -using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; namespace Microsoft.AspNetCore.Mvc.RazorPages { /// /// The context associated with the current request for a Razor page. /// - public class PageContext : ViewContext + public class PageContext : ActionContext { private CompiledPageActionDescriptor _actionDescriptor; - private Page _page; private IList _valueProviderFactories; + private ViewDataDictionary _viewData; + private IList> _viewStartFactories; /// /// Creates an empty . @@ -36,53 +33,32 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// Initializes a new instance of . /// /// The . - /// The . - /// The . - /// The to apply to this instance. - public PageContext( - ActionContext actionContext, - ViewDataDictionary viewData, - ITempDataDictionary tempDataDictionary, - HtmlHelperOptions htmlHelperOptions) - : base(actionContext, NullView.Instance, viewData, tempDataDictionary, TextWriter.Null, htmlHelperOptions) + public PageContext(ActionContext actionContext) + : base(actionContext) { } /// /// Gets or sets the . /// - public new CompiledPageActionDescriptor ActionDescriptor + public virtual new CompiledPageActionDescriptor ActionDescriptor { get { return _actionDescriptor; } set - { - _actionDescriptor = value; - base.ActionDescriptor = value; - } - } - - public Page Page - { - get { return _page; } - set { if (value == null) { throw new ArgumentNullException(nameof(value)); } - _page = value; + _actionDescriptor = value; + base.ActionDescriptor = value; } } - /// - /// Gets or sets the applicable _ViewStart instances. - /// - public IReadOnlyList ViewStarts { get; set; } - /// /// Gets or sets the list of instances for the current request. /// @@ -107,5 +83,45 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages _valueProviderFactories = value; } } + + /// + /// Gets or sets . + /// + public virtual ViewDataDictionary ViewData + { + get + { + return _viewData; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _viewData = value; + } + } + + /// + /// Gets or sets the applicable _ViewStart instances. + /// + public virtual IList> ViewStartFactories + { + get + { + return _viewStartFactories; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _viewStartFactories = value; + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs index bbe0df2d4e..a7daf7ba4f 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; -using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Routing; @@ -25,48 +24,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages [PagesBaseClass] public abstract class PageModel { - private IObjectModelValidator _objectValidator; private IModelMetadataProvider _metadataProvider; private IModelBinderFactory _modelBinderFactory; + private IObjectModelValidator _objectValidator; + private ITempDataDictionary _tempData; private IUrlHelper _urlHelper; - /// - /// Gets or sets the . - /// - public IUrlHelper Url - { - get - { - if (_urlHelper == null) - { - var factory = HttpContext?.RequestServices?.GetRequiredService(); - _urlHelper = factory?.GetUrlHelper(PageContext); - } - - return _urlHelper; - } - set - { - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - - _urlHelper = value; - } - } - /// /// Gets the . /// [PageContext] public PageContext PageContext { get; set; } - /// - /// Gets the . - /// - public ViewContext ViewContext => PageContext; - /// /// Gets the . /// @@ -98,10 +67,61 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages public ClaimsPrincipal User => HttpContext?.User; /// - /// Gets the from the . + /// Gets or sets used by . /// - /// Returns null if is null. - public ITempDataDictionary TempData => PageContext?.TempData; + public ITempDataDictionary TempData + { + get + { + if (_tempData == null) + { + var factory = HttpContext?.RequestServices?.GetRequiredService(); + _tempData = factory?.GetTempData(HttpContext); + } + + return _tempData; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _tempData = value; + } + } + + /// + /// Gets or sets the . + /// + public IUrlHelper Url + { + get + { + if (_urlHelper == null) + { + var factory = HttpContext?.RequestServices?.GetRequiredService(); + _urlHelper = factory?.GetUrlHelper(PageContext); + } + + return _urlHelper; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _urlHelper = value; + } + } + + /// + /// Gets or sets used by . + /// + public ViewDataDictionary ViewData => PageContext?.ViewData; private IObjectModelValidator ObjectValidator { @@ -142,11 +162,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages } } - /// - /// Gets the . - /// - public ViewDataDictionary ViewData => PageContext?.ViewData; - /// /// Updates the specified instance using values from the 's current /// . @@ -797,7 +812,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// Creates a object that renders the page. /// /// The . - public virtual PageResult Page() => new PageResult(PageContext.Page, this); + public virtual PageResult Page() => new PageResult(); /// /// Returns the file specified by () with the diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageResult.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageResult.cs index 0529cf1be4..1f533b93fc 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageResult.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc.RazorPages @@ -13,26 +14,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// public class PageResult : ActionResult { - /// - /// Initializes a new instance of . - /// - /// The to render. - public PageResult(PageBase page) - { - Page = page; - } - - /// - /// Initializes a new instance of with the specified . - /// - /// The to render. - /// The page model. - public PageResult(PageBase page, object model) - { - Page = page; - Model = model; - } - /// /// Gets or sets the Content-Type header for the response. /// @@ -41,12 +22,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// /// Gets the page model. /// - public object Model { get; } + public object Model => ViewData?.Model; /// - /// Gets the to execute. + /// Gets or sets the to be executed. /// - public PageBase Page { get; } + public PageBase Page { get; set; } + + /// + /// Gets or sets the for the page to be executed. + /// + public ViewDataDictionary ViewData { get; set; } /// /// Gets or sets the HTTP status code. @@ -56,14 +42,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// public override Task ExecuteResultAsync(ActionContext context) { - if (!object.ReferenceEquals(context, Page.PageContext)) + if (!(context is PageContext pageContext)) { - throw new ArgumentException( - Resources.FormatPageViewResult_ContextIsInvalid(nameof(context), nameof(Page))); + throw new ArgumentException(Resources.FormatPageViewResult_ContextIsInvalid( + nameof(context), + nameof(Page), + nameof(PageResult))); } var executor = context.HttpContext.RequestServices.GetRequiredService(); - return executor.ExecuteAsync(Page.PageContext, this); + return executor.ExecuteAsync(pageContext, this); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs index cd6c6a8aa1..97366d9018 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages => string.Format(CultureInfo.CurrentCulture, GetString("ActivatedInstance_MustBeAnInstanceOf"), p0, p1); /// - /// 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. /// internal static string PageViewResult_ContextIsInvalid { @@ -61,10 +61,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages } /// - /// 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. /// - internal static string FormatPageViewResult_ContextIsInvalid(object p0, object p1) - => string.Format(CultureInfo.CurrentCulture, GetString("PageViewResult_ContextIsInvalid"), p0, p1); + internal static string FormatPageViewResult_ContextIsInvalid(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("PageViewResult_ContextIsInvalid"), p0, p1, p2); /// /// Value cannot be null or empty. diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx b/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx index 2e8f00040c..dfcbcac906 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx @@ -127,7 +127,7 @@ Page created by '{0}' must be an instance of '{1}'. - 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. Value cannot be null or empty. diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataDictionaryFactory.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataDictionaryFactory.cs new file mode 100644 index 0000000000..7d08cc7125 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataDictionaryFactory.cs @@ -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 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>( + Expression.Convert( + Expression.New(constructor, parameter1, parameter2), + typeof(ViewDataDictionary)), + parameter1, + parameter2) + .Compile(); + } + + public static Func 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>( + Expression.Convert( + Expression.New(constructor, parameter), + typeof(ViewDataDictionary)), + parameter) + .Compile(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorProviderTest.cs similarity index 86% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorTest.cs rename to test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorProviderTest.cs index 27b1da83c2..4276f81ae9 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorProviderTest.cs @@ -4,20 +4,21 @@ using System; using System.Reflection; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; using Xunit; namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { - public class DefaultPageActivatorTest + public class DefaultPageActivatorProviderTest { [Fact] public void CreateActivator_ThrowsIfPageTypeInfoIsNull() { // Arrange var descriptor = new CompiledPageActionDescriptor(); - var activator = new DefaultPageActivator(); + var activator = new DefaultPageActivatorProvider(); // Act & Assert ExceptionAssert.ThrowsArgument( @@ -33,16 +34,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { // Arrange var pageContext = new PageContext(); + var viewContext = new ViewContext(); var descriptor = new CompiledPageActionDescriptor { PageTypeInfo = type.GetTypeInfo(), }; - var activator = new DefaultPageActivator(); + + var activator = new DefaultPageActivatorProvider(); // Act var factory = activator.CreateActivator(descriptor); - var instance = factory(pageContext); + var instance = factory(pageContext, viewContext); // Assert Assert.NotNull(instance); @@ -58,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure PageTypeInfo = typeof(PageWithoutParameterlessConstructor).GetTypeInfo(), }; var pageContext = new PageContext(); - var activator = new DefaultPageActivator(); + var activator = new DefaultPageActivatorProvider(); // Act & Assert Assert.Throws(() => activator.CreateActivator(descriptor)); @@ -71,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { // Arrange var context = new PageContext(); - var activator = new DefaultPageActivator(); + var activator = new DefaultPageActivatorProvider(); var page = new TestPage(); // Act @@ -89,7 +92,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { // Arrange var context = new PageContext(); - var activator = new DefaultPageActivator(); + var viewContext = new ViewContext(); + var activator = new DefaultPageActivatorProvider(); var page = new DisposablePage(); // Act & Assert @@ -98,7 +102,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure PageTypeInfo = page.GetType().GetTypeInfo() }); Assert.NotNull(disposer); - disposer(context, page); + disposer(context, viewContext, page); // Assert Assert.True(page.Disposed); diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryProviderTest.cs similarity index 87% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryTest.cs rename to test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryProviderTest.cs index 110c37a9b2..f0ccc4602d 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryProviderTest.cs @@ -53,11 +53,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { ActionDescriptor = descriptor }; + var viewContext = new ViewContext(); var factoryProvider = CreatePageFactory(); // Act var factory = factoryProvider.CreatePageFactory(descriptor); - var instance = factory(pageContext); + var instance = factory(pageContext, viewContext); // Assert var testPage = Assert.IsType(instance); @@ -75,11 +76,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure PageTypeInfo = typeof(TestPage).GetTypeInfo(), } }; + + var viewContext = new ViewContext(); + var urlHelperFactory = new Mock(); var urlHelper = Mock.Of(); - urlHelperFactory.Setup(f => f.GetUrlHelper(pageContext)) + urlHelperFactory + .Setup(f => f.GetUrlHelper(viewContext)) .Returns(urlHelper) .Verifiable(); + var htmlEncoder = HtmlEncoder.Create(); var factoryProvider = CreatePageFactory( @@ -88,7 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure // Act var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor); - var instance = factory(pageContext); + var instance = factory(pageContext, viewContext); // Assert var testPage = Assert.IsType(instance); @@ -113,9 +119,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure ActionDescriptor = descriptor }; + var viewContext = new ViewContext(); + // Act var factory = CreatePageFactory().CreatePageFactory(descriptor); - var instance = factory(pageContext); + var instance = factory(pageContext, viewContext); // Assert var testPage = Assert.IsType(instance); @@ -135,11 +143,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure }, }; + var viewContext = new ViewContext(); + var factoryProvider = CreatePageFactory(); // Act var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor); - var instance = factory(pageContext); + var instance = factory(pageContext, viewContext); // Assert var testPage = Assert.IsType(instance); @@ -158,11 +168,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure }, }; + var viewContext = new ViewContext(); + var factoryProvider = CreatePageFactory(); // Act var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor); - var instance = factory(pageContext); + var instance = factory(pageContext, viewContext); // Assert var testPage = Assert.IsType(instance); @@ -170,7 +182,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure } [Fact] - public void PageFactorySetsNestedVidewDataDictionaryWhenContextHasANonNullDictionary() + public void PageFactory_SetsViewDataOnPage_FromPageContext() { // Arrange var modelMetadataProvider = new EmptyModelMetadataProvider(); @@ -180,21 +192,28 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { PageTypeInfo = typeof(TestPage).GetTypeInfo() }, - ViewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary()) + ViewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary()) { { "test-key", "test-value" }, } }; + var viewContext = new ViewContext() + { + HttpContext = pageContext.HttpContext, + ViewData = pageContext.ViewData, + }; + var factoryProvider = CreatePageFactory(); // Act var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor); - var instance = factory(pageContext); + var instance = factory(pageContext, viewContext); // Assert var testPage = Assert.IsType(instance); Assert.NotNull(testPage.ViewData); + Assert.Same(pageContext.ViewData, testPage.ViewData); Assert.Equal("test-value", testPage.ViewData["test-key"]); } @@ -205,6 +224,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure var serviceProvider = new ServiceCollection() .AddSingleton(NullLogger.Instance) .BuildServiceProvider(); + var pageContext = new PageContext { ActionDescriptor = new CompiledPageActionDescriptor @@ -215,14 +235,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { RequestServices = serviceProvider, }, + }; + var viewContext = new ViewContext() + { + HttpContext = pageContext.HttpContext, }; var factoryProvider = CreatePageFactory(); // Act var factory = factoryProvider.CreatePageFactory(pageContext.ActionDescriptor); - var instance = factory(pageContext); + var instance = factory(pageContext, viewContext); // Assert var testPage = Assert.IsType(instance); @@ -258,10 +282,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure private static IPageActivatorProvider CreateActivator() { var activator = new Mock(); - activator.Setup(a => a.CreateActivator(It.IsAny())) + activator + .Setup(a => a.CreateActivator(It.IsAny())) .Returns((CompiledPageActionDescriptor descriptor) => { - return (context) => Activator.CreateInstance(descriptor.PageTypeInfo.AsType()); + return (context, viewContext) => Activator.CreateInstance(descriptor.PageTypeInfo.AsType()); }); return activator.Object; } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelActivatorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelActivatorProviderTest.cs index 4e953e3dfe..e2e89e5214 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelActivatorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelActivatorProviderTest.cs @@ -4,6 +4,7 @@ using System; using System.Reflection; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; @@ -95,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { // Arrange var context = new PageContext(); - var activator = new DefaultPageActivator(); + var activator = new DefaultPageModelActivatorProvider(); var actionDescriptor = new CompiledPageActionDescriptor { PageTypeInfo = pageType.GetTypeInfo(), @@ -113,11 +114,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { // Arrange var context = new PageContext(); - var activator = new DefaultPageActivator(); + + var activator = new DefaultPageModelActivatorProvider(); var actionDescriptor = new CompiledPageActionDescriptor { - PageTypeInfo = typeof(DisposableModel).GetTypeInfo(), + ModelTypeInfo = typeof(DisposableModel).GetTypeInfo(), }; + var model = new DisposableModel(); // Act & Assert diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs index 85dd34a504..eb96379878 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs @@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor.Internal; 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.Razor.Language; @@ -38,8 +39,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal FilterDescriptors = new FilterDescriptor[0], }; - Func factory = _ => null; - Action releaser = (_, __) => { }; + Func factory = (a, b) => null; + Action releaser = (a, b, c) => { }; var loader = new Mock(); loader @@ -78,6 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.Same(releaser, entry.ReleasePage); Assert.Null(entry.ModelFactory); Assert.Null(entry.ReleaseModel); + Assert.NotNull(entry.ViewDataFactory); } [Fact] @@ -90,8 +92,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal FilterDescriptors = new FilterDescriptor[0], }; - Func factory = _ => null; - Action releaser = (_, __) => { }; + Func factory = (a, b) => null; + Action releaser = (a, b, c) => { }; Func modelFactory = _ => null; Action modelDisposer = (_, __) => { }; @@ -134,7 +136,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Assert Assert.NotNull(context.Result); + var actionInvoker = Assert.IsType(context.Result); + var entry = actionInvoker.CacheEntry; var compiledPageActionDescriptor = Assert.IsType(entry.ActionDescriptor); Assert.Equal(descriptor.RelativePath, compiledPageActionDescriptor.RelativePath); @@ -142,6 +146,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.Same(releaser, entry.ReleasePage); Assert.Same(modelFactory, entry.ModelFactory); 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>(pageContext.ViewData)); + Assert.Empty(pageContext.ViewStartFactories); } [Fact] @@ -476,16 +490,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal PageActionDescriptor descriptor, Type pageType = null) { + pageType = pageType ?? typeof(object); + var pageTypeInfo = pageType.GetTypeInfo(); + TypeInfo modelTypeInfo = null; if (pageType != null) { - modelTypeInfo = pageType.GetTypeInfo().GetProperty("Model")?.PropertyType.GetTypeInfo(); + modelTypeInfo = pageTypeInfo.GetProperty("Model")?.PropertyType.GetTypeInfo(); } return new CompiledPageActionDescriptor(descriptor) { - ModelTypeInfo = modelTypeInfo, - PageTypeInfo = (pageType ?? typeof(object)).GetTypeInfo() + HandlerTypeInfo = modelTypeInfo ?? pageTypeInfo, + ModelTypeInfo = modelTypeInfo ?? pageTypeInfo, + PageTypeInfo = pageTypeInfo, }; } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs index 045d99901d..7dbca6e257 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs @@ -14,8 +14,10 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -344,10 +346,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal .Verifiable(); var filter3 = new Mock(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( actionDescriptor, - (context) => createCalled = true, + null, + (context, viewContext) => createCalled = true, null, (context) => null, null, @@ -400,10 +410,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal var filter3 = new Mock(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( actionDescriptor, - (context) => createCalled = true, + null, + (context, viewContext) => createCalled = true, null, (context) => null, null, @@ -540,6 +557,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal PageResultExecutor executor = null, IPageHandlerMethodSelector selector = null, PageActionInvokerCacheEntry cacheEntry = null, + ITempDataDictionaryFactory tempDataFactory = null, int maxAllowedErrorsInModelState = 200, List valueProviderFactories = null, RouteData routeData = null, @@ -572,15 +590,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal httpContext: httpContext, routeData: routeData, actionDescriptor: actionDescriptor); - var pageContext = new PageContext( - actionContext, - new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()), - Mock.Of(), - new HtmlHelperOptions()) + var pageContext = new PageContext(actionContext) { - ActionDescriptor = actionDescriptor + ActionDescriptor = actionDescriptor, }; + var viewDataFactory = ViewDataDictionaryFactory.CreateFactory(actionDescriptor.ModelTypeInfo); + pageContext.ViewData = viewDataFactory(new EmptyModelMetadataProvider(), pageContext.ModelState); + if (selector == null) { selector = Mock.Of(); @@ -596,7 +613,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal logger = NullLogger.Instance; } - Func pageFactory = (context) => + if (tempDataFactory == null) + { + tempDataFactory = Mock.Of(m => m.GetTempData(It.IsAny()) == Mock.Of()); + } + + Func pageFactory = (context, viewContext) => { var instance = (Page)Activator.CreateInstance(actionDescriptor.PageTypeInfo.AsType()); instance.PageContext = context; @@ -605,8 +627,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal cacheEntry = new PageActionInvokerCacheEntry( actionDescriptor, + viewDataFactory, pageFactory, - (c, page) => { (page as IDisposable)?.Dispose(); }, + (c, viewContext, page) => { (page as IDisposable)?.Dispose(); }, _ => Activator.CreateInstance(actionDescriptor.ModelTypeInfo.AsType()), (c, model) => { (model as IDisposable)?.Dispose(); }, null, @@ -622,7 +645,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal filters, valueProviderFactories.AsReadOnly(), cacheEntry, - GetParameterBinder()); + GetParameterBinder(), + tempDataFactory, + new HtmlHelperOptions()); return invoker; } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PagePropertyBinderFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PagePropertyBinderFactoryTest.cs index 618385a4f3..771e32de1f 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PagePropertyBinderFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PagePropertyBinderFactoryTest.cs @@ -199,7 +199,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal }; // Act - await factory(page, null); + await factory(page.PageContext, page); // Assert Assert.Equal(10, page.Id); @@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal var model = new PageModelWithProperty(); // Act - await factory(page, model); + await factory(page.PageContext, model); // Assert // Verify that the page properties were not bound. @@ -314,7 +314,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal var defaultValue = model.PropertyWithDefaultValue; // Act - await factory(page, model); + await factory(page.PageContext, model); // Assert Assert.Equal(defaultValue, model.PropertyWithDefaultValue); @@ -375,7 +375,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal var model = new PageModelWithSupportsGetProperty(); // Act - await factory(page, model); + await factory(page.PageContext, model); // Assert Assert.Equal("value", model.SupportsGet); @@ -434,7 +434,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal var model = new PageModelWithSupportsGetProperty(); // Act - await factory(page, model); + await factory(page.PageContext, model); // Assert Assert.Equal("value", model.SupportsGet); diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs index db34bff3b6..9346bfe5c3 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs @@ -1406,15 +1406,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages var modelState = new ModelStateDictionary(); var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState); var modelMetadataProvider = new EmptyModelMetadataProvider(); - var viewDataDictionary = new ViewDataDictionary(modelMetadataProvider, modelState); - var tempData = Mock.Of(); - var pageContext = new PageContext(actionContext, viewDataDictionary, tempData, new HtmlHelperOptions()); + var viewData = new ViewDataDictionary(modelMetadataProvider, modelState); + var pageContext = new PageContext(actionContext) + { + ViewData = viewData, + }; var page = new TestPage { PageContext = pageContext, }; - pageContext.Page = page; var pageModel = new TestPageModel { @@ -1423,13 +1424,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages // Act & Assert Assert.Same(pageContext, pageModel.PageContext); - Assert.Same(pageContext, pageModel.ViewContext); Assert.Same(httpContext, pageModel.HttpContext); Assert.Same(httpContext.Request, pageModel.Request); Assert.Same(httpContext.Response, pageModel.Response); Assert.Same(modelState, pageModel.ModelState); - Assert.Same(viewDataDictionary, pageModel.ViewData); - Assert.Same(tempData, pageModel.TempData); + Assert.Same(viewData, pageModel.ViewData); } [Fact] @@ -1483,10 +1482,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages var page = new TestPage(); var pageModel = new TestPageModel { - PageContext = new PageContext - { - Page = page, - } + PageContext = new PageContext() }; // Act @@ -1494,7 +1490,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages // Assert var pageResult = Assert.IsType(result); - Assert.Same(page, pageResult.Page); + Assert.Null(pageResult.Page); // This is set by the invoker } private class ContentPageModel : PageModel diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs index 50d92356b2..d8fa42deb6 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs @@ -9,8 +9,10 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Testing; using Moq; @@ -28,17 +30,24 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages var modelState = new ModelStateDictionary(); var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState); var modelMetadataProvider = new EmptyModelMetadataProvider(); - var viewDataDictionary = new ViewDataDictionary(modelMetadataProvider, modelState); + var viewData = new ViewDataDictionary(modelMetadataProvider, modelState); var tempData = Mock.Of(); - 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 { PageContext = pageContext, + ViewContext = viewContext, }; // 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.Request, page.Request); Assert.Same(httpContext.Response, page.Response); diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilter/PageSaveTempDataPropertyFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilter/PageSaveTempDataPropertyFilterTest.cs index ecea4a4320..8f04bc0984 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilter/PageSaveTempDataPropertyFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilter/PageSaveTempDataPropertyFilterTest.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Routing; using Moq; using Xunit; @@ -164,7 +166,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal var testProp = pageType.GetProperty(nameof(TestPageString.Test)); var test2Prop = pageType.GetProperty(nameof(TestPageString.Test2)); - provider.TempDataProperties = new List { + provider.TempDataProperties = new List + { new TempDataProperty(testProp, testProp.GetValue, testProp.SetValue), new TempDataProperty(test2Prop, test2Prop.GetValue, test2Prop.SetValue) }; @@ -177,15 +180,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal 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 metadataProvider = new EmptyModelMetadataProvider(); var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary()); - var viewContext = new PageContext( + var viewContext = new ViewContext( actionContext, + NullView.Instance, viewData, tempData, + TextWriter.Null, new HtmlHelperOptions()); return viewContext;