Add properties to Page and PageModel

Fixes #6008
This commit is contained in:
Pranav K 2017-03-27 18:46:10 -07:00
parent 9c5b33dd8a
commit b22326323a
8 changed files with 397 additions and 33 deletions

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
@ -30,10 +31,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor
}
/// <summary>
/// Gets the <see cref="ITempDataDictionary"/> from the <see cref="ViewContext"/>.
/// An <see cref="HttpContext"/> representing the current request execution.
/// </summary>
/// <remarks>Returns null if <see cref="ViewContext"/> is null.</remarks>
public ITempDataDictionary TempData => ViewContext?.TempData;
public HttpContext Context => ViewContext?.HttpContext;
/// <summary>
/// In a Razor layout page, renders the portion of a content page that is not within a named section.

View File

@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal;
using Microsoft.AspNetCore.Razor.Runtime.TagHelpers;
using Microsoft.AspNetCore.Razor.TagHelpers;
@ -35,15 +36,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor
private TagHelperAttributeInfo _tagHelperAttributeInfo;
private IUrlHelper _urlHelper;
public ViewContext ViewContext { get; set; }
public virtual ViewContext ViewContext { get; set; }
public string Layout { get; set; }
/// <summary>
/// An <see cref="HttpContext"/> representing the current request execution.
/// </summary>
public HttpContext Context => ViewContext?.HttpContext;
/// <summary>
/// Gets the <see cref="TextWriter"/> that the page is writing output to.
/// </summary>
@ -101,7 +97,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
/// <summary>
/// Gets the <see cref="ClaimsPrincipal"/> of the current logged in user.
/// </summary>
public virtual ClaimsPrincipal User => Context?.User;
public virtual ClaimsPrincipal User => ViewContext?.HttpContext?.User;
/// <summary>
/// Gets the <see cref="ITempDataDictionary"/> from the <see cref="ViewContext"/>.
/// </summary>
/// <remarks>Returns null if <see cref="ViewContext"/> is null.</remarks>
public ITempDataDictionary TempData => ViewContext?.TempData;
protected Stack<TagHelperScopeInfo> TagHelperScopes { get; } = new Stack<TagHelperScopeInfo>();
@ -282,7 +284,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
if (_urlHelper == null)
{
var services = Context.RequestServices;
var services = ViewContext?.HttpContext.RequestServices;
var factory = services.GetRequiredService<IUrlHelperFactory>();
_urlHelper = factory.GetUrlHelper(ViewContext);
}
@ -707,7 +709,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
}
await Output.FlushAsync();
await Context.Response.Body.FlushAsync();
await ViewContext?.HttpContext.Response.Body.FlushAsync();
return HtmlString.Empty;
}
@ -719,8 +721,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor
/// before <see cref="RazorPageBase.FlushAsync"/> flushes the headers. </remarks>
public virtual HtmlString SetAntiforgeryCookieAndHeader()
{
var antiforgery = Context.RequestServices.GetRequiredService<IAntiforgery>();
antiforgery.SetCookieTokenAndHeader(Context);
var antiforgery = ViewContext?.HttpContext.RequestServices.GetRequiredService<IAntiforgery>();
antiforgery.SetCookieTokenAndHeader(ViewContext?.HttpContext);
return HtmlString.Empty;
}

View File

