diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs
index 4c220f1799..d8613d1e9d 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs
@@ -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
}
///
- /// Gets the from the .
+ /// An representing the current request execution.
///
- /// Returns null if is null.
- public ITempDataDictionary TempData => ViewContext?.TempData;
+ public HttpContext Context => ViewContext?.HttpContext;
///
/// In a Razor layout page, renders the portion of a content page that is not within a named section.
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs
index a58edccf8f..ea56d8302e 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs
@@ -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; }
- ///
- /// An representing the current request execution.
- ///
- public HttpContext Context => ViewContext?.HttpContext;
-
///
/// Gets the that the page is writing output to.
///
@@ -101,7 +97,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor
///
/// Gets the of the current logged in user.
///
- public virtual ClaimsPrincipal User => Context?.User;
+ public virtual ClaimsPrincipal User => ViewContext?.HttpContext?.User;
+
+ ///
+ /// Gets the from the .
+ ///
+ /// Returns null if is null.
+ public ITempDataDictionary TempData => ViewContext?.TempData;
protected Stack TagHelperScopes { get; } = new Stack();
@@ -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();
_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 flushes the headers.
public virtual HtmlString SetAntiforgeryCookieAndHeader()
{
- var antiforgery = Context.RequestServices.GetRequiredService();
- antiforgery.SetCookieTokenAndHeader(Context);
+ var antiforgery = ViewContext?.HttpContext.RequestServices.GetRequiredService();
+ antiforgery.SetCookieTokenAndHeader(ViewContext?.HttpContext);
return HtmlString.Empty;
}
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs
index 0007c2d3e5..32e73ad0b3 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs
@@ -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
///
public PageContext PageContext { get; set; }
+ ///
+ public override ViewContext ViewContext
+ {
+ get => PageContext;
+ set
+ {
+ PageContext = (PageContext)value;
+ }
+ }
+
+ ///
+ /// Gets the .
+ ///
+ public HttpContext HttpContext => PageContext?.HttpContext;
+
+ ///
+ /// Gets the .
+ ///
+ public HttpRequest Request => HttpContext?.Request;
+
+ ///
+ /// Gets the .
+ ///
+ public HttpResponse Response => HttpContext?.Response;
+
///
/// Gets or sets the .
///
@@ -54,12 +80,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
///
public ModelStateDictionary ModelState => PageContext?.ModelState;
- ///
- /// Gets the from the .
- ///
- /// Returns null if is null.
- public ITempDataDictionary TempData => PageContext?.TempData;
-
///
public override void EnsureRenderedBodyOrSections()
{
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs
index b59bc66acd..292b1e4d0a 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs
@@ -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;
+ ///
+ /// Gets or sets the .
+ ///
public PageArgumentBinder Binder
{
get
{
if (_binder == null)
{
- _binder = PageContext.HttpContext.RequestServices.GetRequiredService();
+ _binder = HttpContext?.RequestServices?.GetRequiredService();
}
return _binder;
@@ -37,11 +44,66 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
}
}
- public Page Page => PageContext.Page;
+ ///
+ /// Gets or sets the .
+ ///
+ public IUrlHelper Url
+ {
+ get
+ {
+ if (_urlHelper == null)
+ {
+ var factory = HttpContext?.RequestServices?.GetRequiredService();
+ _urlHelper = factory?.GetUrlHelper(PageContext);
+ }
+ return _urlHelper;
+ }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _urlHelper = value;
+ }
+ }
+
+ ///
+ /// Gets the instance this model belongs to.
+ ///
+ public Page Page => PageContext?.Page;
+
+ ///
+ /// Gets the .
+ ///
[PageContext]
public PageContext PageContext { get; set; }
+ ///
+ /// Gets the .
+ ///
+ public ViewContext ViewContext => PageContext;
+
+ ///
+ /// Gets the .
+ ///
+ public HttpContext HttpContext => PageContext?.HttpContext;
+
+ ///
+ /// Gets the .
+ ///
+ public HttpRequest Request => HttpContext?.Request;
+
+ ///
+ /// Gets the .
+ ///
+ public HttpResponse Response => HttpContext?.Response;
+
+ ///
+ /// Gets the .
+ ///
public ModelStateDictionary ModelState => PageContext.ModelState;
///
@@ -50,34 +112,75 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
/// Returns null if is null.
public ITempDataDictionary TempData => PageContext?.TempData;
+ ///
+ /// Gets the .
+ ///
public ViewDataDictionary ViewData => PageContext?.ViewData;
- protected Task BindAsync(string name)
+ ///
+ /// Binds the model with the specified .
+ ///
+ /// The model type.
+ /// The model name.
+ /// A that on completion returns the bound model.
+ protected internal Task BindAsync(string name)
{
- return Binder.BindModelAsync(PageContext, name);
+ return Binder.BindModelAsync(PageContext, name);
}
- protected Task BindAsync(T @default, string name)
+ ///
+ /// Binds the model with the specified .
+ ///
+ /// The model type.
+ /// The model name.
+ /// The default model value.
+ /// A that on completion returns the bound model.
+ protected internal Task BindAsync(TModel @default, string name)
{
- return Binder.BindModelAsync(PageContext, @default, name);
+ return Binder.BindModelAsync(PageContext, @default, name);
}
- protected Task TryUpdateModelAsync(T value)
+ ///
+ /// Updates the specified instance using values from the controller's current
+ /// .
+ ///
+ /// The type of the model object.
+ /// The model instance to update.
+ /// A that on completion returns true if the update is successful.
+ protected internal Task TryUpdateModelAsync(TModel model)
{
- return Binder.TryUpdateModelAsync(PageContext, value);
+ return Binder.TryUpdateModelAsync(PageContext, model);
}
- protected Task TryUpdateModelAsync(T value, string name)
+ ///
+ /// Updates the specified instance using values from the controller's current
+ /// .
+ ///
+ /// The type of the model object.
+ /// The model instance to update.
+ /// The model name.
+ /// A that on completion returns true if the update is successful.
+ protected internal Task TryUpdateModelAsync(TModel model, string name)
{
- return Binder.TryUpdateModelAsync(PageContext, value, name);
+ return Binder.TryUpdateModelAsync(PageContext, model, name);
}
- protected IActionResult Redirect(string url)
+ ///
+ /// Creates a object that redirects ()
+ /// to the specified .
+ ///
+ /// The URL to redirect to.
+ /// The created for the response.
+ protected internal RedirectResult Redirect(string url)
{
return new RedirectResult(url);
}
- protected IActionResult View()
+ ///
+ /// Creates a object that renders the page.
+ ///
+ /// The .
+ protected internal PageViewResult View()
{
return new PageViewResult(Page);
}
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
index 39675d6718..1046b880b7 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
@@ -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);
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs
new file mode 100644
index 0000000000..2307219ef3
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs
@@ -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();
+ 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();
+ var urlHelperFactory = new Mock();
+ urlHelperFactory.Setup(f => f.GetUrlHelper(It.IsAny()))
+ .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(binder)
+ .BuildServiceProvider();
+ var pageContext = new PageContext
+ {
+ HttpContext = httpContext,
+ };
+ var pageModel = new TestPageModel
+ {
+ PageContext = pageContext,
+ };
+
+ // Act
+ var result = await pageModel.BindAsync("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(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(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 BindAsync(PageContext context, object value, string name, Type type)
+ {
+ return Task.FromResult(ModelBindingResult.Success(Guid.NewGuid()));
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs
new file mode 100644
index 0000000000..1a8fde83b3
--- /dev/null
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs
@@ -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();
+ 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();
+ }
+ }
+ }
+}
diff --git a/test/WebSites/RazorPagesWebSite/InjectedPageProperties.cshtml b/test/WebSites/RazorPagesWebSite/InjectedPageProperties.cshtml
new file mode 100644
index 0000000000..ed07662bce
--- /dev/null
+++ b/test/WebSites/RazorPagesWebSite/InjectedPageProperties.cshtml
@@ -0,0 +1,5 @@
+@page
+
+@Url.GetType()
+@Html.GetType()
+@ViewData.GetType()