diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelActivatorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelActivatorProvider.cs
new file mode 100644
index 0000000000..8789141087
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelActivatorProvider.cs
@@ -0,0 +1,27 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages
+{
+ ///
+ /// Provides methods to create a Razor Page model.
+ ///
+ public interface IPageModelActivatorProvider
+ {
+ ///
+ /// Creates a Razor Page model activator.
+ ///
+ /// The .
+ /// The delegate used to activate the page model.
+ Func CreateActivator(CompiledPageActionDescriptor descriptor);
+
+ ///
+ /// Releases a Razor Page model.
+ ///
+ /// The .
+ /// The delegate used to dispose the activated Razor Page model.
+ Action CreateReleaser(CompiledPageActionDescriptor descriptor);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelFactoryProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelFactoryProvider.cs
new file mode 100644
index 0000000000..b99a7b1a2c
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelFactoryProvider.cs
@@ -0,0 +1,27 @@
+// 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;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages
+{
+ ///
+ /// Provides methods for creation and disposal of Razor Page models.
+ ///
+ public interface IPageModelFactoryProvider
+ {
+ ///
+ /// Creates a factory for producing models for Razor Pages given the specified .
+ ///
+ /// The .
+ /// The Razor Page model factory.
+ Func CreateModelFactory(CompiledPageActionDescriptor descriptor);
+
+ ///
+ /// Releases a Razor Page model.
+ ///
+ /// The .
+ /// The delegate used to release the created Razor Page model.
+ Action CreateModelDisposer(CompiledPageActionDescriptor descriptor);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageActivator.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivator.cs
similarity index 97%
rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageActivator.cs
rename to src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivator.cs
index 53b58d42ff..5b06a51115 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageActivator.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivator.cs
@@ -5,7 +5,7 @@ using System;
using System.Linq.Expressions;
using System.Reflection;
-namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
///
/// that uses type activation to create Pages.
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageFactory.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactory.cs
similarity index 98%
rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageFactory.cs
rename to src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactory.cs
index cadd93913d..c8d4ec56d0 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageFactory.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactory.cs
@@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
-namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class DefaultPageFactory : IPageFactoryProvider
{
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelActivatorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelActivatorProvider.cs
new file mode 100644
index 0000000000..02d7631561
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelActivatorProvider.cs
@@ -0,0 +1,69 @@
+// 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.Reflection;
+using Microsoft.AspNetCore.Mvc.Internal;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
+{
+ ///
+ /// that uses type activation to create Razor Page instances.
+ ///
+ public class DefaultPageModelActivatorProvider : IPageModelActivatorProvider
+ {
+ private readonly Action _disposer = Dispose;
+
+ ///
+ public virtual Func CreateActivator(CompiledPageActionDescriptor actionDescriptor)
+ {
+ if (actionDescriptor == null)
+ {
+ throw new ArgumentNullException(nameof(actionDescriptor));
+ }
+
+ var modelTypeInfo = actionDescriptor.ModelTypeInfo?.AsType();
+ if (modelTypeInfo == null)
+ {
+ throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull(
+ nameof(actionDescriptor.ModelTypeInfo),
+ nameof(actionDescriptor)),
+ nameof(actionDescriptor));
+ }
+
+ var factory = ActivatorUtilities.CreateFactory(modelTypeInfo, Type.EmptyTypes);
+ return (context) => factory(context.HttpContext.RequestServices, EmptyArray.Instance);
+ }
+
+ public virtual Action CreateReleaser(CompiledPageActionDescriptor actionDescriptor)
+ {
+ if (actionDescriptor == null)
+ {
+ throw new ArgumentNullException(nameof(actionDescriptor));
+ }
+
+ if (typeof(IDisposable).GetTypeInfo().IsAssignableFrom(actionDescriptor.ModelTypeInfo))
+ {
+ return _disposer;
+ }
+
+ return null;
+ }
+
+ private static void Dispose(PageContext context, object page)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (page == null)
+ {
+ throw new ArgumentNullException(nameof(page));
+ }
+
+ ((IDisposable)page).Dispose();
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelFactoryProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelFactoryProvider.cs
new file mode 100644
index 0000000000..6821916a4f
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelFactoryProvider.cs
@@ -0,0 +1,70 @@
+// 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.Reflection;
+using Microsoft.Extensions.Internal;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
+{
+ public class DefaultPageModelFactoryProvider : IPageModelFactoryProvider
+ {
+ private static readonly Func> _createActivateInfo =
+ CreateActivateInfo;
+ private readonly IPageModelActivatorProvider _modelActivator;
+
+ public DefaultPageModelFactoryProvider(IPageModelActivatorProvider modelActivator)
+ {
+ _modelActivator = modelActivator;
+ }
+
+ public virtual Func CreateModelFactory(CompiledPageActionDescriptor descriptor)
+ {
+ if (descriptor == null)
+ {
+ throw new ArgumentNullException(nameof(descriptor));
+ }
+
+ if (descriptor.ModelTypeInfo == null)
+ {
+ return null;
+ }
+
+ var modelActivator = _modelActivator.CreateActivator(descriptor);
+ var propertyActivator = PropertyActivator.GetPropertiesToActivate(
+ descriptor.ModelTypeInfo.AsType(),
+ typeof(PageContextAttribute),
+ _createActivateInfo,
+ includeNonPublic: false);
+
+ return pageContext =>
+ {
+ var model = modelActivator(pageContext);
+ for (var i = 0; i < propertyActivator.Length; i++)
+ {
+ propertyActivator[i].Activate(model, pageContext);
+ }
+
+ return model;
+ };
+ }
+
+ public virtual Action CreateModelDisposer(CompiledPageActionDescriptor descriptor)
+ {
+ if (descriptor == null)
+ {
+ throw new ArgumentNullException(nameof(descriptor));
+ }
+
+ if (descriptor.ModelTypeInfo == null)
+ {
+ return null;
+ }
+
+ return _modelActivator.CreateReleaser(descriptor);
+ }
+
+ private static PropertyActivator CreateActivateInfo(PropertyInfo property) =>
+ new PropertyActivator(property, pageContext => pageContext);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
index 728c38da5b..e28e564f9e 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs
@@ -24,6 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private const string ModelPropertyName = "Model";
private readonly IPageLoader _loader;
private readonly IPageFactoryProvider _pageFactoryProvider;
+ private readonly IPageModelFactoryProvider _modelFactoryProvider;
private readonly IActionDescriptorCollectionProvider _collectionProvider;
private readonly IFilterProvider[] _filterProviders;
private readonly IReadOnlyList _valueProviderFactories;
@@ -38,6 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public PageActionInvokerProvider(
IPageLoader loader,
IPageFactoryProvider pageFactoryProvider,
+ IPageModelFactoryProvider modelFactoryProvider,
IActionDescriptorCollectionProvider collectionProvider,
IEnumerable filterProviders,
IEnumerable valueProviderFactories,
@@ -49,8 +51,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
ILoggerFactory loggerFactory)
{
_loader = loader;
- _collectionProvider = collectionProvider;
_pageFactoryProvider = pageFactoryProvider;
+ _modelFactoryProvider = modelFactoryProvider;
+ _collectionProvider = collectionProvider;
_filterProviders = filterProviders.ToArray();
_valueProviderFactories = valueProviderFactories.ToArray();
_modelMetadataProvider = modelMetadataProvider;
@@ -157,12 +160,23 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
PageTypeInfo = compiledType,
};
+ var pageFactory = _pageFactoryProvider.CreatePageFactory(compiledActionDescriptor);
+ var pageDisposer = _pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor);
+
+ Func modelFactory = null;
+ Action modelReleaser = null;
+ if (modelType != null)
+ {
+ modelFactory = _modelFactoryProvider.CreateModelFactory(compiledActionDescriptor);
+ modelReleaser = _modelFactoryProvider.CreateModelDisposer(compiledActionDescriptor);
+ }
+
return new PageActionInvokerCacheEntry(
compiledActionDescriptor,
- _pageFactoryProvider.CreatePageFactory(compiledActionDescriptor),
- _pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor),
- c => { throw new NotImplementedException(); },
- (_, __) => { throw new NotImplementedException(); },
+ pageFactory,
+ pageDisposer,
+ modelFactory,
+ modelReleaser,
cachedFilters);
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContextAttribute.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContextAttribute.cs
new file mode 100644
index 0000000000..d92ec99c26
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContextAttribute.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages
+{
+ ///
+ /// Specifies that a Razor Page model property should be set with the current when creating
+ /// the model instance. The property must have a public set method.
+ ///
+ [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)]
+ public class PageContextAttribute : Attribute
+ {
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageActivatorTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorTest.cs
similarity index 98%
rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageActivatorTest.cs
rename to test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorTest.cs
index 6d15679713..27b1da83c2 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageActivatorTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorTest.cs
@@ -8,7 +8,7 @@ using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Xunit;
-namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class DefaultPageActivatorTest
{
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryTest.cs
similarity index 99%
rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageFactoryTest.cs
rename to test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryTest.cs
index eadb731cb4..b54fd794a1 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageFactoryTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryTest.cs
@@ -18,7 +18,7 @@ using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using Xunit;
-namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class DefaultPageFactoryProviderTest
{
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelActivatorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelActivatorProviderTest.cs
new file mode 100644
index 0000000000..4e953e3dfe
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelActivatorProviderTest.cs
@@ -0,0 +1,155 @@
+// 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.Reflection;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc.ViewFeatures;
+using Microsoft.AspNetCore.Testing;
+using Microsoft.Extensions.DependencyInjection;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
+{
+ public class DefaultPageModelActivatorProviderTest
+ {
+ [Fact]
+ public void CreateActivator_ThrowsIfModelTypeInfoOnActionDescriptorIsNull()
+ {
+ // Arrange
+ var activatorProvider = new DefaultPageModelActivatorProvider();
+ var actionDescriptor = new CompiledPageActionDescriptor();
+
+ // Act & Assert
+ ExceptionAssert.ThrowsArgument(
+ () => activatorProvider.CreateActivator(actionDescriptor),
+ "actionDescriptor",
+ "The 'ModelTypeInfo' property of 'actionDescriptor' must not be null.");
+ }
+
+ [Fact]
+ public void CreateActivator_CreatesModelInstance()
+ {
+ // Arrange
+ var activatorProvider = new DefaultPageModelActivatorProvider();
+ var actionDescriptor = new CompiledPageActionDescriptor
+ {
+ ModelTypeInfo = typeof(SimpleModel).GetTypeInfo(),
+ };
+ var serviceCollection = new ServiceCollection();
+ var generator = Mock.Of();
+ serviceCollection.AddSingleton(generator);
+ var httpContext = new DefaultHttpContext
+ {
+ RequestServices = serviceCollection.BuildServiceProvider(),
+ };
+ var pageContext = new PageContext
+ {
+ HttpContext = httpContext
+ };
+
+ // Act
+ var activator = activatorProvider.CreateActivator(actionDescriptor);
+ var model = activator(pageContext);
+
+ // Assert
+ var simpleModel = Assert.IsType(model);
+ Assert.NotNull(simpleModel);
+ }
+
+ [Fact]
+ public void CreateActivator_TypeActivatesModelType()
+ {
+ // Arrange
+ var activatorProvider = new DefaultPageModelActivatorProvider();
+ var actionDescriptor = new CompiledPageActionDescriptor
+ {
+ ModelTypeInfo = typeof(ModelWithServices).GetTypeInfo(),
+ };
+ var serviceCollection = new ServiceCollection();
+ var generator = Mock.Of();
+ serviceCollection.AddSingleton(generator);
+ var httpContext = new DefaultHttpContext
+ {
+ RequestServices = serviceCollection.BuildServiceProvider(),
+ };
+ var pageContext = new PageContext
+ {
+ HttpContext = httpContext
+ };
+
+ // Act
+ var activator = activatorProvider.CreateActivator(actionDescriptor);
+ var model = activator(pageContext);
+
+ // Assert
+ var modelWithServices = Assert.IsType(model);
+ Assert.Same(generator, modelWithServices.Generator);
+ }
+
+ [Theory]
+ [InlineData(typeof(SimpleModel))]
+ [InlineData(typeof(object))]
+ public void CreateReleaser_ReturnsNullForModelsThatDoNotImplementDisposable(Type pageType)
+ {
+ // Arrange
+ var context = new PageContext();
+ var activator = new DefaultPageActivator();
+ var actionDescriptor = new CompiledPageActionDescriptor
+ {
+ PageTypeInfo = pageType.GetTypeInfo(),
+ };
+
+ // Act
+ var releaser = activator.CreateReleaser(actionDescriptor);
+
+ // Assert
+ Assert.Null(releaser);
+ }
+
+ [Fact]
+ public void CreateReleaser_CreatesDelegateThatDisposesDisposableTypes()
+ {
+ // Arrange
+ var context = new PageContext();
+ var activator = new DefaultPageActivator();
+ var actionDescriptor = new CompiledPageActionDescriptor
+ {
+ PageTypeInfo = typeof(DisposableModel).GetTypeInfo(),
+ };
+ var model = new DisposableModel();
+
+ // Act & Assert
+ var releaser = activator.CreateReleaser(actionDescriptor);
+ releaser(context, model);
+
+ // Assert
+ Assert.True(model.Disposed);
+ }
+
+ private class SimpleModel
+ {
+ }
+
+ private class ModelWithServices
+ {
+ public ModelWithServices(IHtmlGenerator generator)
+ {
+ Generator = generator;
+ }
+
+ public IHtmlGenerator Generator { get; }
+ }
+
+ private class DisposableModel : IDisposable
+ {
+ public bool Disposed { get; private set; }
+
+ public void Dispose()
+ {
+ Disposed = true;
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelFactoryProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelFactoryProviderTest.cs
new file mode 100644
index 0000000000..b405f17e2a
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelFactoryProviderTest.cs
@@ -0,0 +1,136 @@
+// 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.Reflection;
+using Moq;
+using Xunit;
+
+namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
+{
+ public class DefaultPageModelFactoryProviderTest
+ {
+ [Fact]
+ public void CreateModelFactory_ReturnsNullFactoryIfModelTypeIsNull()
+ {
+ // Arrange
+ var descriptor = new CompiledPageActionDescriptor();
+ var pageContext = new PageContext();
+ var factoryProvider = CreateModelFactoryProvider();
+
+ // Act
+ var factory = factoryProvider.CreateModelFactory(descriptor);
+
+ // Assert
+ Assert.Null(factory);
+ }
+
+ [Fact]
+ public void CreateModelDisposer_ReturnsNullFactoryIfModelTypeIsNull()
+ {
+ // Arrange
+ var descriptor = new CompiledPageActionDescriptor();
+ var pageContext = new PageContext();
+ var factoryProvider = CreateModelFactoryProvider();
+
+ // Act
+ var disposer = factoryProvider.CreateModelDisposer(descriptor);
+
+ // Assert
+ Assert.Null(disposer);
+ }
+
+ [Fact]
+ public void ModelFactory_InitializesModelInstances()
+ {
+ // Arrange
+ var descriptor = new CompiledPageActionDescriptor
+ {
+ ModelTypeInfo = typeof(SimpleModel).GetTypeInfo(),
+ };
+ var pageContext = new PageContext();
+ var factoryProvider = CreateModelFactoryProvider();
+
+ // Act
+ var factory = factoryProvider.CreateModelFactory(descriptor);
+ var instance = factory(pageContext);
+
+ // Assert
+ var model = Assert.IsType(instance);
+ Assert.NotNull(model);
+ }
+
+ [Fact]
+ public void ModelFactory_InjectsPropertiesWithPageContextAttribute()
+ {
+ // Arrange
+ var descriptor = new CompiledPageActionDescriptor
+ {
+ ModelTypeInfo = typeof(ModelWithPageContext).GetTypeInfo(),
+ };
+ var pageContext = new PageContext();
+ var factoryProvider = CreateModelFactoryProvider();
+
+ // Act
+ var factory = factoryProvider.CreateModelFactory(descriptor);
+ var instance = factory(pageContext);
+
+ // Assert
+ var testModel = Assert.IsType(instance);
+ Assert.Same(pageContext, testModel.ContextWithAttribute);
+ Assert.Null(testModel.ContextWithoutAttribute);
+ }
+
+ [Fact]
+ public void CreateModelDisposer_ReturnsDisposerFromModelActivatorProvider()
+ {
+ // Arrange
+ var descriptor = new CompiledPageActionDescriptor
+ {
+ ModelTypeInfo = typeof(SimpleModel).GetTypeInfo()
+ };
+ var pageContext = new PageContext();
+ var modelActivatorProvider = new Mock();
+ Action disposer = (_, __) => { };
+ modelActivatorProvider.Setup(p => p.CreateReleaser(descriptor))
+ .Returns(disposer);
+ var factoryProvider = CreateModelFactoryProvider(modelActivatorProvider.Object);
+
+ // Act
+ var actual = factoryProvider.CreateModelDisposer(descriptor);
+
+ // Assert
+ Assert.Same(disposer, actual);
+ }
+
+ private static DefaultPageModelFactoryProvider CreateModelFactoryProvider(
+ IPageModelActivatorProvider modelActivator = null)
+ {
+ if (modelActivator == null)
+ {
+ var mockActivator = new Mock();
+ mockActivator.Setup(a => a.CreateActivator(It.IsAny()))
+ .Returns((CompiledPageActionDescriptor descriptor) =>
+ {
+ return (context) => Activator.CreateInstance(descriptor.ModelTypeInfo.AsType());
+ });
+
+ modelActivator = mockActivator.Object;
+ }
+
+ return new DefaultPageModelFactoryProvider(modelActivator);
+ }
+
+ private class SimpleModel
+ {
+ }
+
+ private class ModelWithPageContext
+ {
+ [PageContext]
+ public PageContext ContextWithAttribute { get; set; }
+
+ public PageContext ContextWithoutAttribute { get; set; }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
index 38c5876b99..acc3aa8c75 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs
@@ -3,24 +3,24 @@
using System;
using System.Diagnostics;
+using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
+using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Logging.Testing;
using Moq;
using Xunit;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Routing;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class PageInvokerProviderTest
{
[Fact]
- public void OnProvidersExecuting_PopulatesCacheEntry()
+ public void OnProvidersExecuting_WithEmptyModel_PopulatesCacheEntry()
{
// Arrange
var descriptor = new PageActionDescriptor
@@ -30,22 +30,23 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
};
Func factory = _ => null;
Action releaser = (_, __) => { };
+
var loader = new Mock();
loader.Setup(l => l.Load(It.IsAny()))
.Returns(typeof(object));
var descriptorCollection = new ActionDescriptorCollection(new[] { descriptor }, version: 1);
var actionDescriptorProvider = new Mock();
actionDescriptorProvider.Setup(p => p.ActionDescriptors).Returns(descriptorCollection);
- var factoryProvider = new Mock();
- factoryProvider.Setup(f => f.CreatePageFactory(It.IsAny()))
+ var pageFactoryProvider = new Mock();
+ pageFactoryProvider.Setup(f => f.CreatePageFactory(It.IsAny()))
.Returns(factory);
- factoryProvider.Setup(f => f.CreatePageDisposer(It.IsAny()))
+ pageFactoryProvider.Setup(f => f.CreatePageDisposer(It.IsAny()))
.Returns(releaser);
var invokerProvider = CreateInvokerProvider(
loader.Object,
actionDescriptorProvider.Object,
- factoryProvider.Object);
+ pageFactoryProvider.Object);
var context = new ActionInvokerProviderContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
@@ -60,6 +61,63 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Equal(descriptor.RelativePath, compiledPageActionDescriptor.RelativePath);
Assert.Same(factory, entry.PageFactory);
Assert.Same(releaser, entry.ReleasePage);
+ Assert.Null(entry.ModelFactory);
+ Assert.Null(entry.ReleaseModel);
+ }
+
+ [Fact]
+ public void OnProvidersExecuting_WithModel_PopulatesCacheEntry()
+ {
+ // Arrange
+ var descriptor = new PageActionDescriptor
+ {
+ RelativePath = "Path1",
+ FilterDescriptors = new FilterDescriptor[0],
+ };
+ Func factory = _ => null;
+ Action releaser = (_, __) => { };
+ Func modelFactory = _ => null;
+ Action modelDisposer = (_, __) => { };
+
+ 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 pageFactoryProvider = new Mock();
+ pageFactoryProvider.Setup(f => f.CreatePageFactory(It.IsAny()))
+ .Returns(factory);
+ pageFactoryProvider.Setup(f => f.CreatePageDisposer(It.IsAny()))
+ .Returns(releaser);
+
+ var modelFactoryProvider = new Mock();
+ modelFactoryProvider.Setup(f => f.CreateModelFactory(It.IsAny()))
+ .Returns(modelFactory);
+ modelFactoryProvider.Setup(f => f.CreateModelDisposer(It.IsAny()))
+ .Returns(modelDisposer);
+
+ var invokerProvider = CreateInvokerProvider(
+ loader.Object,
+ actionDescriptorProvider.Object,
+ pageFactoryProvider.Object,
+ modelFactoryProvider.Object);
+ 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;
+ var compiledPageActionDescriptor = Assert.IsType(entry.ActionDescriptor);
+ Assert.Equal(descriptor.RelativePath, compiledPageActionDescriptor.RelativePath);
+ Assert.Same(factory, entry.PageFactory);
+ Assert.Same(releaser, entry.ReleasePage);
+ Assert.Same(modelFactory, entry.ModelFactory);
+ Assert.Same(modelDisposer, entry.ReleaseModel);
}
[Fact]
@@ -80,8 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var invokerProvider = CreateInvokerProvider(
loader.Object,
- actionDescriptorProvider.Object,
- Mock.Of());
+ actionDescriptorProvider.Object);
var context = new ActionInvokerProviderContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
@@ -124,8 +181,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
.Returns(typeof(object));
var invokerProvider = CreateInvokerProvider(
loader.Object,
- actionDescriptorProvider.Object,
- Mock.Of());
+ actionDescriptorProvider.Object);
var context = new ActionInvokerProviderContext(
new ActionContext(new DefaultHttpContext(), new RouteData(), descriptor));
@@ -150,7 +206,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private static PageActionInvokerProvider CreateInvokerProvider(
IPageLoader loader,
IActionDescriptorCollectionProvider actionDescriptorProvider,
- IPageFactoryProvider factoryProvider)
+ IPageFactoryProvider pageProvider = null,
+ IPageModelFactoryProvider modelProvider = null)
{
var tempDataFactory = new Mock();
tempDataFactory.Setup(t => t.GetTempData(It.IsAny()))
@@ -158,7 +215,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
return new PageActionInvokerProvider(
loader,
- factoryProvider,
+ pageProvider ?? Mock.Of(),
+ modelProvider ?? Mock.Of(),
actionDescriptorProvider,
new IFilterProvider[0],
new IValueProviderFactory[0],
@@ -169,5 +227,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
new DiagnosticListener("Microsoft.AspNetCore"),
NullLoggerFactory.Instance);
}
+
+ private class PageWithModel
+ {
+ public object Model { get; set; }
+ }
}
}