@ -3,10 +3,11 @@
using System;
using System.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.Extensions.DependencyInjection;
namespace Microsoft.AspNetCore.Mvc.RazorPages
@ -23,6 +24,31 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </summary>
public PageContext PageContext { get; set; }
/// <inheritdoc />
public override ViewContext ViewContext
{
get => PageContext;
set
{
PageContext = (PageContext)value;
}
}
/// <summary>
/// Gets the <see cref="Http.HttpContext"/>.
/// </summary>
public HttpContext HttpContext => PageContext?.HttpContext;
/// <summary>
/// Gets the <see cref="HttpRequest"/>.
/// </summary>
public HttpRequest Request => HttpContext?.Request;
/// <summary>
/// Gets the <see cref="HttpResponse"/>.
/// </summary>
public HttpResponse Response => HttpContext?.Response;
/// <summary>
/// Gets or sets the <see cref="PageArgumentBinder"/>.
/// </summary>
@ -54,12 +80,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// </summary>
public ModelStateDictionary ModelState => PageContext?.ModelState;
/// <summary>
/// Gets the <see cref="ITempDataDictionary"/> from the <see cref="PageContext"/>.
/// </summary>
/// <remarks>Returns null if <see cref="PageContext"/> is null.</remarks>
public ITempDataDictionary TempData => PageContext?.TempData;
/// <inheritdoc />
public override void EnsureRenderedBodyOrSections()
{

View File

@ -3,8 +3,11 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.DependencyInjection;
@ -13,14 +16,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
public abstract class PageModel
{
private PageArgumentBinder _binder;
private IUrlHelper _urlHelper;
/// <summary>
/// Gets or sets the <see cref="PageArgumentBinder"/>.
/// </summary>
public PageArgumentBinder Binder
{
get
{
if (_binder == null)
{
_binder = PageContext.HttpContext.RequestServices.GetRequiredService<PageArgumentBinder>();
_binder = HttpContext?.RequestServices?.GetRequiredService<PageArgumentBinder>();
}
return _binder;
@ -37,11 +44,66 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
}
}
public Page Page => PageContext.Page;
/// <summary>
/// Gets or sets the <see cref="IUrlHelper"/>.
/// </summary>
public IUrlHelper Url
{
get
{
if (_urlHelper == null)
{
var factory = HttpContext?.RequestServices?.GetRequiredService<IUrlHelperFactory>();
_urlHelper = factory?.GetUrlHelper(PageContext);
}
return _urlHelper;
}
set
{
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
_urlHelper = value;
}
}
/// <summary>
/// Gets the <see cref="RazorPages.Page"/> instance this model belongs to.
/// </summary>
public Page Page => PageContext?.Page;
/// <summary>
/// Gets the <see cref="RazorPages.PageContext"/>.
/// </summary>
[PageContext]
public PageContext PageContext { get; set; }
/// <summary>
/// Gets the <see cref="ViewContext"/>.
/// </summary>
public ViewContext ViewContext => PageContext;
/// <summary>
/// Gets the <see cref="Http.HttpContext"/>.
/// </summary>
public HttpContext HttpContext => PageContext?.HttpContext;
/// <summary>
/// Gets the <see cref="HttpRequest"/>.
/// </summary>
public HttpRequest Request => HttpContext?.Request;
/// <summary>
/// Gets the <see cref="HttpResponse"/>.
/// </summary>
public HttpResponse Response => HttpContext?.Response;
/// <summary>
/// Gets the <see cref="ModelStateDictionary"/>.
/// </summary>
public ModelStateDictionary ModelState => PageContext.ModelState;
/// <summary>
@ -50,34 +112,75 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// <remarks>Returns null if <see cref="PageContext"/> is null.</remarks>
public ITempDataDictionary TempData => PageContext?.TempData;
/// <summary>
/// Gets the <see cref="ViewDataDictionary"/>.
/// </summary>
public ViewDataDictionary ViewData => PageContext?.ViewData;
protected Task<T> BindAsync<T>(string name)
/// <summary>
/// Binds the model with the specified <paramref name="name"/>.
/// </summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="name">The model name.</param>
/// <returns>A <see cref="Task"/> that on completion returns the bound model.</returns>
protected internal Task<TModel> BindAsync<TModel>(string name)
{
return Binder.BindModelAsync<T>(PageContext, name);
return Binder.BindModelAsync<TModel>(PageContext, name);
}
protected Task<T> BindAsync<T>(T @default, string name)
/// <summary>
/// Binds the model with the specified <paramref name="name"/>.
/// </summary>
/// <typeparam name="TModel">The model type.</typeparam>
/// <param name="name">The model name.</param>
/// <param name="default">The default model value.</param>
/// <returns>A <see cref="Task"/> that on completion returns the bound model.</returns>
protected internal Task<TModel> BindAsync<TModel>(TModel @default, string name)
{
return Binder.BindModelAsync<T>(PageContext, @default, name);
return Binder.BindModelAsync(PageContext, @default, name);
}
protected Task<bool> TryUpdateModelAsync<T>(T value)
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the controller's current
/// <see cref="IValueProvider"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
protected internal Task<bool> TryUpdateModelAsync<TModel>(TModel model)
{
return Binder.TryUpdateModelAsync<T>(PageContext, value);
return Binder.TryUpdateModelAsync(PageContext, model);
}
protected Task<bool> TryUpdateModelAsync<T>(T value, string name)
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the controller's current
/// <see cref="IValueProvider"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
/// <param name="model">The model instance to update.</param>
/// <param name="name">The model name.</param>
/// <returns>A <see cref="Task"/> that on completion returns <c>true</c> if the update is successful.</returns>
protected internal Task<bool> TryUpdateModelAsync<TModel>(TModel model, string name)
{
return Binder.TryUpdateModelAsync<T>(PageContext, value, name);
return Binder.TryUpdateModelAsync(PageContext, model, name);
}
protected IActionResult Redirect(string url)
/// <summary>
/// Creates a <see cref="RedirectResult"/> object that redirects (<see cref="StatusCodes.Status302Found"/>)
/// to the specified <paramref name="url"/>.
/// </summary>
/// <param name="url">The URL to redirect to.</param>
/// <returns>The created <see cref="RedirectResult"/> for the response.</returns>
protected internal RedirectResult Redirect(string url)
{
return new RedirectResult(url);
}
protected IActionResult View()
/// <summary>
/// Creates a <see cref="PageViewResult"/> object that renders the page.
/// </summary>
/// <returns>The <see cref="PageViewResult"/>.</returns>
protected internal PageViewResult View()
{
return new PageViewResult(Page);
}

View File

@ -590,6 +590,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.DoesNotContain(validationError, content);
}
[Fact]
public async Task PagePropertiesAreInjected()
{
// Arrange
var expected =
@"Microsoft.AspNetCore.Mvc.Routing.UrlHelper
Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper`1[AspNetCore._InjectedPageProperties_cshtml]
Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore._InjectedPageProperties_cshtml]";
// Act
var response = await Client.GetStringAsync("InjectedPageProperties");
// Assert
Assert.Equal(expected, response.Trim());
}
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
{
var getResponse = await Client.GetAsync(request.RequestUri);

View File

@ -0,0 +1,165 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
public class PageModelTest
{
[Fact]
public void PageModelPropertiesArePopulatedFromContext()
{
// Arrange
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewDataDictionary = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = Mock.Of<ITempDataDictionary>();
var pageContext = new PageContext(actionContext, viewDataDictionary, tempData, new HtmlHelperOptions());
var page = new TestPage
{
PageContext = pageContext,
};
pageContext.Page = page;
var pageModel = new TestPageModel
{
PageContext = pageContext,
};
// Act & Assert
Assert.Same(page, pageModel.Page);
Assert.Same(pageContext, pageModel.PageContext);
Assert.Same(pageContext, pageModel.ViewContext);
Assert.Same(httpContext, pageModel.HttpContext);
Assert.Same(httpContext.Request, pageModel.Request);
Assert.Same(httpContext.Response, pageModel.Response);
Assert.Same(modelState, pageModel.ModelState);
Assert.Same(viewDataDictionary, pageModel.ViewData);
Assert.Same(tempData, pageModel.TempData);
}
[Fact]
public void UrlHelperIsSet()
{
// Arrange
var httpContext = new DefaultHttpContext();
var urlHelper = Mock.Of<IUrlHelper>();
var urlHelperFactory = new Mock<IUrlHelperFactory>();
urlHelperFactory.Setup(f => f.GetUrlHelper(It.IsAny<ActionContext>()))
.Returns(urlHelper);
httpContext.RequestServices = new ServiceCollection()
.AddSingleton(urlHelperFactory.Object)
.BuildServiceProvider();
var actionContext = new ActionContext
{
HttpContext = httpContext,
};
var pageContext = new PageContext
{
HttpContext = httpContext,
};
var pageModel = new TestPageModel
{
PageContext = pageContext,
};
// Act & Assert
Assert.Same(urlHelper, pageModel.Url);
}
[Fact]
public async Task BindModel_InvokesBindOnPageArgumentBinder()
{
// Arrange
var httpContext = new DefaultHttpContext();
var binder = new TestPageArgumentBinder();
httpContext.RequestServices = new ServiceCollection()
.AddSingleton<PageArgumentBinder>(binder)
.BuildServiceProvider();
var pageContext = new PageContext
{
HttpContext = httpContext,
};
var pageModel = new TestPageModel
{
PageContext = pageContext,
};
// Act
var result = await pageModel.BindAsync<Guid>("test-name");
// Assert
Assert.NotNull(result);
}
[Fact]
public void Redirect_ReturnsARedirectResult()
{
// Arrange
var pageModel = new TestPageModel();
// Act
var result = pageModel.Redirect("test-url");
// Assert
var redirectResult = Assert.IsType<RedirectResult>(result);
Assert.Equal("test-url", redirectResult.Url);
}
[Fact]
public void View_ReturnsPageViewResult()
{
// Arrange
var page = new TestPage();
var pageModel = new TestPageModel
{
PageContext = new PageContext
{
Page = page,
}
};
// Act
var result = pageModel.View();
// Assert
var pageResult = Assert.IsType<PageViewResult>(result);
Assert.Same(page, pageResult.Page);
}
private class TestPageModel : PageModel
{
}
private class TestPage : Page
{
public override Task ExecuteAsync()
{
throw new NotImplementedException();
}
}
private class TestPageArgumentBinder : PageArgumentBinder
{
protected override Task<ModelBindingResult> BindAsync(PageContext context, object value, string name, Type type)
{
return Task.FromResult(ModelBindingResult.Success(Guid.NewGuid()));
}
}
}
}

View File

@ -0,0 +1,53 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Mvc.Routing;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
public class PageTest
{
[Fact]
public void PagePropertiesArePopulatedFromContext()
{
// Arrange
var httpContext = new DefaultHttpContext();
var modelState = new ModelStateDictionary();
var actionContext = new ActionContext(httpContext, new RouteData(), new PageActionDescriptor(), modelState);
var modelMetadataProvider = new EmptyModelMetadataProvider();
var viewDataDictionary = new ViewDataDictionary(modelMetadataProvider, modelState);
var tempData = Mock.Of<ITempDataDictionary>();
var pageContext = new PageContext(actionContext, viewDataDictionary, tempData, new HtmlHelperOptions());
var page = new TestPage
{
PageContext = pageContext,
};
// Act & Assert
Assert.Same(pageContext, page.ViewContext);
Assert.Same(httpContext, page.HttpContext);
Assert.Same(httpContext.Request, page.Request);
Assert.Same(httpContext.Response, page.Response);
Assert.Same(modelState, page.ModelState);
Assert.Same(tempData, page.TempData);
}
private class TestPage : Page
{
public override Task ExecuteAsync()
{
throw new NotImplementedException();
}
}
}
}

View File

@ -0,0 +1,5 @@
@page
@Url.GetType()
@Html.GetType()
@ViewData.GetType()