Make Partial on PageBase and PageModel work correctly (#13013)

* Make Partial on PageBase and PageModel work correctly

Fixes https://github.com/aspnet/AspNetCore/issues/10438
This commit is contained in:
Pranav K 2019-08-13 12:49:10 -07:00 committed by GitHub
parent c80f7d1dd9
commit 9bd027aa99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 212 additions and 148 deletions

View File

@ -368,6 +368,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
{ {
protected PageBase() { } protected PageBase() { }
public Microsoft.AspNetCore.Http.HttpContext HttpContext { get { throw null; } } public Microsoft.AspNetCore.Http.HttpContext HttpContext { get { throw null; } }
public Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider MetadataProvider { get { throw null; } set { } }
public Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary ModelState { get { throw null; } } public Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary ModelState { get { throw null; } }
public Microsoft.AspNetCore.Mvc.RazorPages.PageContext PageContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public Microsoft.AspNetCore.Mvc.RazorPages.PageContext PageContext { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } }
public Microsoft.AspNetCore.Http.HttpRequest Request { get { throw null; } } public Microsoft.AspNetCore.Http.HttpRequest Request { get { throw null; } }
@ -500,6 +501,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
{ {
protected PageModel() { } protected PageModel() { }
public Microsoft.AspNetCore.Http.HttpContext HttpContext { get { throw null; } } public Microsoft.AspNetCore.Http.HttpContext HttpContext { get { throw null; } }
public Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider MetadataProvider { get { throw null; } set { } }
public Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary ModelState { get { throw null; } } public Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary ModelState { get { throw null; } }
[Microsoft.AspNetCore.Mvc.RazorPages.PageContextAttribute] [Microsoft.AspNetCore.Mvc.RazorPages.PageContextAttribute]
public Microsoft.AspNetCore.Mvc.RazorPages.PageContext PageContext { get { throw null; } set { } } public Microsoft.AspNetCore.Mvc.RazorPages.PageContext PageContext { get { throw null; } set { } }

View File

@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
@ -61,6 +62,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </summary> /// </summary>
public ModelStateDictionary ModelState => PageContext?.ModelState; public ModelStateDictionary ModelState => PageContext?.ModelState;
/// <summary>
/// Gets or sets the <see cref="IModelMetadataProvider"/>.
/// </summary>
public IModelMetadataProvider MetadataProvider
{
get
{
_metadataProvider ??= HttpContext?.RequestServices?.GetRequiredService<IModelMetadataProvider>();
return _metadataProvider;
}
set => _metadataProvider = value ?? throw new ArgumentNullException(nameof(value));
}
private IObjectModelValidator ObjectValidator private IObjectModelValidator ObjectValidator
{ {
get get
@ -74,19 +88,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
} }
} }
private IModelMetadataProvider MetadataProvider
{
get
{
if (_metadataProvider == null)
{
_metadataProvider = HttpContext?.RequestServices?.GetRequiredService<IModelMetadataProvider>();
}
return _metadataProvider;
}
}
private IModelBinderFactory ModelBinderFactory private IModelBinderFactory ModelBinderFactory
{ {
get get
@ -1206,12 +1207,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// <returns>The created <see cref="PartialViewResult"/> object for the response.</returns> /// <returns>The created <see cref="PartialViewResult"/> object for the response.</returns>
public virtual PartialViewResult Partial(string viewName, object model) public virtual PartialViewResult Partial(string viewName, object model)
{ {
ViewContext.ViewData.Model = model; // ViewContext.ViewData is an instance of ViewDataDictionary<MyPageModel>, but we need an instance
// of ViewDataDictionary<MyPartialViewModel>.
var viewData = new ViewDataDictionary(MetadataProvider, ViewContext.ViewData.ModelState)
{
Model = model,
};
return new PartialViewResult return new PartialViewResult
{ {
ViewName = viewName, ViewName = viewName,
ViewData = ViewContext.ViewData ViewData = viewData
}; };
} }

View File

@ -139,6 +139,19 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
} }
} }
/// <summary>
/// Gets or sets the <see cref="IModelMetadataProvider"/>.
/// </summary>
public IModelMetadataProvider MetadataProvider
{
get
{
_metadataProvider ??= HttpContext?.RequestServices?.GetRequiredService<IModelMetadataProvider>();
return _metadataProvider;
}
set => _metadataProvider = value ?? throw new ArgumentNullException(nameof(value));
}
/// <summary> /// <summary>
/// Gets the <see cref="ViewDataDictionary"/>. /// Gets the <see cref="ViewDataDictionary"/>.
/// </summary> /// </summary>
@ -157,19 +170,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
} }
} }
private IModelMetadataProvider MetadataProvider
{
get
{
if (_metadataProvider == null)
{
_metadataProvider = HttpContext?.RequestServices?.GetRequiredService<IModelMetadataProvider>();
}
return _metadataProvider;
}
}
private IModelBinderFactory ModelBinderFactory private IModelBinderFactory ModelBinderFactory
{ {
get get
@ -1631,12 +1631,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// <returns>The created <see cref="PartialViewResult"/> object for the response.</returns> /// <returns>The created <see cref="PartialViewResult"/> object for the response.</returns>
public virtual PartialViewResult Partial(string viewName, object model) public virtual PartialViewResult Partial(string viewName, object model)
{ {
ViewData.Model = model; // PageModel.ViewData is an instance of ViewDataDictionary<MyPageModel>, but we need an instance
// of ViewDataDictionary<MyPartialViewModel>.
var viewData = new ViewDataDictionary(MetadataProvider, ViewData.ModelState)
{
Model = model,
};
return new PartialViewResult return new PartialViewResult
{ {
ViewName = viewName, ViewName = viewName,
ViewData = ViewData ViewData = viewData
}; };
} }

View File

@ -1898,13 +1898,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
public void PartialView_WithName() public void PartialView_WithName()
{ {
// Arrange // Arrange
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary());
var pageModel = new TestPageModel var pageModel = new TestPageModel
{ {
PageContext = new PageContext PageContext = new PageContext
{ {
ViewData = viewData ViewData = viewData
} },
MetadataProvider = modelMetadataProvider,
}; };
// Act // Act
@ -1913,20 +1915,22 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
// Assert // Assert
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("LoginStatus", result.ViewName); Assert.Equal("LoginStatus", result.ViewName);
Assert.Same(viewData, result.ViewData); Assert.Null(result.Model);
} }
[Fact] [Fact]
public void PartialView_WithNameAndModel() public void PartialView_WithNameAndModel()
{ {
// Arrange // Arrange
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary());
var pageModel = new TestPageModel var pageModel = new TestPageModel
{ {
PageContext = new PageContext PageContext = new PageContext
{ {
ViewData = viewData ViewData = viewData
} },
MetadataProvider = modelMetadataProvider,
}; };
var model = new { Username = "Admin" }; var model = new { Username = "Admin" };
@ -1937,7 +1941,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("LoginStatus", result.ViewName); Assert.Equal("LoginStatus", result.ViewName);
Assert.Equal(model, result.Model); Assert.Equal(model, result.Model);
Assert.Same(viewData, result.ViewData);
} }
[Fact] [Fact]

