Adding IView.Path and ViewContext.ExecutingPagePath

Fixes #1940
This commit is contained in:
Pranav K 2015-02-10 23:09:08 -08:00
parent 29c346ebf8
commit a33e83f363
16 changed files with 239 additions and 5 deletions

View File

@ -5,8 +5,21 @@ using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc.Rendering namespace Microsoft.AspNet.Mvc.Rendering
{ {
/// <summary>
/// Specifies the contract for a view.
/// </summary>
public interface IView public interface IView
{ {
/// <summary>
/// Gets the path of the view as resolved by the <see cref="IViewEngine"/>.
/// </summary>
string Path { get; }
/// <summary>
/// Asynchronously renders the view using the specified <paramref name="context"/>.
/// </summary>
/// <param name="context">The <see cref="ViewContext"/>.</param>
/// <returns>A <see cref="Task"/> that on completion renders the view.</returns>
Task RenderAsync([NotNull] ViewContext context); Task RenderAsync([NotNull] ViewContext context);
} }
} }

View File

@ -6,6 +6,9 @@ using Microsoft.AspNet.Mvc.Rendering;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
/// <summary>
/// Context for view execution.
/// </summary>
public class ViewContext : ActionContext public class ViewContext : ActionContext
{ {
// We need a default FormContext if the user uses html <form> instead of an MvcForm // We need a default FormContext if the user uses html <form> instead of an MvcForm
@ -14,6 +17,13 @@ namespace Microsoft.AspNet.Mvc
private FormContext _formContext; private FormContext _formContext;
private DynamicViewData _viewBag; private DynamicViewData _viewBag;
/// <summary>
/// Initializes a new instance of <see cref="ViewContext"/>.
/// </summary>
/// <param name="actionContext">The <see cref="ActionContext"/>.</param>
/// <param name="view">The <see cref="IView"/> being rendered.</param>
/// <param name="viewData">The <see cref="ViewDataDictionary"/>.</param>
/// <param name="writer">The <see cref="TextWriter"/> to render output to.</param>
public ViewContext( public ViewContext(
[NotNull] ActionContext actionContext, [NotNull] ActionContext actionContext,
[NotNull] IView view, [NotNull] IView view,
@ -31,6 +41,13 @@ namespace Microsoft.AspNet.Mvc
ValidationMessageElement = "span"; ValidationMessageElement = "span";
} }
/// <summary>
/// Initializes a new instance of <see cref="ViewContext"/>.
/// </summary>
/// <param name="viewContext">The <see cref="ViewContext"/> to copy values from.</param>
/// <param name="view">The <see cref="IView"/> being rendered.</param>
/// <param name="viewData">The <see cref="ViewDataDictionary"/>.</param>
/// <param name="writer">The <see cref="TextWriter"/> to render output to.</param>
public ViewContext( public ViewContext(
[NotNull] ViewContext viewContext, [NotNull] ViewContext viewContext,
[NotNull] IView view, [NotNull] IView view,
@ -49,6 +66,10 @@ namespace Microsoft.AspNet.Mvc
Writer = writer; Writer = writer;
} }
/// <summary>
/// Gets or sets the <see cref="Mvc.FormContext"/> for the form element being rendered.
/// A default context is returned if no form is currently being rendered.
/// </summary>
public virtual FormContext FormContext public virtual FormContext FormContext
{ {
get get
@ -62,6 +83,9 @@ namespace Microsoft.AspNet.Mvc
} }
} }
/// <summary>
/// Gets or sets a value that indicates whether client-side validation is enabled.
/// </summary>
public bool ClientValidationEnabled { get; set; } public bool ClientValidationEnabled { get; set; }
/// <summary> /// <summary>
@ -84,6 +108,9 @@ namespace Microsoft.AspNet.Mvc
/// </summary> /// </summary>
public string ValidationMessageElement { get; set; } public string ValidationMessageElement { get; set; }
/// <summary>
/// Gets the dynamic view bag.
/// </summary>
public dynamic ViewBag public dynamic ViewBag
{ {
get get
@ -97,12 +124,30 @@ namespace Microsoft.AspNet.Mvc
} }
} }
/// <summary>
/// Gets or sets the <see cref="IView"/> currently being rendered, if any.
/// </summary>
public IView View { get; set; } public IView View { get; set; }
/// <summary>
/// Gets or sets the <see cref="ViewDataDictionary"/>.
/// </summary>
public ViewDataDictionary ViewData { get; set; } public ViewDataDictionary ViewData { get; set; }
/// <summary>
/// Gets or sets the <see cref="TextWriter"/> used to write the output.
/// </summary>
public TextWriter Writer { get; set; } public TextWriter Writer { get; set; }
/// <summary>
/// Gets or sets the path of the view file currently being rendered.
/// </summary>
/// <remarks>
/// The rendering of a view may involve one or more files (e.g. _ViewStart, Layouts etc).
/// This property contains the path of the file currently being rendered.
/// </remarks>
public string ExecutingFilePath { get; set; }
public FormContext GetFormContextForClientValidation() public FormContext GetFormContextForClientValidation()
{ {
return (ClientValidationEnabled) ? FormContext : null; return (ClientValidationEnabled) ? FormContext : null;

View File

@ -42,6 +42,12 @@ namespace Microsoft.AspNet.Mvc.Razor
IsPartial = isPartial; IsPartial = isPartial;
} }
/// <inheritdoc />
public string Path
{
get { return RazorPage.Path; }
}
/// <summary> /// <summary>
/// Gets <see cref="IRazorPage"/> instance that the views executes on. /// Gets <see cref="IRazorPage"/> instance that the views executes on.
/// </summary> /// </summary>
@ -93,7 +99,9 @@ namespace Microsoft.AspNet.Mvc.Razor
// The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers // The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers
// and ViewComponents to reference it. // and ViewComponents to reference it.
var oldWriter = context.Writer; var oldWriter = context.Writer;
var oldFilePath = context.ExecutingFilePath;
context.Writer = writer; context.Writer = writer;
context.ExecutingFilePath = page.Path;
try try
{ {
@ -109,6 +117,7 @@ namespace Microsoft.AspNet.Mvc.Razor
finally finally
{ {
context.Writer = oldWriter; context.Writer = oldWriter;
context.ExecutingFilePath = oldFilePath;
writer.Dispose(); writer.Dispose();
} }
} }
@ -131,12 +140,21 @@ namespace Microsoft.AspNet.Mvc.Razor
var viewStarts = _viewStartProvider.GetViewStartPages(RazorPage.Path); var viewStarts = _viewStartProvider.GetViewStartPages(RazorPage.Path);
string layout = null; string layout = null;
foreach (var viewStart in viewStarts) var oldFilePath = context.ExecutingFilePath;
try
{ {
// Copy the layout value from the previous view start (if any) to the current. foreach (var viewStart in viewStarts)
viewStart.Layout = layout; {
await RenderPageCoreAsync(viewStart, context); context.ExecutingFilePath = viewStart.Path;
layout = viewStart.Layout; // Copy the layout value from the previous view start (if any) to the current.
viewStart.Layout = layout;
await RenderPageCoreAsync(viewStart, context);
layout = viewStart.Layout;
}
}
finally
{
context.ExecutingFilePath = oldFilePath;
} }
// Copy over interesting properties from the ViewStart page to the entry page. // Copy over interesting properties from the ViewStart page to the entry page.

