Make @inherit + Razor Pages + _ViewImports work

Fixes #6769
This commit is contained in:
Pranav K 2017-09-12 17:37:45 -07:00
parent 35601f95b3
commit 037c1ec47d
13 changed files with 265 additions and 16 deletions

View File

@ -30,19 +30,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
_metadataProvider = metadataProvider;
// In the absence of a model on the current type, we'll attempt to use ViewDataDictionary<object> on the current type.
var viewDataDictionaryModelType = modelType ?? typeof(object);
if (modelType != null)
if (viewDataDictionaryModelType != null)
{
_viewDataDictionaryType = typeof(ViewDataDictionary<>).MakeGenericType(modelType);
_rootFactory = ViewDataDictionaryFactory.CreateFactory(modelType.GetTypeInfo());
_nestedFactory = ViewDataDictionaryFactory.CreateNestedFactory(modelType.GetTypeInfo());
_viewDataDictionaryType = typeof(ViewDataDictionary<>).MakeGenericType(viewDataDictionaryModelType);
_rootFactory = ViewDataDictionaryFactory.CreateFactory(viewDataDictionaryModelType.GetTypeInfo());
_nestedFactory = ViewDataDictionaryFactory.CreateNestedFactory(viewDataDictionaryModelType.GetTypeInfo());
}
_propertyActivators = PropertyActivator<ViewContext>.GetPropertiesToActivate(
pageType,
typeof(RazorInjectAttribute),
propertyInfo => CreateActivateInfo(propertyInfo, propertyValueAccessors),
includeNonPublic: true);
pageType,
typeof(RazorInjectAttribute),
propertyInfo => CreateActivateInfo(propertyInfo, propertyValueAccessors),
includeNonPublic: true);
}
public void Activate(object page, ViewContext context)
@ -64,7 +66,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal
}
}
private ViewDataDictionary CreateViewDataDictionary(ViewContext context)
// Internal for unit testing.
internal ViewDataDictionary CreateViewDataDictionary(ViewContext context)
{
// Create a ViewDataDictionary<TModel> if the ViewContext.ViewData is not set or the type of
// ViewContext.ViewData is an incompatible type.

View File

@ -42,11 +42,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
public virtual Func<PageContext, ViewContext, object> CreatePageFactory(CompiledPageActionDescriptor actionDescriptor)
{
if (!typeof(Page).GetTypeInfo().IsAssignableFrom(actionDescriptor.PageTypeInfo))
if (!typeof(PageBase).GetTypeInfo().IsAssignableFrom(actionDescriptor.PageTypeInfo))
{
throw new InvalidOperationException(Resources.FormatActivatedInstance_MustBeAnInstanceOf(
_pageActivator.GetType().FullName,
typeof(Page).FullName));
typeof(PageBase).FullName));
}
var activatorFactory = _pageActivator.CreateActivator(actionDescriptor);
@ -59,7 +59,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
return (pageContext, viewContext) =>
{
var page = (Page)activatorFactory(pageContext, viewContext);
var page = (PageBase)activatorFactory(pageContext, viewContext);
page.PageContext = pageContext;
page.Path = pageContext.ActionDescriptor.RelativePath;
page.ViewContext = viewContext;

View File

@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
private Dictionary<string, object> _arguments;
private HandlerMethodDescriptor _handler;
private Page _page;
private PageBase _page;
private object _pageModel;
private ViewContext _viewContext;
@ -124,7 +124,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
_htmlHelperOptions);
_viewContext.ExecutingFilePath = _pageContext.ActionDescriptor.RelativePath;
_page = (Page)CacheEntry.PageFactory(_pageContext, _viewContext);
_page = (PageBase)CacheEntry.PageFactory(_pageContext, _viewContext);
if (_actionDescriptor.ModelTypeInfo == _actionDescriptor.PageTypeInfo)
{
@ -271,7 +271,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
if (_page == null)
{
_page = (Page)CacheEntry.PageFactory(_pageContext, _viewContext);
_page = (PageBase)CacheEntry.PageFactory(_pageContext, _viewContext);
}
pageResult.Page = _page;

View File

@ -1189,6 +1189,26 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore._InjectedP
Assert.StartsWith(expected, responseContent.Trim());
}
[Fact]
public Task InheritsOnViewImportsWorksForPagesWithoutModel()
=> InheritsOnViewImportsWorks("Pages/CustomBaseType/Page");
[Fact]
public Task InheritsOnViewImportsWorksForPagesWithModel()
=> InheritsOnViewImportsWorks("Pages/CustomBaseType/PageWithModel");
private async Task InheritsOnViewImportsWorks(string path)
{
// Arrange
var expected = "<custom-base-type-layout>RazorPagesWebSite.CustomPageBase</custom-base-type-layout>";
// Act
var response = await Client.GetStringAsync(path);
// Assert
Assert.Equal(expected, response.Trim());
}
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
{
var getResponse = await Client.GetAsync(request.RequestUri);

View File

@ -0,0 +1,197 @@
// 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 Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.Razor.Internal
{
public class RazorPagePropertyActivatorTest
{
[Fact]
public void CreateViewDataDictionary_MakesNewInstance_WhenValueOnContextIsNull()
{
// Arrange
var activator = new RazorPagePropertyActivator(
typeof(TestPage),
typeof(TestModel),
new TestModelMetadataProvider(),
propertyValueAccessors: null);
var viewContext = new ViewContext();
// Act
var viewDataDictionary = activator.CreateViewDataDictionary(viewContext);
// Assert
Assert.NotNull(viewDataDictionary);
Assert.IsType<ViewDataDictionary<TestModel>>(viewDataDictionary);
}
[Fact]
public void CreateViewDataDictionary_MakesNewInstanceWithObjectModelType_WhenValueOnContextAndModelTypeAreNull()
{
// Arrange
var activator = new RazorPagePropertyActivator(
typeof(TestPage),
modelType: null,
metadataProvider: new TestModelMetadataProvider(),
propertyValueAccessors: null);
var viewContext = new ViewContext();
// Act
var viewDataDictionary = activator.CreateViewDataDictionary(viewContext);
// Assert
Assert.NotNull(viewDataDictionary);
Assert.IsType<ViewDataDictionary<object>>(viewDataDictionary);
}
[Fact]
public void CreateViewDataDictionary_CreatesNestedViewDataDictionary_WhenContextInstanceIsNonGeneric()
{
// Arrange
var modelMetadataProvider = new TestModelMetadataProvider();
var activator = new RazorPagePropertyActivator(
typeof(TestPage),
modelType: typeof(TestModel),
metadataProvider: modelMetadataProvider,
propertyValueAccessors: null);
var original = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary())
{
{ "test-key", "test-value" },
};
var viewContext = new ViewContext
{
ViewData = original,
};
// Act
var viewDataDictionary = activator.CreateViewDataDictionary(viewContext);
// Assert
Assert.NotNull(viewDataDictionary);
Assert.NotSame(original, viewDataDictionary);
Assert.IsType<ViewDataDictionary<TestModel>>(viewDataDictionary);
Assert.Equal("test-value", viewDataDictionary["test-key"]);
}
[Fact]
public void CreateViewDataDictionary_CreatesNestedViewDataDictionary_WhenModelTypeDoesNotMatch()
{
// Arrange
var modelMetadataProvider = new TestModelMetadataProvider();
var activator = new RazorPagePropertyActivator(
typeof(TestPage),
modelType: typeof(TestModel),
metadataProvider: modelMetadataProvider,
propertyValueAccessors: null);
var original = new ViewDataDictionary<object>(modelMetadataProvider, new ModelStateDictionary())
{
{ "test-key", "test-value" },
};
var viewContext = new ViewContext
{
ViewData = original,
};
// Act
var viewDataDictionary = activator.CreateViewDataDictionary(viewContext);
// Assert
Assert.NotNull(viewDataDictionary);
Assert.NotSame(original, viewDataDictionary);
Assert.IsType<ViewDataDictionary<TestModel>>(viewDataDictionary);
Assert.Equal("test-value", viewDataDictionary["test-key"]);
}
[Fact]
public void CreateViewDataDictionary_CreatesNestedViewDataDictionary_WhenNullModelTypeDoesNotMatch()
{
// Arrange
var modelMetadataProvider = new TestModelMetadataProvider();
var activator = new RazorPagePropertyActivator(
typeof(TestPage),
modelType: null,
metadataProvider: modelMetadataProvider,
propertyValueAccessors: null);
var original = new ViewDataDictionary<TestModel>(modelMetadataProvider, new ModelStateDictionary())
{
{ "test-key", "test-value" },
};
var viewContext = new ViewContext
{
ViewData = original,
};
// Act
var viewDataDictionary = activator.CreateViewDataDictionary(viewContext);
// Assert
Assert.NotNull(viewDataDictionary);
Assert.NotSame(original, viewDataDictionary);
Assert.IsType<ViewDataDictionary<object>>(viewDataDictionary);
Assert.Equal("test-value", viewDataDictionary["test-key"]);
}
[Fact]
public void CreateViewDataDictionary_ReturnsInstanceOnContext_IfModelTypeMatches()
{
// Arrange
var modelMetadataProvider = new TestModelMetadataProvider();
var activator = new RazorPagePropertyActivator(
typeof(TestPage),
modelType: typeof(TestModel),
metadataProvider: modelMetadataProvider,
propertyValueAccessors: null);
var original = new ViewDataDictionary<TestModel>(modelMetadataProvider, new ModelStateDictionary())
{
{ "test-key", "test-value" },
};
var viewContext = new ViewContext
{
ViewData = original,
};
// Act
var viewDataDictionary = activator.CreateViewDataDictionary(viewContext);
// Assert
Assert.NotNull(viewDataDictionary);
Assert.Same(original, viewDataDictionary);
}
[Fact]
public void CreateViewDataDictionary_ReturnsInstanceOnContext_WithNullModelType()
{
// Arrange
var modelMetadataProvider = new TestModelMetadataProvider();
var activator = new RazorPagePropertyActivator(
typeof(TestPage),
modelType: null,
metadataProvider: modelMetadataProvider,
propertyValueAccessors: null);
var original = new ViewDataDictionary<object>(modelMetadataProvider, new ModelStateDictionary());
var viewContext = new ViewContext
{
ViewData = original,
};
// Act
var viewDataDictionary = activator.CreateViewDataDictionary(viewContext);
// Assert
Assert.NotNull(viewDataDictionary);
Assert.Same(original, viewDataDictionary);
}
private class TestPage
{
}
private class TestModel
{
}
}
}

View File

@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => factoryProvider.CreatePageFactory(descriptor));
Assert.Equal(
$"Page created by '{pageActivator.GetType()}' must be an instance of '{typeof(Page)}'.",
$"Page created by '{pageActivator.GetType()}' must be an instance of '{typeof(PageBase)}'.",
ex.Message);
}

View File

@ -0,0 +1,10 @@
// 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 Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite
{
public abstract class CustomPageBase : PageBase
{
}
}

View File

@ -0,0 +1,2 @@
@page
@GetType().BaseType.FullName

View File

@ -0,0 +1,10 @@
// 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 Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite
{
public class PageWithModel : PageModel
{
}
}

View File

@ -0,0 +1,3 @@
@page
@model PageWithModel
@GetType().BaseType.FullName

View File

@ -0,0 +1,2 @@
@using RazorPagesWebSite
@inherits CustomPageBase

View File

@ -0,0 +1 @@
@{ Layout = "../Shared/_CustomBaseTypeLayout"; }

View File

@ -0,0 +1 @@
<custom-base-type-layout>@RenderBody()</custom-base-type-layout>