diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs index 35a6e75a45..fd8e5b055c 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs @@ -110,9 +110,11 @@ namespace Microsoft.Extensions.DependencyInjection ServiceDescriptor.Singleton()); // Page and Page model creation and activation - services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs new file mode 100644 index 0000000000..9ea5c53790 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs @@ -0,0 +1,43 @@ +// 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.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + /// + /// that uses type activation to create Razor Page instances. + /// + public class ServiceBasedPageModelActivatorProvider : IPageModelActivatorProvider + { + public Func CreateActivator(CompiledPageActionDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + var modelTypeInfo = descriptor.ModelTypeInfo?.AsType(); + if (modelTypeInfo == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(descriptor.ModelTypeInfo), + nameof(descriptor)), + nameof(descriptor)); + } + + return context => + { + return context.HttpContext.RequestServices.GetRequiredService(modelTypeInfo); + }; + } + + public Action CreateReleaser(CompiledPageActionDescriptor descriptor) + { + return null; + } + } +} + diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs new file mode 100644 index 0000000000..f1ead9d4bd --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs @@ -0,0 +1,152 @@ +// 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.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class ServiceBasedPageModelActivatorProviderTest + { + [Fact] + public void CreateActivator_ThrowsIfModelTypeInfoOnActionDescriptorIsNull() + { + // Arrange + + var activatorProvider = new ServiceBasedPageModelActivatorProvider(); + var descriptor = new CompiledPageActionDescriptor(); + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => activatorProvider.CreateActivator(descriptor), + "descriptor", + "The 'ModelTypeInfo' property of 'descriptor' must not be null."); + } + + [Fact] + public void Create_GetsServicesFromServiceProvider() + { + // Arrange + var simpleModel = new DISimpleModel(); + var serviceProvider = new Mock(MockBehavior.Strict); + serviceProvider.Setup(s => s.GetService(typeof(DISimpleModel))) + .Returns(simpleModel) + .Verifiable(); + + var activatorProvider = new ServiceBasedPageModelActivatorProvider(); + var pageContext = new PageContext + { + HttpContext = new DefaultHttpContext + { + RequestServices = serviceProvider.Object, + }, + ActionDescriptor = new CompiledPageActionDescriptor + { + ModelTypeInfo = typeof(DISimpleModel).GetTypeInfo(), + } + }; + + // Act + var activator = activatorProvider.CreateActivator(pageContext.ActionDescriptor); + var instance = activator(pageContext); + + // Assert + Assert.Same(simpleModel, instance); + serviceProvider.Verify(); + } + + [Fact] + public void CreateActivator_CreatesModelInstance() + { + // Arrange + var controller = new DISimpleModel(); + var serviceProvider = new Mock(MockBehavior.Strict); + serviceProvider.Setup(s => s.GetService(typeof(DISimpleModel))) + .Returns(controller) + .Verifiable(); + + var activatorProvider = new ServiceBasedPageModelActivatorProvider(); + var pageContext = new PageContext + { + HttpContext = new DefaultHttpContext + { + RequestServices = serviceProvider.Object, + }, + ActionDescriptor = new CompiledPageActionDescriptor + { + ModelTypeInfo = typeof(DISimpleModel).GetTypeInfo(), + } + }; + + // Act + var activator = activatorProvider.CreateActivator(pageContext.ActionDescriptor); + var model = activator(pageContext); + + // Assert + var simpleModel = Assert.IsType(model); + Assert.NotNull(simpleModel); + } + + [Fact] + public void Create_ThrowsIfModelIsNotRegisteredInServiceProvider() + { + // Arrange + var expected = "No service for type '" + typeof(DISimpleModel) + "' has been registered."; + var model = new DISimpleModel(); + + var httpContext = new DefaultHttpContext + { + RequestServices = Mock.Of() + }; + + var activatorProvider = new ServiceBasedPageModelActivatorProvider(); + var context = new PageContext + { + HttpContext = httpContext, + ActionDescriptor = new CompiledPageActionDescriptor + { + ModelTypeInfo = typeof(DISimpleModel).GetTypeInfo(), + } + }; + + // Act and Assert + var activator = activatorProvider.CreateActivator(context.ActionDescriptor); + var ex = Assert.Throws( + () => activator(context)); + + Assert.Equal(expected, ex.Message); + } + + [Theory] + [InlineData(typeof(SimpleModel))] + [InlineData(typeof(object))] + public void CreateReleaser_ReturnsNullForPageModels(Type pageType) + { + // Arrange + var context = new PageContext(); + var activator = new ServiceBasedPageModelActivatorProvider(); + var actionDescriptor = new CompiledPageActionDescriptor + { + PageTypeInfo = pageType.GetTypeInfo(), + }; + + // Act + var releaser = activator.CreateReleaser(actionDescriptor); + + // Assert + Assert.Null(releaser); + } + + private class SimpleModel + { + } + + private class DISimpleModel : SimpleModel + { + } + } +}