Remove pass-through activator for pages

This change removes a 'pass through' IRazorPageActivator that we were
using to avoid the default one initializing the Page twice.

The fix is to add an adapter so that the IRazorPage that the RazorView has
isn't the *real* page, it's the adapter. The adapter doesn't have anything
interesting to activate :).
This commit is contained in:
Ryan Nowak 2017-05-24 17:32:28 -07:00
parent c3f7613725
commit b7db6dbc8e
13 changed files with 219 additions and 456 deletions

View File

@ -30,9 +30,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
_metadataProvider = metadataProvider;
_viewDataDictionaryType = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
_rootFactory = ViewDataDictionaryFactory.CreateFactory(modelType.GetTypeInfo());
_nestedFactory = ViewDataDictionaryFactory.CreateNestedFactory(modelType.GetTypeInfo());
if (modelType != null)
{
_viewDataDictionaryType = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
_rootFactory = ViewDataDictionaryFactory.CreateFactory(modelType.GetTypeInfo());
_nestedFactory = ViewDataDictionaryFactory.CreateNestedFactory(modelType.GetTypeInfo());
}
_propertyActivators = PropertyActivator<ViewContext>.GetPropertiesToActivate(
pageType,
@ -48,7 +52,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
throw new ArgumentNullException(nameof(context));
}
context.ViewData = CreateViewDataDictionary(context);
if (_viewDataDictionaryType != null)
{
context.ViewData = CreateViewDataDictionary(context);
}
for (var i = 0; i < _propertyActivators.Length; i++)
{

View File

@ -80,48 +80,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
internal static string FormatLayoutCannotBeRendered(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("LayoutCannotBeRendered"), p0, p1);
/// <summary>
/// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
/// </summary>
internal static string MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword
{
get => GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword");
}
/// <summary>
/// The 'inherits' keyword is not allowed when a '{0}' keyword is used.
/// </summary>
internal static string FormatMvcRazorCodeParser_CannotHaveModelAndInheritsKeyword(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword"), p0);
/// <summary>
/// The '{0}' keyword must be followed by a type name on the same line.
/// </summary>
internal static string MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName
{
get => GetString("MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName");
}
/// <summary>
/// The '{0}' keyword must be followed by a type name on the same line.
/// </summary>
internal static string FormatMvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName"), p0);
/// <summary>
/// Only one '{0}' statement is allowed in a file.
/// </summary>
internal static string MvcRazorCodeParser_OnlyOneModelStatementIsAllowed
{
get => GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed");
}
/// <summary>
/// Only one '{0}' statement is allowed in a file.
/// </summary>
internal static string FormatMvcRazorCodeParser_OnlyOneModelStatementIsAllowed(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"), p0);
/// <summary>
/// There is no active writing scope to end.
/// </summary>
@ -234,20 +192,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
internal static string FormatSectionsNotRendered(object p0, object p1, object p2)
=> string.Format(CultureInfo.CurrentCulture, GetString("SectionsNotRendered"), p0, p1, p2);
/// <summary>
/// View of type '{0}' cannot be activated by '{1}'.
/// </summary>
internal static string ViewCannotBeActivated
{
get => GetString("ViewCannotBeActivated");
}
/// <summary>
/// View of type '{0}' cannot be activated by '{1}'.
/// </summary>
internal static string FormatViewCannotBeActivated(object p0, object p1)
=> string.Format(CultureInfo.CurrentCulture, GetString("ViewCannotBeActivated"), p0, p1);
/// <summary>
/// '{0} must be set to access '{1}'.
/// </summary>

View File

@ -67,15 +67,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor
if (!_activationInfo.TryGetValue(pageType, out propertyActivator))
{
// Look for a property named "Model". If it is non-null, we'll assume this is
// the equivalent of TModel Model property on RazorPage<TModel>
var modelProperty = pageType.GetRuntimeProperty(ModelPropertyName);
if (modelProperty == null)
{
var message = Resources.FormatViewCannotBeActivated(pageType.FullName, GetType().FullName);
throw new InvalidOperationException(message);
}
var modelType = modelProperty.PropertyType;
// the equivalent of TModel Model property on RazorPage<TModel>.
//
// Otherwise if we don't have a model property the activator will just skip setting
// the view data.
var modelType = pageType.GetRuntimeProperty(ModelPropertyName)?.PropertyType;
propertyActivator = new RazorPagePropertyActivator(
pageType,
modelType,

View File

@ -106,7 +106,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
/// <remarks>Returns null if <see cref="ViewContext"/> is null.</remarks>
public ITempDataDictionary TempData => ViewContext?.TempData;
protected Stack<TagHelperScopeInfo> TagHelperScopes { get; } = new Stack<TagHelperScopeInfo>();
private Stack<TagHelperScopeInfo> TagHelperScopes { get; } = new Stack<TagHelperScopeInfo>();
private ITagHelperFactory TagHelperFactory
{
@ -755,7 +755,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
public bool Suppressed { get; set; }
}
protected struct TagHelperScopeInfo
private struct TagHelperScopeInfo
{
public TagHelperScopeInfo(ViewBuffer buffer, HtmlEncoder encoder, TextWriter writer)
{

View File

@ -132,15 +132,6 @@
<data name="LayoutCannotBeRendered" xml:space="preserve">
<value>Layout page '{0}' cannot be rendered after '{1}' has been invoked.</value>
</data>
<data name="MvcRazorCodeParser_CannotHaveModelAndInheritsKeyword" xml:space="preserve">
<value>The 'inherits' keyword is not allowed when a '{0}' keyword is used.</value>
</data>
<data name="MvcRazorCodeParser_ModelKeywordMustBeFollowedByTypeName" xml:space="preserve">
<value>The '{0}' keyword must be followed by a type name on the same line.</value>
</data>
<data name="MvcRazorCodeParser_OnlyOneModelStatementIsAllowed" xml:space="preserve">
<value>Only one '{0}' statement is allowed in a file.</value>
</data>
<data name="RazorPage_ThereIsNoActiveWritingScopeToEnd" xml:space="preserve">
<value>There is no active writing scope to end.</value>
</data>
@ -165,9 +156,6 @@
<data name="SectionsNotRendered" xml:space="preserve">
<value>The following sections have been defined but have not been rendered by the page at '{0}': '{1}'. To ignore an unrendered section call {2}("sectionName").</value>
</data>
<data name="ViewCannotBeActivated" xml:space="preserve">
<value>View of type '{0}' cannot be activated by '{1}'.</value>
</data>
<data name="ViewContextMustBeSet" xml:space="preserve">
<value>'{0} must be set to access '{1}'.</value>
</data>

View File

@ -114,7 +114,7 @@ namespace Microsoft.Extensions.DependencyInjection
services.TryAddSingleton<IPageModelFactoryProvider, DefaultPageModelFactoryProvider>();
services.TryAddSingleton<IPageActivatorProvider, DefaultPageActivatorProvider>();
services.TryAddSingleton<IPageFactoryProvider, DefaultPageFactory>();
services.TryAddSingleton<IPageFactoryProvider, DefaultPageFactoryProvider>();
services.TryAddSingleton<IPageLoader, DefaultPageLoader>();
services.TryAddSingleton<IPageHandlerMethodSelector, DefaultPageHandlerMethodSelector>();

View File

@ -13,13 +13,13 @@ using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
public class DefaultPageFactory : IPageFactoryProvider
public class DefaultPageFactoryProvider : IPageFactoryProvider
{
private readonly IPageActivatorProvider _pageActivator;
private readonly IModelMetadataProvider _modelMetadataProvider;
private readonly RazorPagePropertyActivator.PropertyValueAccessors _propertyAccessors;
public DefaultPageFactory(
public DefaultPageFactoryProvider(
IPageActivatorProvider pageActivator,
IModelMetadataProvider metadataProvider,
IUrlHelperFactory urlHelperFactory,

View File

@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
_razorViewEngine = razorViewEngine;
_htmlEncoder = htmlEncoder;
_razorPageActivator = new PassThruRazorPageActivator(razorPageActivator);
_razorPageActivator = razorPageActivator;
}
/// <summary>
@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
_razorViewEngine,
_razorPageActivator,
viewStarts,
result.Page,
new RazorPageAdapter(result.Page),
_htmlEncoder);
return ExecuteAsync(viewContext, result.ContentType, result.StatusCode);

View File

@ -0,0 +1,80 @@
// 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.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
{
// Implements IRazorPage so that RazorPageBase-derived classes don't get activated twice.
//
// The page gets activated before handler methods run, but the RazorView will also activate
// each page.
public class RazorPageAdapter : IRazorPage
{
private readonly RazorPageBase _page;
public RazorPageAdapter(RazorPageBase page)
{
if (page == null)
{
throw new ArgumentNullException(nameof(page));
}
_page = page;
}
public ViewContext ViewContext
{
get { return _page.ViewContext; }
set { _page.ViewContext = value; }
}
public IHtmlContent BodyContent
{
get { return _page.BodyContent; }
set { _page.BodyContent = value; }
}
public bool IsLayoutBeingRendered
{
get { return _page.IsLayoutBeingRendered; }
set { _page.IsLayoutBeingRendered = value; }
}
public string Path
{
get { return _page.Path; }
set { _page.Path = value; }
}
public string Layout
{
get { return _page.Layout; }
set { _page.Layout = value; }
}
public IDictionary<string, RenderAsyncDelegate> PreviousSectionWriters
{
get { return _page.PreviousSectionWriters; }
set { _page.PreviousSectionWriters = value; }
}
public IDictionary<string, RenderAsyncDelegate> SectionWriters => _page.SectionWriters;
public void EnsureRenderedBodyOrSections()
{
_page.EnsureRenderedBodyOrSections();
}
public Task ExecuteAsync()
{
return _page.ExecuteAsync();
}
}
}

View File

@ -1,39 +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 Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class PassThruRazorPageActivator : IRazorPageActivator
{
private readonly IRazorPageActivator _pageActivator;
public PassThruRazorPageActivator(IRazorPageActivator pageActivator)
{
_pageActivator = pageActivator;
}
public void Activate(IRazorPage page, ViewContext context)
{
var razorView = (RazorView)context.View;
if (ReferenceEquals(page, razorView.RazorPage))
{
var actionDescriptor = (CompiledPageActionDescriptor)context.ActionDescriptor;
var vddType = typeof(ViewDataDictionary<>);
var modelTypeInfo = actionDescriptor.ModelTypeInfo ?? actionDescriptor.PageTypeInfo;
vddType = vddType.MakeGenericType(modelTypeInfo.AsType());
context.ViewData = (ViewDataDictionary)Activator.CreateInstance(vddType, context.ViewData);
}
else
{
_pageActivator.Activate(page, context);
}
}
}
}

View File

@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// A base class for a Razor page.
/// </summary>
[PagesBaseClass]
public abstract class PageBase : RazorPageBase, IRazorPage
public abstract class PageBase : RazorPageBase
{
private IObjectModelValidator _objectValidator;
private IModelMetadataProvider _metadataProvider;
@ -112,6 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// <inheritdoc />
public override void EnsureRenderedBodyOrSections()
{
// This will never be called by MVC. MVC only calls this method on layout pages, and a Page can never be a layout page.
throw new NotSupportedException();
}

View File

@ -2,13 +2,12 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Rendering;
@ -20,339 +19,185 @@ using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.WebEncoders.Testing;
using Moq;
using Newtonsoft.Json;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor
{
public class RazorPageActivatorTest
{
public RazorPageActivatorTest()
{
DiagnosticSource = new DiagnosticListener("Microsoft.AspNetCore");
HtmlEncoder = new HtmlTestEncoder();
JsonHelper = Mock.Of<IJsonHelper>();
MetadataProvider = new EmptyModelMetadataProvider();
ModelExpressionProvider = new ModelExpressionProvider(MetadataProvider, new ExpressionTextCache());
UrlHelperFactory = new UrlHelperFactory();
}
private DiagnosticSource DiagnosticSource { get; }
private HtmlEncoder HtmlEncoder { get; }
private IJsonHelper JsonHelper { get; }
private IModelMetadataProvider MetadataProvider { get; }
private IModelExpressionProvider ModelExpressionProvider { get; }
private IUrlHelperFactory UrlHelperFactory { get; }
[Fact]
public void Activate_ActivatesAndContextualizesPropertiesOnViews()
public void Activate_ContextualizesServices_AndSetsProperties_OnPage()
{
// Arrange
var modelMetadataProvider = new EmptyModelMetadataProvider();
var modelExpressionProvider = new ModelExpressionProvider(modelMetadataProvider, new ExpressionTextCache());
var urlHelperFactory = new UrlHelperFactory();
var jsonHelper = new JsonHelper(
new JsonOutputFormatter(new JsonSerializerSettings(), ArrayPool<char>.Shared),
ArrayPool<char>.Shared);
var htmlEncoder = new HtmlTestEncoder();
var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore");
var activator = new RazorPageActivator(
new EmptyModelMetadataProvider(),
urlHelperFactory,
jsonHelper,
diagnosticSource,
htmlEncoder,
modelExpressionProvider);
var activator = CreateActivator();
var instance = new TestRazorPage();
var viewData = new ViewDataDictionary<MyModel>(MetadataProvider, new ModelStateDictionary());
var viewContext = CreateViewContext();
var myService = new MyService();
var helper = Mock.Of<IHtmlHelper<object>>();
var serviceProvider = new ServiceCollection()
.AddSingleton(myService)
.AddSingleton(helper)
.AddSingleton(new ExpressionTextCache())
.BuildServiceProvider();
var httpContext = new DefaultHttpContext
{
RequestServices = serviceProvider
};
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var viewContext = new ViewContext(
actionContext,
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null,
new HtmlHelperOptions());
var urlHelper = urlHelperFactory.GetUrlHelper(viewContext);
var urlHelper = UrlHelperFactory.GetUrlHelper(viewContext);
// Act
activator.Activate(instance, viewContext);
// Assert
Assert.Same(helper, instance.Html);
Assert.Same(myService, instance.MyService);
Assert.Same(viewContext, myService.ViewContext);
Assert.Same(diagnosticSource, instance.DiagnosticSource);
Assert.Same(htmlEncoder, instance.HtmlEncoder);
Assert.Same(jsonHelper, instance.Json);
Assert.Same(DiagnosticSource, instance.DiagnosticSource);
Assert.Same(HtmlEncoder, instance.HtmlEncoder);
Assert.Same(JsonHelper, instance.Json);
Assert.Same(urlHelper, instance.Url);
Assert.Same(viewContext.ViewData, instance.ViewData);
// Has no [RazorInject] so it shouldn't get injected
Assert.Null(instance.MyService2);
// We're not testing the IViewContextualizable implementation here because it's a mock.
Assert.NotNull(instance.Html);
Assert.IsAssignableFrom<IHtmlHelper<object>>(instance.Html);
var service = instance.MyService;
Assert.NotNull(service);
Assert.Same(viewContext, service.ViewContext);
}
[Fact]
public void Activate_ThrowsIfTheViewDoesNotDeriveFromRazorViewOfT()
public void Activate_ContextualizesServices_AndSetsProperties_OnPageWithoutModel()
{
// Arrange
var modelMetadataProvider = new EmptyModelMetadataProvider();
var modelExpressionProvider = new ModelExpressionProvider(modelMetadataProvider, new ExpressionTextCache());
var activator = new RazorPageActivator(
modelMetadataProvider,
new UrlHelperFactory(),
new JsonHelper(
new JsonOutputFormatter(new JsonSerializerSettings(), ArrayPool<char>.Shared),
ArrayPool<char>.Shared),
new DiagnosticListener("Microsoft.AspNetCore"),
new HtmlTestEncoder(),
modelExpressionProvider);
var activator = CreateActivator();
var instance = new DoesNotDeriveFromRazorPageOfT();
var viewData = new ViewDataDictionary<object>(MetadataProvider, new ModelStateDictionary());
var viewContext = CreateViewContext(viewData);
var myService = new MyService();
var helper = Mock.Of<IHtmlHelper<object>>();
var serviceProvider = new Mock<IServiceProvider>();
var httpContext = new DefaultHttpContext
{
RequestServices = new ServiceCollection().BuildServiceProvider()
};
var urlHelper = UrlHelperFactory.GetUrlHelper(viewContext);
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var viewContext = new ViewContext(
actionContext,
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null,
new HtmlHelperOptions());
var instance = new NoModelPropertyPage();
// Act and Assert
var ex = Assert.Throws<InvalidOperationException>(() => activator.Activate(instance, viewContext));
var message = $"View of type '{instance.GetType()}' cannot be activated by '{typeof(RazorPageActivator)}'.";
Assert.Equal(message, ex.Message);
// Act
activator.Activate(instance, viewContext);
// Assert
Assert.Same(DiagnosticSource, instance.DiagnosticSource);
Assert.Same(HtmlEncoder, instance.HtmlEncoder);
// When we don't have a model property, the activator will just leave viewdata alone.
Assert.NotNull(viewContext.ViewData);
}
[Fact]
public void Activate_InstantiatesNewViewDataDictionaryType_IfTheTypeDoesNotMatch()
{
// Arrange
var modelMetadataProvider = new EmptyModelMetadataProvider();
var modelExpressionProvider = new ModelExpressionProvider(modelMetadataProvider, new ExpressionTextCache());
var activator = new RazorPageActivator(
modelMetadataProvider,
new UrlHelperFactory(),
new JsonHelper(
new JsonOutputFormatter(new JsonSerializerSettings(), ArrayPool<char>.Shared),
ArrayPool<char>.Shared),
new DiagnosticListener("Microsoft.AspNetCore.Mvc"),
new HtmlTestEncoder(),
modelExpressionProvider);
var activator = CreateActivator();
var viewData = new ViewDataDictionary<object>(MetadataProvider, new ModelStateDictionary())
{
{ "key", "value" },
};
var viewContext = CreateViewContext(viewData);
var urlHelper = UrlHelperFactory.GetUrlHelper(viewContext);
var instance = new TestRazorPage();
var myService = new MyService();
var helper = Mock.Of<IHtmlHelper<object>>();
var serviceProvider = new ServiceCollection()
.AddSingleton(myService)
.AddSingleton(helper)
.AddSingleton(new ExpressionTextCache())
.BuildServiceProvider();
var httpContext = new DefaultHttpContext
{
RequestServices = serviceProvider
};
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var viewData = new ViewDataDictionary<object>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = new MyModel()
};
var viewContext = new ViewContext(
actionContext,
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null,
new HtmlHelperOptions());
// Act
activator.Activate(instance, viewContext);
// Assert
Assert.IsType<ViewDataDictionary<MyModel>>(viewContext.ViewData);
}
Assert.Same(DiagnosticSource, instance.DiagnosticSource);
Assert.Same(HtmlEncoder, instance.HtmlEncoder);
Assert.Same(JsonHelper, instance.Json);
Assert.Same(urlHelper, instance.Url);
Assert.Same(viewContext.ViewData, instance.ViewData);
[Fact]
public void Activate_UsesPassedInViewDataDictionaryInstance_IfPassedInTypeMatches()
{
// Arrange
var modelMetadataProvider = new EmptyModelMetadataProvider();
var modelExpressionProvider = new ModelExpressionProvider(modelMetadataProvider, new ExpressionTextCache());
var activator = new RazorPageActivator(
modelMetadataProvider,
new UrlHelperFactory(),
new JsonHelper(
new JsonOutputFormatter(new JsonSerializerSettings(), ArrayPool<char>.Shared),
ArrayPool<char>.Shared),
new DiagnosticListener("Microsoft.AspNetCore.Mvc"),
new HtmlTestEncoder(),
modelExpressionProvider);
// The original ViewDataDictionary was replaced.
Assert.NotSame(viewData, viewContext.ViewData);
Assert.NotSame(viewData, instance.ViewData);
var instance = new TestRazorPage();
var myService = new MyService();
var helper = Mock.Of<IHtmlHelper<object>>();
var serviceProvider = new ServiceCollection()
.AddSingleton(myService)
.AddSingleton(helper)
.AddSingleton(new ExpressionTextCache())
.BuildServiceProvider();
var httpContext = new DefaultHttpContext
{
RequestServices = serviceProvider
};
// But this value is copied
Assert.Equal("value", viewData["key"]);
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var viewData = new ViewDataDictionary<MyModel>(new EmptyModelMetadataProvider(), new ModelStateDictionary())
{
Model = new MyModel()
};
var viewContext = new ViewContext(
actionContext,
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null,
new HtmlHelperOptions());
// Has no [RazorInject] so it shouldn't get injected
Assert.Null(instance.MyService2);
// Act
activator.Activate(instance, viewContext);
// We're not testing the IViewContextualizable implementation here because it's a mock.
Assert.NotNull(instance.Html);
Assert.IsAssignableFrom<IHtmlHelper<object>>(instance.Html);
// Assert
Assert.Same(viewData, viewContext.ViewData);
}
[Fact]
public void Activate_DeterminesModelTypeFromProperty()
{
// Arrange
var modelMetadataProvider = new EmptyModelMetadataProvider();
var modelExpressionProvider = new ModelExpressionProvider(modelMetadataProvider, new ExpressionTextCache());
var activator = new RazorPageActivator(
modelMetadataProvider,
new UrlHelperFactory(),
new JsonHelper(
new JsonOutputFormatter(new JsonSerializerSettings(), ArrayPool<char>.Shared),
ArrayPool<char>.Shared),
new DiagnosticListener("Microsoft.AspNetCore.Mvc"),
new HtmlTestEncoder(),
modelExpressionProvider);
var instance = new DoesNotDeriveFromRazorPageOfTButHasModelProperty();
var myService = new MyService();
var helper = Mock.Of<IHtmlHelper<object>>();
var serviceProvider = new ServiceCollection()
.AddSingleton(myService)
.AddSingleton(helper)
.AddSingleton(new ExpressionTextCache())
.BuildServiceProvider();
var httpContext = new DefaultHttpContext
{
RequestServices = serviceProvider
};
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var viewData = new ViewDataDictionary<object>(new EmptyModelMetadataProvider(), new ModelStateDictionary());
var viewContext = new ViewContext(
actionContext,
Mock.Of<IView>(),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null,
new HtmlHelperOptions());
// Act
activator.Activate(instance, viewContext);
// Assert
Assert.IsType<ViewDataDictionary<string>>(viewContext.ViewData);
var service = instance.MyService;
Assert.NotNull(service);
Assert.Same(viewContext, service.ViewContext);
}
[Fact]
public void Activate_Throws_WhenViewDataPropertyHasIncorrectType()
{
// Arrange
var modelMetadataProvider = new EmptyModelMetadataProvider();
var modelExpressionProvider = new ModelExpressionProvider(modelMetadataProvider, new ExpressionTextCache());
var activator = new RazorPageActivator(
modelMetadataProvider,
new UrlHelperFactory(),
new JsonHelper(
new JsonOutputFormatter(new JsonSerializerSettings(), ArrayPool<char>.Shared),
ArrayPool<char>.Shared),
new DiagnosticListener("Microsoft.AspNetCore.Mvc"),
new HtmlTestEncoder(),
modelExpressionProvider);
var activator = CreateActivator();
var viewData = new ViewDataDictionary<MyModel>(MetadataProvider, new ModelStateDictionary());
var viewContext = CreateViewContext(viewData);
var instance = new HasIncorrectViewDataPropertyType();
var collection = new ServiceCollection();
collection.AddSingleton(new ExpressionTextCache());
var httpContext = new DefaultHttpContext
{
RequestServices = collection.BuildServiceProvider(),
};
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var viewContext = new ViewContext(
actionContext,
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()),
Mock.Of<ITempDataDictionary>(),
TextWriter.Null,
new HtmlHelperOptions());
// Act & Assert
Assert.Throws<InvalidCastException>(() => activator.Activate(instance, viewContext));
}
[Fact]
public void Activate_CanGetUrlHelperFromDependencyInjection()
private RazorPageActivator CreateActivator()
{
// Arrange
var modelMetadataProvider = new EmptyModelMetadataProvider();
var modelExpressionProvider = new ModelExpressionProvider(modelMetadataProvider, new ExpressionTextCache());
var activator = new RazorPageActivator(
modelMetadataProvider,
new UrlHelperFactory(),
new JsonHelper(
new JsonOutputFormatter(new JsonSerializerSettings(), ArrayPool<char>.Shared),
ArrayPool<char>.Shared),
new DiagnosticListener("Microsoft.AspNetCore.Mvc"),
new HtmlTestEncoder(),
modelExpressionProvider);
return new RazorPageActivator(MetadataProvider, UrlHelperFactory, JsonHelper, DiagnosticSource, HtmlEncoder, ModelExpressionProvider);
}
var instance = new HasUnusualIUrlHelperProperty();
private ViewContext CreateViewContext(ViewDataDictionary viewData = null)
{
if (viewData == null)
{
viewData = new ViewDataDictionary(MetadataProvider, new ModelStateDictionary());
}
// IUrlHelperFactory should not be used. But set it up to match a real configuration.
var collection = new ServiceCollection();
collection
var myService = new MyService();
var htmlHelper = Mock.Of<IHtmlHelper<object>>();
var serviceProvider = new ServiceCollection()
.AddSingleton(myService)
.AddSingleton(htmlHelper)
.AddSingleton(new ExpressionTextCache())
.AddSingleton<IUrlHelperWrapper, UrlHelperWrapper>();
.BuildServiceProvider();
var httpContext = new DefaultHttpContext
{
RequestServices = collection.BuildServiceProvider(),
RequestServices = serviceProvider
};
var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor());
var viewContext = new ViewContext(
return new ViewContext(
actionContext,
Mock.Of<IView>(),
new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()),
viewData,
Mock.Of<ITempDataDictionary>(),
TextWriter.Null,
new HtmlHelperOptions());
// Act
activator.Activate(instance, viewContext);
// Assert
Assert.NotNull(instance.UrlHelper);
}
private abstract class TestPageBase<TModel> : RazorPage<TModel>
@ -380,11 +225,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
}
}
private abstract class DoesNotDeriveFromRazorPageOfTBase<TModel> : RazorPage
private abstract class NoModelPropertyBase<TModel> : RazorPage
{
[RazorInject]
public ViewDataDictionary ViewData { get; set; }
}
private class DoesNotDeriveFromRazorPageOfT : DoesNotDeriveFromRazorPageOfTBase<MyModel>
private class NoModelPropertyPage : NoModelPropertyBase<MyModel>
{
public override Task ExecuteAsync()
{
@ -392,16 +239,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
}
}
private class DoesNotDeriveFromRazorPageOfTButHasModelProperty : DoesNotDeriveFromRazorPageOfTBase<MyModel>
{
public string Model { get; set; }
public override Task ExecuteAsync()
{
throw new NotImplementedException();
}
}
private class HasIncorrectViewDataPropertyType : RazorPage<MyModel>
{
[RazorInject]
@ -413,57 +250,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor
}
}
private class HasUnusualIUrlHelperProperty : RazorPage<MyModel>
{
[RazorInject]
public IUrlHelperWrapper UrlHelper { get; set; }
public override Task ExecuteAsync()
{
throw new NotImplementedException();
}
}
private class UrlHelperWrapper : IUrlHelperWrapper
{
public ActionContext ActionContext
{
get
{
throw new NotImplementedException();
}
}
public string Action(UrlActionContext actionContext)
{
throw new NotImplementedException();
}
public string Content(string contentPath)
{
throw new NotImplementedException();
}
public bool IsLocalUrl(string url)
{
throw new NotImplementedException();
}
public string Link(string routeName, object values)
{
throw new NotImplementedException();
}
public string RouteUrl(UrlRouteContext routeContext)
{
throw new NotImplementedException();
}
}
private interface IUrlHelperWrapper : IUrlHelper
{
}
private class MyService : IViewContextAware
{
public ViewContext ViewContext { get; private set; }

View File

@ -260,7 +260,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
Assert.NotNull(testPage.ModelExpressionProviderWithInject);
}
private static DefaultPageFactory CreatePageFactory(
private static DefaultPageFactoryProvider CreatePageFactory(
IPageActivatorProvider pageActivator = null,
IModelMetadataProvider provider = null,
IUrlHelperFactory urlHelperFactory = null,
@ -269,7 +269,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
HtmlEncoder htmlEncoder = null,
IModelExpressionProvider modelExpressionProvider = null)
{
return new DefaultPageFactory(
return new DefaultPageFactoryProvider(
pageActivator ?? CreateActivator(),
provider ?? Mock.Of<IModelMetadataProvider>(),
urlHelperFactory ?? Mock.Of<IUrlHelperFactory>(),