View File

@ -1700,14 +1700,17 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
public void PartialView_WithName() public void PartialView_WithName()
{ {
// Arrange // Arrange
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary());
var pageModel = new TestPage var pageModel = new TestPage
{ {
ViewContext = new ViewContext ViewContext = new ViewContext
{ {
ViewData = viewData ViewData = viewData
} },
MetadataProvider = modelMetadataProvider,
}; };
viewData.Model = pageModel;
// Act // Act
var result = pageModel.Partial("LoginStatus"); var result = pageModel.Partial("LoginStatus");
@ -1715,21 +1718,24 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
// Assert // Assert
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("LoginStatus", result.ViewName); Assert.Equal("LoginStatus", result.ViewName);
Assert.Same(viewData, result.ViewData); Assert.Null(result.Model);
} }
[Fact] [Fact]
public void PartialView_WithNameAndModel() public void PartialView_WithNameAndModel()
{ {
// Arrange // Arrange
var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewData = new ViewDataDictionary(modelMetadataProvider, new ModelStateDictionary());
var pageModel = new TestPage var pageModel = new TestPage
{ {
ViewContext = new ViewContext ViewContext = new ViewContext
{ {
ViewData = viewData ViewData = viewData
} },
MetadataProvider = modelMetadataProvider,
}; };
viewData.Model = pageModel;
var model = new { Username = "Admin" }; var model = new { Username = "Admin" };
// Act // Act
@ -1739,7 +1745,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
Assert.NotNull(result); Assert.NotNull(result);
Assert.Equal("LoginStatus", result.ViewName); Assert.Equal("LoginStatus", result.ViewName);
Assert.Equal(model, result.Model); Assert.Equal(model, result.Model);
Assert.Same(viewData, result.ViewData);
} }
[Fact] [Fact]

View File

