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);
/// <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>
internal static string UrlHelper_RelativePagePathIsNotSupported
{
@ -1293,7 +1293,7 @@ namespace Microsoft.AspNetCore.Mvc.Core
}
/// <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>
internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0);

View File

@ -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<IModelMetadataProvider, ModelStateDictionary, ViewDataDictionary> _rootFactory;
private readonly Func<ViewDataDictionary, ViewDataDictionary> _nestedFactory;
private readonly Type _viewDataDictionaryType;
private readonly PropertyActivator<ViewContext>[] _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<ViewContext>.GetPropertiesToActivate(
_viewDataDictionaryType = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
_rootFactory = ViewDataDictionaryFactory.CreateFactory(modelType.GetTypeInfo());
_nestedFactory = ViewDataDictionaryFactory.CreateNestedFactory(modelType.GetTypeInfo());
_propertyActivators = PropertyActivator<ViewContext>.GetPropertiesToActivate(
pageType,
typeof(RazorInjectAttribute),
propertyInfo => CreateActivateInfo(propertyInfo, propertyValueAccessors),
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)
{
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<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).
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<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(
PropertyInfo property,
PropertyValueAccessors valueAccessors)

View File

@ -113,7 +113,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton<IPageModelActivatorProvider, DefaultPageModelActivatorProvider>();
services.TryAddSingleton<IPageModelFactoryProvider, DefaultPageModelFactoryProvider>();
services.TryAddSingleton<IPageActivatorProvider, DefaultPageActivator>();
services.TryAddSingleton<IPageActivatorProvider, DefaultPageActivatorProvider>();
services.TryAddSingleton<IPageFactoryProvider, DefaultPageFactory>();
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.
using System;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
@ -15,13 +16,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <returns>The delegate used to activate the page.</returns>
Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor descriptor);
Func<PageContext, ViewContext, object> CreateActivator(CompiledPageActionDescriptor descriptor);
/// <summary>
/// Releases a Razor page.
/// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <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.
using System;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
@ -15,13 +16,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <returns>The Razor page factory.</returns>
Func<PageContext, object> CreatePageFactory(CompiledPageActionDescriptor descriptor);
Func<PageContext, ViewContext, object> CreatePageFactory(CompiledPageActionDescriptor descriptor);
/// <summary>
/// Releases a Razor page.
/// </summary>
/// <param name="descriptor">The <see cref="CompiledPageActionDescriptor"/>.</param>
/// <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.Linq.Expressions;
using System.Reflection;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
/// <summary>
/// <see cref="IPageActivatorProvider"/> that uses type activation to create Pages.
/// </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 />
public virtual Func<PageContext, object> CreateActivator(CompiledPageActionDescriptor actionDescriptor)
public virtual Func<PageContext, ViewContext, object> CreateActivator(CompiledPageActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
{
@ -34,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
return CreatePageFactory(pageTypeInfo);
}
public virtual Action<PageContext, object> CreateReleaser(CompiledPageActionDescriptor actionDescriptor)
public virtual Action<PageContext, ViewContext, object> CreateReleaser(CompiledPageActionDescriptor actionDescriptor)
{
if (actionDescriptor == null)
{
@ -49,27 +50,33 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
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();
var newExpression = Expression.New(pageTypeInfo);
// () => new Page();
var pageFactory = Expression
.Lambda<Func<PageContext, object>>(newExpression, parameter)
.Lambda<Func<PageContext, ViewContext, object>>(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
}
}
}

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))
{
@ -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<PageContext, object> CreatePageDisposer(CompiledPageActionDescriptor descriptor)
public virtual Action<PageContext, ViewContext, object> CreatePageDisposer(CompiledPageActionDescriptor descriptor)
{
if (descriptor == null)
{

View File

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

View File

@ -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<IValueProviderFactory> 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;
/// <remarks>
/// <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)
{
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<IRazorPage>();
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<object, object[], Task<IActionResult>> 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<object[]> 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<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()
{
try

View File

@ -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<PageContext, object> pageFactory,
Action<PageContext, object> releasePage,
Func<IModelMetadataProvider, ModelStateDictionary, ViewDataDictionary> viewDataFactory,
Func<PageContext, ViewContext, object> pageFactory,
Action<PageContext, ViewContext, object> releasePage,
Func<PageContext, object> modelFactory,
Action<PageContext, object> releaseModel,
Func<Page, object, Task> propertyBinder,
Func<PageContext, object, Task> propertyBinder,
Func<object, object[], Task<IActionResult>>[] executors,
IReadOnlyList<Func<IRazorPage>> 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<PageContext, object> PageFactory { get; }
public Func<PageContext, ViewContext, object> PageFactory { get; }
/// <summary>
/// The action invoked to release a page. This may be <c>null</c>.
/// </summary>
public Action<PageContext, object> ReleasePage { get; }
public Action<PageContext, ViewContext, object> ReleasePage { 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).
/// This may be <c>null</c>.
/// </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<IModelMetadataProvider, ModelStateDictionary, ViewDataDictionary> ViewDataFactory { get; }
/// <summary>
/// Gets the applicable ViewStart pages.
/// </summary>

View File

@ -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<IValueProviderFactory>(_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,

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public static class PagePropertyBinderFactory
{
public static Func<Page, object, Task> CreateBinder(
public static Func<PageContext, object, Task> 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);
}
}

View File

@ -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);

View File

@ -37,14 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
public PageContext PageContext { get; set; }
/// <inheritdoc />
public override ViewContext ViewContext
{
get => PageContext;
set
{
PageContext = (PageContext)value;
}
}
public override ViewContext ViewContext { get; set; }
/// <summary>
/// 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.
/// The view associated with the page will be executed.
/// </remarks>
public virtual PageResult Page() => new PageResult(this);
public virtual PageResult Page() => new PageResult();
/// <summary>
/// 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.
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
{
/// <summary>
/// The context associated with the current request for a Razor page.
/// </summary>
public class PageContext : ViewContext
public class PageContext : ActionContext
{
private CompiledPageActionDescriptor _actionDescriptor;
private Page _page;
private IList<IValueProviderFactory> _valueProviderFactories;
private ViewDataDictionary _viewData;
private IList<Func<IRazorPage>> _viewStartFactories;
/// <summary>
/// Creates an empty <see cref="PageContext"/>.
@ -36,53 +33,32 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// Initializes a new instance of <see cref="PageContext"/>.
/// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/>.</param>
/// <param name="viewData">The <see cref="ViewDataDictionary"/>.</param>
/// <param name="tempDataDictionary">The <see cref="ITempDataDictionary"/>.</param>
/// <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)
public PageContext(ActionContext actionContext)
: base(actionContext)
{
}
/// <summary>
/// Gets or sets the <see cref="PageActionDescriptor"/>.
/// </summary>
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;
}
}
/// <summary>
/// Gets or sets the applicable _ViewStart instances.
/// </summary>
public IReadOnlyList<IRazorPage> ViewStarts { get; set; }
/// <summary>
/// Gets or sets the list of <see cref="IValueProviderFactory"/> instances for the current request.
/// </summary>
@ -107,5 +83,45 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
_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.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;
/// <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 the <see cref="RazorPages.PageContext"/>.
/// </summary>
[PageContext]
public PageContext PageContext { get; set; }
/// <summary>
/// Gets the <see cref="ViewContext"/>.
/// </summary>
public ViewContext ViewContext => PageContext;
/// <summary>
/// Gets the <see cref="Http.HttpContext"/>.
/// </summary>
@ -98,10 +67,61 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
public ClaimsPrincipal User => HttpContext?.User;
/// <summary>
/// Gets the <see cref="ITempDataDictionary"/> from the <see cref="PageContext"/>.
/// Gets or sets <see cref="ITempDataDictionary"/> used by <see cref="PageResult"/>.
/// </summary>
/// <remarks>Returns null if <see cref="PageContext"/> is null.</remarks>
public ITempDataDictionary TempData => PageContext?.TempData;
public ITempDataDictionary 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
{
@ -142,11 +162,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
}
}
/// <summary>
/// Gets the <see cref="ViewDataDictionary"/>.
/// </summary>
public ViewDataDictionary ViewData => PageContext?.ViewData;
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the <see cref="PageModel"/>'s current
/// <see cref="IValueProvider"/>.
@ -797,7 +812,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// Creates a <see cref="PageResult"/> object that renders the page.
/// </summary>
/// <returns>The <see cref="PageResult"/>.</returns>
public virtual PageResult Page() => new PageResult(PageContext.Page, this);
public virtual PageResult Page() => new PageResult();
/// <summary>
/// Returns the file specified by <paramref name="physicalPath" /> (<see cref="StatusCodes.Status200OK"/>) with the

View File

@ -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
/// </summary>
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>
/// Gets or sets the Content-Type header for the response.
/// </summary>
@ -41,12 +22,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// <summary>
/// Gets the page model.
/// </summary>
public object Model { get; }
public object Model => ViewData?.Model;
/// <summary>
/// Gets the <see cref="RazorPages.Page"/> to execute.
/// Gets or sets the <see cref="PageBase"/> to be executed.
/// </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>
/// Gets or sets the HTTP status code.
@ -56,14 +42,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// <inheritdoc />
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<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);
/// <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>
internal static string PageViewResult_ContextIsInvalid
{
@ -61,10 +61,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
}
/// <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>
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);
/// <summary>
/// Value cannot be null or empty.

View File

@ -127,7 +127,7 @@
<value>Page created by '{0}' must be an instance of '{1}'.</value>
</data>
<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 name="ArgumentCannotBeNullOrEmpty" xml:space="preserve">
<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.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<ArgumentException>(() => 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);

View File

@ -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<TestPage>(instance);
@ -75,11 +76,16 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
PageTypeInfo = typeof(TestPage).GetTypeInfo(),
}
};
var viewContext = new ViewContext();
var urlHelperFactory = new Mock<IUrlHelperFactory>();
var urlHelper = Mock.Of<IUrlHelper>();
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<TestPage>(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<ViewDataTestPage>(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<ViewDataTestPage>(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<NonGenericViewDataTestPage>(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<TestPage>(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<TestPage>(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<ILogger>(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<PropertiesWithoutRazorInject>(instance);
@ -258,10 +282,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
private static IPageActivatorProvider CreateActivator()
{
var activator = new Mock<IPageActivatorProvider>();
activator.Setup(a => a.CreateActivator(It.IsAny<CompiledPageActionDescriptor>()))
activator
.Setup(a => a.CreateActivator(It.IsAny<CompiledPageActionDescriptor>()))
.Returns((CompiledPageActionDescriptor descriptor) =>
{
return (context) => Activator.CreateInstance(descriptor.PageTypeInfo.AsType());
return (context, viewContext) => Activator.CreateInstance(descriptor.PageTypeInfo.AsType());
});
return activator.Object;
}

View File

@ -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

View File

@ -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<PageContext, object> factory = _ => null;
Action<PageContext, object> releaser = (_, __) => { };
Func<PageContext, ViewContext, object> factory = (a, b) => null;
Action<PageContext, ViewContext, object> releaser = (a, b, c) => { };
var loader = new Mock<IPageLoader>();
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<PageContext, object> factory = _ => null;
Action<PageContext, object> releaser = (_, __) => { };
Func<PageContext, ViewContext, object> factory = (a, b) => null;
Action<PageContext, ViewContext, object> releaser = (a, b, c) => { };
Func<PageContext, object> modelFactory = _ => null;
Action<PageContext, object> modelDisposer = (_, __) => { };
@ -134,7 +136,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Assert
Assert.NotNull(context.Result);
var actionInvoker = Assert.IsType<PageActionInvoker>(context.Result);
var entry = actionInvoker.CacheEntry;
var compiledPageActionDescriptor = Assert.IsType<CompiledPageActionDescriptor>(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<ViewDataDictionary<TestPageModel>>(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,
};
}

View File

@ -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<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(
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<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(
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<IValueProviderFactory> 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<ITempDataDictionary>(),
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<IPageHandlerMethodSelector>();
@ -596,7 +613,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
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());
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;
}

View File

@ -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);

View File

@ -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<ITempDataDictionary>();
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<PageResult>(result);
Assert.Same(page, pageResult.Page);
Assert.Null(pageResult.Page); // This is set by the invoker
}
private class ContentPageModel : PageModel

View File

@ -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<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
{
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);

View File

@ -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<TempDataProperty> {
provider.TempDataProperties = new List<TempDataProperty>
{
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;