View File

@ -0,0 +1,21 @@
<Layout>
/Views/ViewWithPaths/_Layout.cshtml
/Views/ViewWithPaths/Index.cshtml
</Layout>
<ViewStart>
Views\ViewWithPaths\_ViewStart.cshtml
/Views/ViewWithPaths/Index.cshtml
</ViewStart>
<Index>
/Views/ViewWithPaths/Index.cshtml
/Views/ViewWithPaths/Index.cshtml
<component>
/Views/Shared/Components/ComponentForViewWithPaths/Default.cshtml
/Views/Shared/Components/ComponentForViewWithPaths/Default.cshtml
</component>
<Partial>
/Views/ViewWithPaths/_Partial.cshtml
/Views/ViewWithPaths/_Partial.cshtml
</Partial>
</Index>

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.TestHost; using Microsoft.AspNet.TestHost;
@ -395,5 +396,21 @@ Partial that does not specify Layout
// Assert // Assert
Assert.Equal(expected, body.Trim()); Assert.Equal(expected, body.Trim());
} }
[Fact]
public async Task RazorView_SetsViewPathAndExecutingPagePath()
{
// Arrange
var expected = await GetType().GetTypeInfo().Assembly
.ReadResourceAsStringAsync("compiler/resources/ViewEngineController.ViewWithPaths.txt");
var server = TestServer.Create(_provider, _app);
var client = server.CreateClient();
// Act
var body = await client.GetStringAsync("http://localhost/ViewWithPaths");
// Assert
Assert.Equal(expected, body.Trim());
}
} }
} }

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -83,6 +84,61 @@ namespace Microsoft.AspNet.Mvc.Razor
Assert.Same(expectedWriter, viewContext.Writer); Assert.Same(expectedWriter, viewContext.Writer);
} }
[Fact]
public async Task ViewContext_ExecutingPagePath_ReturnsPathOfRazorPageBeingExecuted()
{
// Arrange
var pagePath = "/my/view";
var paths = new List<string>();
var page = new TestableRazorPage(v =>
{
paths.Add(v.ViewContext.ExecutingFilePath);
Assert.Equal(pagePath, v.ViewContext.View.Path);
})
{
Path = pagePath
};
var viewStart = new TestableRazorPage(v =>
{
v.Layout = LayoutPath;
paths.Add(v.ViewContext.ExecutingFilePath);
Assert.Equal(pagePath, v.ViewContext.View.Path);
})
{
Path = "_ViewStart"
};
var layout = new TestableRazorPage(v =>
{
v.RenderBodyPublic();
paths.Add(v.ViewContext.ExecutingFilePath);
Assert.Equal(pagePath, v.ViewContext.View.Path);
})
{
Path = LayoutPath
};
var activator = Mock.Of<IRazorPageActivator>();
var viewEngine = new Mock<IRazorViewEngine>();
viewEngine.Setup(v => v.FindPage(It.IsAny<ActionContext>(), LayoutPath))
.Returns(new RazorPageResult(LayoutPath, layout));
var view = new RazorView(viewEngine.Object,
activator,
CreateViewStartProvider(viewStart),
page,
isPartial: false);
var viewContext = CreateViewContext(view);
var expectedWriter = viewContext.Writer;
// Act
await view.RenderAsync(viewContext);
// Assert
Assert.Equal(new[] { "_ViewStart", pagePath, LayoutPath }, paths);
}
[Fact] [Fact]
public async Task RenderAsync_AsPartial_ActivatesViews() public async Task RenderAsync_AsPartial_ActivatesViews()
{ {

View File

@ -9,6 +9,8 @@ namespace CompositeViewEngineWebSite
{ {
public class TestPartialView : IView public class TestPartialView : IView
{ {
public string Path { get; set; }
public async Task RenderAsync(ViewContext context) public async Task RenderAsync(ViewContext context)
{ {
await context.Writer.WriteLineAsync("world"); await context.Writer.WriteLineAsync("world");

View File

@ -9,6 +9,8 @@ namespace CompositeViewEngineWebSite
{ {
public class TestView : IView public class TestView : IView
{ {
public string Path { get; set; }
public async Task RenderAsync(ViewContext context) public async Task RenderAsync(ViewContext context)
{ {
await context.Writer.WriteLineAsync("Content from test view"); await context.Writer.WriteLineAsync("Content from test view");

View File

@ -68,6 +68,8 @@ namespace ModelBindingWebSite.Controllers
private sealed class TestView : IView private sealed class TestView : IView
{ {
public string Path { get; set; }
public Task RenderAsync(ViewContext context) public Task RenderAsync(ViewContext context)
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace MvcSample.Web.Components
{
[ViewComponent(Name = "ComponentForViewWithPaths")]
public class ComponentForViewWithPaths : ViewComponent
{
public IViewComponentResult Invoke()
{
return View();
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace RazorWebSite.Controllers
{
public class ViewWithPathsController : Controller
{
[HttpGet("/ViewWithPaths")]
public IActionResult Index()
{
return View();
}
}
}

View File

@ -0,0 +1,4 @@
<component>
@ViewContext.ExecutingFilePath
@ViewContext.View.Path
</component>

View File

@ -0,0 +1,6 @@
<Index>
@ViewContext.ExecutingFilePath
@ViewContext.View.Path
@Component.Invoke("ComponentForViewWithPaths")
@Html.Partial("_Partial")
</Index>

View File

@ -0,0 +1,5 @@
<Layout>
@ViewContext.ExecutingFilePath
@ViewContext.View.Path
</Layout>
@RenderBody()

View File

@ -0,0 +1,4 @@
<Partial>
@ViewContext.ExecutingFilePath
@ViewContext.View.Path
</Partial>

View File

@ -0,0 +1,7 @@
@{
Layout = "_Layout";
}
<ViewStart>
@ViewContext.ExecutingFilePath
@ViewContext.View.Path
</ViewStart>