@ -1,5 +1,5 @@
@page @page
@model TestModel @model PagesHome
@{ @{
ViewData["Title"] = "Hello from pages"; ViewData["Title"] = "Hello from pages";

View File

@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
namespace MvcSandbox namespace MvcSandbox
{ {
public class TestModel : PageModel public class PagesHome : PageModel
{ {
public string Name { get; private set; } = "World"; public string Name { get; private set; } = "World";

View File

@ -144,23 +144,43 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
} }
[Fact] [Fact]
public async Task Page_Handler_ReturnPartialWithoutModel() public async Task PageWithoutModel_ReturnPartial()
{ {
// Act // Act
var document = await Client.GetHtmlDocumentAsync("RenderPartialWithoutModel"); using var document = await Client.GetHtmlDocumentAsync("PageWithoutModelRenderPartial");
var element = document.RequiredQuerySelector("#content"); var element = document.RequiredQuerySelector("#content");
Assert.Equal("Welcome, Guest", element.TextContent); Assert.Equal("Hello from Razor Page", element.TextContent);
} }
[Fact] [Fact]
public async Task Page_Handler_ReturnPartialWithModel() public async Task PageWithModel_Works()
{ {
// Act // Act
var document = await Client.GetHtmlDocumentAsync("RenderPartialWithModel"); using var document = await Client.GetHtmlDocumentAsync("RenderPartial");
var element = document.RequiredQuerySelector("#content"); var element = document.RequiredQuerySelector("#content");
Assert.Equal("Welcome, Admin", element.TextContent); Assert.Equal("Hello from RenderPartialModel", element.TextContent);
}
[Fact]
public async Task PageWithModel_PartialUsingPageModelWorks()
{
// Act
using var document = await Client.GetHtmlDocumentAsync("RenderPartial/UsePageModelAsPartialModel");
var element = document.RequiredQuerySelector("#content");
Assert.Equal("Hello from RenderPartialWithModel", element.TextContent);
}
[Fact]
public async Task PageWithModel_PartialWithNoModel()
{
// Act
using var document = await Client.GetHtmlDocumentAsync("RenderPartial/NoPartialModel");
var element = document.RequiredQuerySelector("#content");
Assert.Equal("Hello default", element.TextContent);
} }
[Fact] [Fact]

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.
namespace RazorPagesWebSite.Models
{
public class RenderPartialModel
{
public string Value { get; set; }
}
}

View File

@ -0,0 +1,6 @@
@page
@using RazorPagesWebSite.Models
@functions {
public IActionResult OnGet() => Partial("_RenderPartial", new RenderPartialModel { Value = "Hello from Razor Page" });
}

View File

@ -0,0 +1,7 @@
@page "{handler?}"
@model RazorPagesWebSite.RenderPartialWithModel
@{
throw new Exception("This should not be called");
}

View File

@ -0,0 +1,20 @@
// 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;
using Microsoft.AspNetCore.Mvc.RazorPages;
using RazorPagesWebSite.Models;
namespace RazorPagesWebSite
{
public class RenderPartialWithModel : PageModel
{
public string Text { get; set; } = $"Hello from {nameof(RenderPartialWithModel)}";
public IActionResult OnGet() => Partial("_RenderPartial", new RenderPartialModel { Value = $"Hello from {nameof(RenderPartialModel)}" });
public IActionResult OnGetUsePageModelAsPartialModel() => Partial("_RenderPartialPageModel", this);
public IActionResult OnGetNoPartialModel() => Partial("_RenderPartial");
}
}

View File

@ -1,15 +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 Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite
{
public class RenderPartialWithModel : PageModel
{
public IActionResult OnGet() => Partial("_PartialWithModel", this);
public string Username => "Admin";
}
}

View File

@ -1,4 +0,0 @@
@page
@model RazorPagesWebSite.RenderPartialWithModel
<p>The partial will be loaded here ...</p>

View File

@ -1,5 +0,0 @@
@page
@functions {
public IActionResult OnGet() => Partial("_PartialWithoutModel");
}

View File

@ -1 +0,0 @@
<span id="content">Welcome, Guest</span>

View File

@ -0,0 +1,4 @@
@using RazorPagesWebSite.Models
@model RenderPartialModel
<span id="content">@(Model?.Value ?? "Hello default")</span>

View File

@ -1,3 +1,3 @@
@model RazorPagesWebSite.RenderPartialWithModel @model RazorPagesWebSite.RenderPartialWithModel
<span id="content">Welcome, @Model.Username</span> <span id="content">@Model.Text</span>