diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorProject.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorProject.cs index b8429759ca..7afb4ba867 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorProject.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorProject.cs @@ -19,6 +19,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal _provider = provider; } + public override RazorProjectItem GetItem(string path) + { + EnsureValidPath(path); + var fileInfo = _provider.GetFileInfo(path); + return new DefaultRazorProjectItem(fileInfo, basePath: string.Empty, path: path); + } + public override IEnumerable EnumerateItems(string path) { if (path == null) diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorProjectItem.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorProjectItem.cs index ff3a69f88d..e57b86e994 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorProjectItem.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorProjectItem.cs @@ -22,6 +22,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal public override string Path { get; } + public override bool Exists => _fileInfo.Exists; + public override string PhysicalPath => _fileInfo.PhysicalPath; public override Stream Read() diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs new file mode 100644 index 0000000000..65aba3b9ab --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.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.Diagnostics; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + /// + /// Executes a Razor Page. + /// + public class PageResultExecutor : ViewExecutor + { + private readonly IRazorViewEngine _razorViewEngine; + private readonly IRazorPageActivator _razorPageActivator; + private readonly HtmlEncoder _htmlEncoder; + + /// + /// Creates a new . + /// + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + public PageResultExecutor( + IHttpResponseStreamWriterFactory writerFactory, + ICompositeViewEngine compositeViewEngine, + IRazorViewEngine razorViewEngine, + IRazorPageActivator razorPageActivator, + DiagnosticSource diagnosticSource, + HtmlEncoder htmlEncoder) + : base(writerFactory, compositeViewEngine, diagnosticSource) + { + _razorViewEngine = razorViewEngine; + _razorPageActivator = razorPageActivator; + _htmlEncoder = htmlEncoder; + } + + /// + /// Executes a Razor Page asynchronously. + /// + public virtual Task ExecuteAsync(PageContext pageContext, PageViewResult result) + { + if (result.Model != null) + { + pageContext.ViewData.Model = result.Model; + } + + var view = new RazorView(_razorViewEngine, _razorPageActivator, pageContext.PageStarts, result.Page, _htmlEncoder); + return ExecuteAsync(pageContext, 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 c74a37a247..1683f61130 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.Core.Internal; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.Extensions.Logging; @@ -304,6 +305,23 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal _page = (Page)CacheEntry.PageFactory(_pageContext); _pageContext.Page = _page; + IRazorPage[] pageStarts; + + if (CacheEntry.PageStartFactories == null || CacheEntry.PageStartFactories.Count == 0) + { + pageStarts = EmptyArray.Instance; + } + else + { + pageStarts = new IRazorPage[CacheEntry.PageStartFactories.Count]; + for (var i = 0; i < pageStarts.Length; i++) + { + var pageFactory = CacheEntry.PageStartFactories[i]; + pageStarts[i] = pageFactory(); + } + } + _pageContext.PageStarts = pageStarts; + if (actionDescriptor.ModelTypeInfo == null) { _model = _page; diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs index 1386b2bc16..437ecffab4 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Razor; namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { @@ -14,6 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Action releasePage, Func modelFactory, Action releaseModel, + IReadOnlyList> pageStartFactories, FilterItem[] cacheableFilters) { ActionDescriptor = actionDescriptor; @@ -21,6 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal ReleasePage = releasePage; ModelFactory = modelFactory; ReleaseModel = releaseModel; + PageStartFactories = pageStartFactories; CacheableFilters = cacheableFilters; } @@ -35,6 +39,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public Func ModelFactory { get; } + /// + /// Gets the applicable PageStarts. + /// + public IReadOnlyList> PageStartFactories { get; } + /// /// The action invoked to release a model. This may be null. /// diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs index e28e564f9e..f2988fc103 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs @@ -12,8 +12,10 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; 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.ViewFeatures; +using Microsoft.AspNetCore.Razor.Evolution; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -21,10 +23,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { public class PageActionInvokerProvider : IActionInvokerProvider { + private const string PageStartFileName = "_PageStart.cshtml"; private const string ModelPropertyName = "Model"; private readonly IPageLoader _loader; private readonly IPageFactoryProvider _pageFactoryProvider; private readonly IPageModelFactoryProvider _modelFactoryProvider; + private readonly IRazorPageFactoryProvider _razorPageFactoryProvider; private readonly IActionDescriptorCollectionProvider _collectionProvider; private readonly IFilterProvider[] _filterProviders; private readonly IReadOnlyList _valueProviderFactories; @@ -32,6 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private readonly ITempDataDictionaryFactory _tempDataFactory; private readonly HtmlHelperOptions _htmlHelperOptions; private readonly IPageHandlerMethodSelector _selector; + private readonly RazorProject _razorProject; private readonly DiagnosticSource _diagnosticSource; private readonly ILogger _logger; private volatile InnerCache _currentCache; @@ -40,6 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal IPageLoader loader, IPageFactoryProvider pageFactoryProvider, IPageModelFactoryProvider modelFactoryProvider, + IRazorPageFactoryProvider razorPageFactoryProvider, IActionDescriptorCollectionProvider collectionProvider, IEnumerable filterProviders, IEnumerable valueProviderFactories, @@ -47,12 +53,14 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal ITempDataDictionaryFactory tempDataFactory, IOptions htmlHelperOptions, IPageHandlerMethodSelector selector, + RazorProject razorProject, DiagnosticSource diagnosticSource, ILoggerFactory loggerFactory) { _loader = loader; _pageFactoryProvider = pageFactoryProvider; _modelFactoryProvider = modelFactoryProvider; + _razorPageFactoryProvider = razorPageFactoryProvider; _collectionProvider = collectionProvider; _filterProviders = filterProviders.ToArray(); _valueProviderFactories = valueProviderFactories.ToArray(); @@ -60,6 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal _tempDataFactory = tempDataFactory; _htmlHelperOptions = htmlHelperOptions.Value; _selector = selector; + _razorProject = razorProject; _diagnosticSource = diagnosticSource; _logger = loggerFactory.CreateLogger(); } @@ -171,15 +180,34 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal modelReleaser = _modelFactoryProvider.CreateModelDisposer(compiledActionDescriptor); } + var pageStartFactories = GetPageStartFactories(compiledActionDescriptor); + return new PageActionInvokerCacheEntry( compiledActionDescriptor, pageFactory, pageDisposer, modelFactory, modelReleaser, + pageStartFactories, cachedFilters); } + private List> GetPageStartFactories(CompiledPageActionDescriptor descriptor) + { + var pageStartFactories = new List>(); + var pageStartItems = _razorProject.FindHierarchicalItems(descriptor.ViewEnginePath, PageStartFileName); + foreach (var item in pageStartItems) + { + var factoryResult = _razorPageFactoryProvider.CreateFactory(item.Path); + if (factoryResult.Success) + { + pageStartFactories.Insert(0, factoryResult.RazorPageFactory); + } + } + + return pageStartFactories; + } + private class InnerCache { public InnerCache(int version) diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageResultExecutor.cs deleted file mode 100644 index 575265b331..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageResultExecutor.cs +++ /dev/null @@ -1,21 +0,0 @@ -// 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.Threading.Tasks; - -namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal -{ - public class PageResultExecutor - { - public virtual Task ExecuteAsync(PageContext pageContext, PageViewResult result) - { - if (result.Model != null) - { - result.Page.PageContext.ViewData.Model = result.Model; - } - - throw new NotImplementedException(); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs index 7e5bae2352..576d62092e 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs @@ -2,7 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; +using System.Collections.Generic; using System.IO; +using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; @@ -72,5 +75,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages _page = value; } } + + /// + /// Gets or sets the applicable _PageStart instances. + /// + public IReadOnlyList PageStarts { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageViewResult.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageViewResult.cs index cadeef803b..20595e54ab 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageViewResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageViewResult.cs @@ -3,7 +3,7 @@ using System; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.RazorPages.Internal; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc.RazorPages diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs index e60e75e07a..699e8d2612 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Internal; @@ -10,7 +11,6 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.ViewFeatures @@ -25,8 +25,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures /// public static readonly string DefaultContentType = "text/html; charset=utf-8"; - private readonly IModelMetadataProvider _modelMetadataProvider; - /// /// Creates a new . /// @@ -43,22 +41,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures ITempDataDictionaryFactory tempDataFactory, DiagnosticSource diagnosticSource, IModelMetadataProvider modelMetadataProvider) + : this(writerFactory, viewEngine, diagnosticSource) { if (viewOptions == null) { throw new ArgumentNullException(nameof(viewOptions)); } - if (writerFactory == null) - { - throw new ArgumentNullException(nameof(writerFactory)); - } - - if (viewEngine == null) - { - throw new ArgumentNullException(nameof(viewEngine)); - } - if (tempDataFactory == null) { throw new ArgumentNullException(nameof(tempDataFactory)); @@ -69,17 +58,40 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures throw new ArgumentNullException(nameof(diagnosticSource)); } - if (modelMetadataProvider == null) + ViewOptions = viewOptions.Value; + TempDataFactory = tempDataFactory; + ModelMetadataProvider = modelMetadataProvider; + } + + /// + /// Creates a new . + /// + /// The . + /// The . + /// The . + protected ViewExecutor( + IHttpResponseStreamWriterFactory writerFactory, + ICompositeViewEngine viewEngine, + DiagnosticSource diagnosticSource) + { + if (writerFactory == null) { - throw new ArgumentNullException(nameof(modelMetadataProvider)); + throw new ArgumentNullException(nameof(writerFactory)); + } + + if (viewEngine == null) + { + throw new ArgumentNullException(nameof(viewEngine)); + } + + if (diagnosticSource == null) + { + throw new ArgumentNullException(nameof(diagnosticSource)); } - ViewOptions = viewOptions.Value; WriterFactory = writerFactory; ViewEngine = viewEngine; - TempDataFactory = tempDataFactory; DiagnosticSource = diagnosticSource; - _modelMetadataProvider = modelMetadataProvider; } /// @@ -102,6 +114,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures /// protected MvcViewOptions ViewOptions { get; } + /// + /// Gets the . + /// + protected IModelMetadataProvider ModelMetadataProvider { get; } + /// /// Gets the . /// @@ -140,9 +157,24 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures throw new ArgumentNullException(nameof(view)); } + if (ViewOptions == null) + { + throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(nameof(ViewOptions), GetType().Name)); + } + + if (TempDataFactory == null) + { + throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(nameof(TempDataFactory), GetType().Name)); + } + + if (ModelMetadataProvider == null) + { + throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(nameof(ModelMetadataProvider), GetType().Name)); + } + if (viewData == null) { - viewData = new ViewDataDictionary(_modelMetadataProvider, actionContext.ModelState); + viewData = new ViewDataDictionary(ModelMetadataProvider, actionContext.ModelState); } if (tempData == null) @@ -150,7 +182,40 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures tempData = TempDataFactory.GetTempData(actionContext.HttpContext); } - var response = actionContext.HttpContext.Response; + var viewContext = new ViewContext( + actionContext, + view, + viewData, + tempData, + TextWriter.Null, + ViewOptions.HtmlHelperOptions); + + await ExecuteAsync(viewContext, contentType, statusCode); + } + + /// + /// Executes a view asynchronously. + /// + /// The associated with the current request. + /// + /// The content-type header value to set in the response. If null, + /// will be used. + /// + /// + /// The HTTP status code to set in the response. May be null. + /// + /// A which will complete when view execution is completed. + protected async Task ExecuteAsync( + ViewContext viewContext, + string contentType, + int? statusCode) + { + if (viewContext == null) + { + throw new ArgumentNullException(nameof(viewContext)); + } + + var response = viewContext.HttpContext.Response; string resolvedContentType = null; Encoding resolvedContentTypeEncoding = null; @@ -170,19 +235,23 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures using (var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding)) { - var viewContext = new ViewContext( - actionContext, - view, - viewData, - tempData, - writer, - ViewOptions.HtmlHelperOptions); + var view = viewContext.View; - DiagnosticSource.BeforeView(view, viewContext); + var oldWriter = viewContext.Writer; + try + { + viewContext.Writer = writer; - await view.RenderAsync(viewContext); + DiagnosticSource.BeforeView(view, viewContext); - DiagnosticSource.AfterView(view, viewContext); + await view.RenderAsync(viewContext); + + DiagnosticSource.AfterView(view, viewContext); + } + finally + { + viewContext.Writer = oldWriter; + } // Perf: Invoke FlushAsync to ensure any buffered content is asynchronously written to the underlying // response asynchronously. In the absence of this line, the buffer gets synchronously written to the diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs index acc3aa8c75..e2482230d1 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs @@ -8,10 +8,14 @@ using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.Evolution; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging.Testing; +using Microsoft.Extensions.Primitives; using Moq; using Xunit; @@ -120,6 +124,53 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.Same(modelDisposer, entry.ReleaseModel); } + [Fact] + public void OnProvidersExecuting_CachesViewStartFactories() + { + // Arrange + var descriptor = new PageActionDescriptor + { + RelativePath = "/Home/Path1/File.cshtml", + ViewEnginePath = "/Home/Path1/File.cshtml", + FilterDescriptors = new FilterDescriptor[0], + }; + + var loader = new Mock(); + loader.Setup(l => l.Load(It.IsAny())) + .Returns(typeof(PageWithModel)); + var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1); + var actionDescriptorProvider = new Mock(); + actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection); + var razorPageFactoryProvider = new Mock(); + Func factory1 = () => null; + Func factory2 = () => null; + razorPageFactoryProvider.Setup(f => f.CreateFactory("/Home/Path1/_PageStart.cshtml")) + .Returns(new RazorPageFactoryResult(factory1, new IChangeToken[0])); + razorPageFactoryProvider.Setup(f => f.CreateFactory("/_PageStart.cshtml")) + .Returns(new RazorPageFactoryResult(factory2, new[] { Mock.Of() })); + var fileProvider = new TestFileProvider(); + fileProvider.AddFile("/Home/Path1/_PageStart.cshtml", "content1"); + fileProvider.AddFile("/_PageStart.cshtml", "content2"); + var defaultRazorProject = new DefaultRazorProject(fileProvider); + + var invokerProvider = CreateInvokerProvider( + loader.Object, + actionDescriptorProvider.Object, + razorPageFactoryProvider: razorPageFactoryProvider.Object, + razorProject: defaultRazorProject); + var context = new ActionInvokerProviderContext( + new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor)); + + // Act + invokerProvider.OnProvidersExecuting(context); + + // Assert + Assert.NotNull(context.Result); + var actionInvoker = Assert.IsType(context.Result); + var entry = actionInvoker.CacheEntry; + Assert.Equal(new[] { factory2, factory1 }, entry.PageStartFactories); + } + [Fact] public void OnProvidersExecuting_CachesEntries() { @@ -207,16 +258,24 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal IPageLoader loader, IActionDescriptorCollectionProvider actionDescriptorProvider, IPageFactoryProvider pageProvider = null, - IPageModelFactoryProvider modelProvider = null) + IPageModelFactoryProvider modelProvider = null, + IRazorPageFactoryProvider razorPageFactoryProvider = null, + RazorProject razorProject = null) { var tempDataFactory = new Mock(); tempDataFactory.Setup(t => t.GetTempData(It.IsAny())) .Returns((HttpContext context) => new TempDataDictionary(context, Mock.Of())); + if (razorProject == null) + { + razorProject = Mock.Of(); + } + return new PageActionInvokerProvider( loader, pageProvider ?? Mock.Of(), modelProvider ?? Mock.Of(), + razorPageFactoryProvider ?? Mock.Of(), actionDescriptorProvider, new IFilterProvider[0], new IValueProviderFactory[0], @@ -224,6 +283,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal tempDataFactory.Object, new TestOptionsManager(), Mock.Of(), + razorProject, new DiagnosticListener("Microsoft.AspNetCore"), NullLoggerFactory.Instance); } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs index 6484c78217..37c391b916 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs @@ -5,11 +5,15 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; +using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; +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.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; @@ -346,6 +350,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal null, (context) => null, null, + null, new FilterItem[0]); var invoker = CreateInvoker( new[] { filter1.Object, filter2.Object, filter3.Object }, @@ -399,6 +404,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal null, (context) => null, null, + null, new FilterItem[0]); var invoker = CreateInvoker( new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }, @@ -530,12 +536,24 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal int maxAllowedErrorsInModelState = 200, List valueProviderFactories = null, RouteData routeData = null, - ILogger logger = null, - object diagnosticListener = null) + ILogger logger = null) { + var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); + var httpContext = new DefaultHttpContext(); var serviceCollection = new ServiceCollection(); - serviceCollection.AddSingleton(executor ?? new PageResultExecutor()); + if (executor == null) + { + executor = new PageResultExecutor( + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + diagnosticSource, + HtmlEncoder.Default); + } + + serviceCollection.AddSingleton(executor ?? executor); httpContext.RequestServices = serviceCollection.BuildServiceProvider(); if (routeData == null) @@ -584,8 +602,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal (c, page) => { (page as IDisposable)?.Dispose(); }, _ => Activator.CreateInstance(actionDescriptor.ModelTypeInfo.AsType()), (c, model) => { (model as IDisposable)?.Dispose(); }, + null, new FilterItem[0]); - var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); var invoker = new PageActionInvoker( selector, @@ -603,6 +621,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private readonly Func _executeAction; public TestPageResultExecutor(Func executeAction) + : base( + Mock.Of(), + Mock.Of(), + Mock.Of(), + Mock.Of(), + new DiagnosticListener("Microsoft.AspNetCore"), + HtmlEncoder.Default) { _executeAction = executeAction; }