diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorView.cs
new file mode 100644
index 0000000000..7f8f411a7e
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/IRazorView.cs
@@ -0,0 +1,22 @@
+// 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.Rendering;
+
+namespace Microsoft.AspNet.Mvc.Razor
+{
+ ///
+ /// Represents the contract for an that executes as part of its
+ /// execution.
+ ///
+ public interface IRazorView : IView
+ {
+ ///
+ /// Contextualizes the current instance of the providing it with the
+ /// to execute.
+ ///
+ /// The instance to execute.
+ /// Determines if the view is to be executed as a partial.
+ void Contextualize(IRazorPage razorPage, bool isPartial);
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
index 9235868bbc..dd8c6568fb 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
@@ -234,6 +234,22 @@ namespace Microsoft.AspNet.Mvc.Razor
return string.Format(CultureInfo.CurrentCulture, GetString("ViewContextMustBeSet"), p0, p1);
}
+ ///
+ /// The '{0}' method must be called before '{1}' can be invoked.
+ ///
+ internal static string ViewMustBeContextualized
+ {
+ get { return GetString("ViewMustBeContextualized"); }
+ }
+
+ ///
+ /// The '{0}' method must be called before '{1}' can be invoked.
+ ///
+ internal static string FormatViewMustBeContextualized(object p0, object p1)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("ViewMustBeContextualized"), p0, p1);
+ }
+
///
/// The method '{0}' cannot be invoked by this view.
///
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
index 712bf61b34..d2587b5bd7 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
@@ -3,56 +3,61 @@
using System;
using System.Threading.Tasks;
-using Microsoft.AspNet.Mvc.Rendering;
namespace Microsoft.AspNet.Mvc.Razor
{
///
- /// Represents a that executes one or more instances as part of
- /// view rendering.
+ /// Default implementation for that executes one or more
+ /// instances as part of view rendering.
///
- public class RazorView : IView
+ public class RazorView : IRazorView
{
private readonly IRazorPageFactory _pageFactory;
private readonly IRazorPageActivator _pageActivator;
private readonly IViewStartProvider _viewStartProvider;
- private readonly IRazorPage _page;
+ private IRazorPage _razorPage;
+ private bool _isPartial;
///
/// Initializes a new instance of RazorView
///
- /// The page to execute
/// The view factory used to instantiate layout and _ViewStart pages.
/// The used to activate pages.
- public RazorView([NotNull] IRazorPageFactory pageFactory,
- [NotNull] IRazorPageActivator pageActivator,
- [NotNull] IViewStartProvider viewStartProvider,
- [NotNull] IRazorPage page)
+ /// The used for discovery of _ViewStart
+ /// pages
+ public RazorView(IRazorPageFactory pageFactory,
+ IRazorPageActivator pageActivator,
+ IViewStartProvider viewStartProvider)
{
_pageFactory = pageFactory;
_pageActivator = pageActivator;
_viewStartProvider = viewStartProvider;
- _page = page;
}
- ///
- /// Gets or sets a value that determines if the view hierarchy is executed as part of
- /// executing the instance. The view hierarchy involves _ViewStart
- /// and Layout pages.
- ///
- public bool ExecuteViewHierarchy { get; set; }
+ ///
+ public virtual void Contextualize(IRazorPage razorPage, bool isPartial)
+ {
+ _razorPage = razorPage;
+ _isPartial = isPartial;
+ }
///
public virtual async Task RenderAsync([NotNull] ViewContext context)
{
- if (ExecuteViewHierarchy)
+ if (_razorPage == null)
{
- var bodyWriter = await RenderPageAsync(_page, context, executeViewStart: true);
+ var message = Resources.FormatViewMustBeContextualized(nameof(Contextualize), nameof(RenderAsync));
+ throw new InvalidOperationException(message);
+ }
+
+ if (!_isPartial)
+ {
+ var bodyWriter = await RenderPageAsync(_razorPage, context, executeViewStart: true);
await RenderLayoutAsync(context, bodyWriter);
}
else
{
- await RenderPageCoreAsync(_page, context);
+ await RenderPageCoreAsync(_razorPage, context);
}
}
@@ -93,14 +98,14 @@ namespace Microsoft.AspNet.Mvc.Razor
private async Task RenderViewStartAsync(ViewContext context)
{
- var viewStarts = _viewStartProvider.GetViewStartPages(_page.Path);
+ var viewStarts = _viewStartProvider.GetViewStartPages(_razorPage.Path);
foreach (var viewStart in viewStarts)
{
await RenderPageCoreAsync(viewStart, context);
// Copy over interesting properties from the ViewStart page to the entry page.
- _page.Layout = viewStart.Layout;
+ _razorPage.Layout = viewStart.Layout;
}
}
@@ -109,7 +114,7 @@ namespace Microsoft.AspNet.Mvc.Razor
{
// A layout page can specify another layout page. We'll need to continue
// looking for layout pages until they're no longer specified.
- var previousPage = _page;
+ var previousPage = _razorPage;
while (!string.IsNullOrEmpty(previousPage.Layout))
{
var layoutPage = _pageFactory.CreateInstance(previousPage.Layout);
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs
index a9ff53396f..6b476b4d50 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs
@@ -31,23 +31,14 @@ namespace Microsoft.AspNet.Mvc.Razor
};
private readonly IRazorPageFactory _pageFactory;
- private readonly ITypeActivator _typeActivator;
- private readonly IServiceProvider _serviceProvider;
///
- /// Initializes a new instance of the RazorViewEngine class.
+ /// Initializes a new instance of the class.
///
- /// The page factory used for creating .
- /// Activator for activated instances of .
- /// The provider used to provide instances of ViewStarts applicable to the
- /// page being rendered.
- public RazorViewEngine(IRazorPageFactory pageFactory,
- ITypeActivator typeActivator,
- IServiceProvider serviceProvider)
+ /// The page factory used for creating instances.
+ public RazorViewEngine(IRazorPageFactory pageFactory)
{
_pageFactory = pageFactory;
- _typeActivator = typeActivator;
- _serviceProvider = serviceProvider;
}
public IEnumerable ViewLocationFormats
@@ -83,7 +74,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var page = _pageFactory.CreateInstance(viewName);
if (page != null)
{
- return CreateFoundResult(page, viewName, partial);
+ return CreateFoundResult(context, page, viewName, partial);
}
}
return ViewEngineResult.NotFound(viewName, new[] { viewName });
@@ -100,7 +91,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var page = _pageFactory.CreateInstance(path);
if (page != null)
{
- return CreateFoundResult(page, path, partial);
+ return CreateFoundResult(context, page, path, partial);
}
}
@@ -108,10 +99,17 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
- private ViewEngineResult CreateFoundResult(IRazorPage page, string viewName, bool partial)
+ private ViewEngineResult CreateFoundResult(ActionContext actionContext,
+ IRazorPage page,
+ string viewName,
+ bool partial)
{
- var view = _typeActivator.CreateInstance(_serviceProvider, page);
- view.ExecuteViewHierarchy = !partial;
+ // A single request could result in creating multiple IRazorView instances (for partials, view components)
+ // and might store state. We'll use the service container to create new instances as we require.
+
+ var services = actionContext.HttpContext.RequestServices;
+ var view = services.GetService();
+ view.Contextualize(page, partial);
return ViewEngineResult.Found(viewName, view);
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
index 44185c3b9c..39439205d0 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
@@ -159,6 +159,9 @@
'{0} must be set to access '{1}'.
+
+ The '{0}' method must be called before '{1}' can be invoked.
+
The method '{0}' cannot be invoked by this view.
diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs
index beeb20bc60..d91082535e 100644
--- a/src/Microsoft.AspNet.Mvc/MvcServices.cs
+++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs
@@ -50,6 +50,7 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Singleton();
yield return describe.Scoped();
yield return describe.Singleton();
+ yield return describe.Transient();
yield return describe.Singleton();
// Virtual path view factory needs to stay scoped so views can get get scoped services.
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs
index 6fd7bbe963..9fc9744e0c 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs
@@ -3,10 +3,9 @@
using System;
using System.Collections.Generic;
-using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc.Rendering;
+using Microsoft.AspNet.PipelineCore;
using Microsoft.AspNet.Routing;
-using Microsoft.Framework.DependencyInjection;
using Moq;
using Xunit;
@@ -183,17 +182,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
var page = Mock.Of();
pageFactory.Setup(p => p.CreateInstance(It.IsAny()))
.Returns(Mock.Of());
-
- var serviceProvider = new Mock();
- serviceProvider.Setup(p => p.GetService(typeof(IRazorPageFactory)))
- .Returns(pageFactory.Object);
- serviceProvider.Setup(p => p.GetService(typeof(IRazorPageActivator)))
- .Returns(Mock.Of());
- serviceProvider.Setup(p => p.GetService(typeof(IViewStartProvider)))
- .Returns(Mock.Of());
- var viewEngine = new RazorViewEngine(pageFactory.Object,
- new TypeActivator(),
- serviceProvider.Object);
+ var viewEngine = new RazorViewEngine(pageFactory.Object);
var context = GetActionContext(_controllerTestContext);
// Act
@@ -201,7 +190,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
// Assert
Assert.True(result.Success);
- Assert.IsType(result.View);
+ Assert.IsAssignableFrom(result.View);
Assert.Equal("/Views/bar/test-view.cshtml", result.ViewName);
}
@@ -211,16 +200,20 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
pageFactory.Setup(vpf => vpf.CreateInstance(It.IsAny()))
.Returns(null);
- var viewEngine = new RazorViewEngine(pageFactory.Object,
- Mock.Of(),
- Mock.Of());
+ var viewEngine = new RazorViewEngine(pageFactory.Object);
return viewEngine;
}
- private static ActionContext GetActionContext(IDictionary routeValues)
+ private static ActionContext GetActionContext(IDictionary routeValues,
+ IRazorView razorView = null)
{
- var httpContext = Mock.Of();
+ var httpContext = new DefaultHttpContext();
+ var serviceProvider = new Mock();
+ serviceProvider.Setup(p => p.GetService(typeof(IRazorView)))
+ .Returns(razorView ?? Mock.Of());
+
+ httpContext.RequestServices = serviceProvider.Object;
var routeData = new RouteData { Values = routeValues };
return new ActionContext(httpContext, routeData, new ActionDescriptor());
}
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs
index 0e0ede1156..00ea8e44ed 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs
@@ -16,7 +16,24 @@ namespace Microsoft.AspNet.Mvc.Razor
private const string LayoutPath = "~/Shared/_Layout.cshtml";
[Fact]
- public async Task RenderAsync_WithoutHierarchy_DoesNotCreateOutputBuffer()
+ public async Task RenderAsync_ThrowsIfContextualizeHasNotBeenInvoked()
+ {
+ // Arrange
+ var page = new TestableRazorPage(v => { });
+ var view = new RazorView(Mock.Of(),
+ Mock.Of(),
+ CreateViewStartProvider());
+ var viewContext = CreateViewContext(view);
+ var expected = viewContext.Writer;
+
+ // Act and Assert
+ var ex = await Assert.ThrowsAsync(() => view.RenderAsync(viewContext));
+ Assert.Equal("The 'Contextualize' method must be called before 'RenderAsync' can be invoked.",
+ ex.Message);
+ }
+
+ [Fact]
+ public async Task RenderAsync_AsPartial_DoesNotCreateOutputBuffer()
{
// Arrange
TextWriter actual = null;
@@ -27,8 +44,8 @@ namespace Microsoft.AspNet.Mvc.Razor
});
var view = new RazorView(Mock.Of(),
Mock.Of(),
- CreateViewStartProvider(),
- page);
+ CreateViewStartProvider());
+ view.Contextualize(page, isPartial: true);
var viewContext = CreateViewContext(view);
var expected = viewContext.Writer;
@@ -41,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
[Fact]
- public async Task RenderAsync_WithoutHierarchy_ActivatesViews_WithThePassedInViewContext()
+ public async Task RenderAsync_AsPartial_ActivatesViews_WithThePassedInViewContext()
{
// Arrange
var viewData = new ViewDataDictionary(Mock.Of());
@@ -54,8 +71,8 @@ namespace Microsoft.AspNet.Mvc.Razor
var view = new RazorView(Mock.Of(),
activator.Object,
- CreateViewStartProvider(),
- page);
+ CreateViewStartProvider());
+ view.Contextualize(page, isPartial: true);
var viewContext = CreateViewContext(view);
var expectedWriter = viewContext.Writer;
activator.Setup(a => a.Activate(page, It.IsAny()))
@@ -75,7 +92,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
[Fact]
- public async Task RenderAsync_WithoutHierarchy_ActivatesViews()
+ public async Task RenderAsync_AsPartial_ActivatesViews()
{
// Arrange
var page = new TestableRazorPage(v => { });
@@ -84,8 +101,8 @@ namespace Microsoft.AspNet.Mvc.Razor
.Verifiable();
var view = new RazorView(Mock.Of(),
activator.Object,
- CreateViewStartProvider(),
- page);
+ CreateViewStartProvider());
+ view.Contextualize(page, isPartial: true);
var viewContext = CreateViewContext(view);
// Act
@@ -96,7 +113,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
[Fact]
- public async Task RenderAsync_WithoutHierarchy_DoesNotExecuteLayoutOrViewStartPages()
+ public async Task RenderAsync_AsPartial_DoesNotExecuteLayoutOrViewStartPages()
{
var page = new TestableRazorPage(v =>
{
@@ -106,8 +123,8 @@ namespace Microsoft.AspNet.Mvc.Razor
var viewStartProvider = CreateViewStartProvider();
var view = new RazorView(pageFactory.Object,
Mock.Of(),
- viewStartProvider,
- page);
+ viewStartProvider);
+ view.Contextualize(page, isPartial: true);
var viewContext = CreateViewContext(view);
// Act
@@ -119,7 +136,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
[Fact]
- public async Task RenderAsync_WithHierarchy_CreatesOutputBuffer()
+ public async Task RenderAsync_CreatesOutputBuffer()
{
// Arrange
TextWriter actual = null;
@@ -129,11 +146,8 @@ namespace Microsoft.AspNet.Mvc.Razor
});
var view = new RazorView(Mock.Of(),
Mock.Of(),
- CreateViewStartProvider(),
- page)
- {
- ExecuteViewHierarchy = true
- };
+ CreateViewStartProvider());
+ view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
var original = viewContext.Writer;
@@ -146,7 +160,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
[Fact]
- public async Task RenderAsync_WithHierarchy_CopiesBufferedContentToOutput()
+ public async Task RenderAsync_CopiesBufferedContentToOutput()
{
// Arrange
var page = new TestableRazorPage(v =>
@@ -155,11 +169,8 @@ namespace Microsoft.AspNet.Mvc.Razor
});
var view = new RazorView(Mock.Of(),
Mock.Of(),
- CreateViewStartProvider(),
- page)
- {
- ExecuteViewHierarchy = true
- };
+ CreateViewStartProvider());
+ view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
var original = viewContext.Writer;
@@ -171,7 +182,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
[Fact]
- public async Task RenderAsync_WithHierarchy_ActivatesPages()
+ public async Task RenderAsync_ActivatesPages()
{
// Arrange
var page = new TestableRazorPage(v =>
@@ -183,12 +194,9 @@ namespace Microsoft.AspNet.Mvc.Razor
.Verifiable();
var view = new RazorView(Mock.Of(),
activator.Object,
- CreateViewStartProvider(),
- page)
- {
- ExecuteViewHierarchy = true
- };
-
+ CreateViewStartProvider());
+ view.Contextualize(page, isPartial: false);
+
var viewContext = CreateViewContext(view);
// Act
@@ -199,7 +207,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
[Fact]
- public async Task RenderAsync_WithHierarchy_ExecutesViewStart()
+ public async Task RenderAsync_ExecutesViewStart()
{
// Arrange
var actualLayoutPath = "";
@@ -228,11 +236,8 @@ namespace Microsoft.AspNet.Mvc.Razor
.Verifiable();
var view = new RazorView(Mock.Of(),
activator.Object,
- CreateViewStartProvider(viewStart1, viewStart2),
- page)
- {
- ExecuteViewHierarchy = true
- };
+ CreateViewStartProvider(viewStart1, viewStart2));
+ view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act
@@ -243,7 +248,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
[Fact]
- public async Task RenderAsync_WithHierarchy_ExecutesLayoutPages()
+ public async Task RenderAsync_ExecutesLayoutPages()
{
// Arrange
var expected =
@@ -285,11 +290,8 @@ foot-content";
var view = new RazorView(pageFactory.Object,
activator.Object,
- CreateViewStartProvider(),
- page)
- {
- ExecuteViewHierarchy = true
- };
+ CreateViewStartProvider());
+ view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act
@@ -302,7 +304,7 @@ foot-content";
}
[Fact]
- public async Task RenderAsync_WithHierarchy_ThrowsIfSectionsWereDefinedButNotRendered()
+ public async Task RenderAsync_ThrowsIfSectionsWereDefinedButNotRendered()
{
// Arrange
var page = new TestableRazorPage(v =>
@@ -321,11 +323,8 @@ foot-content";
var view = new RazorView(pageFactory.Object,
Mock.Of(),
- CreateViewStartProvider(),
- page)
- {
- ExecuteViewHierarchy = true
- };
+ CreateViewStartProvider());
+ view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act and Assert
@@ -334,7 +333,7 @@ foot-content";
}
[Fact]
- public async Task RenderAsync_WithHierarchy_ThrowsIfBodyWasNotRendered()
+ public async Task RenderAsync_ThrowsIfBodyWasNotRendered()
{
// Arrange
var page = new TestableRazorPage(v =>
@@ -350,11 +349,8 @@ foot-content";
var view = new RazorView(pageFactory.Object,
Mock.Of(),
- CreateViewStartProvider(),
- page)
- {
- ExecuteViewHierarchy = true
- };
+ CreateViewStartProvider());
+ view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act and Assert
@@ -363,7 +359,7 @@ foot-content";
}
[Fact]
- public async Task RenderAsync_WithHierarchy_ExecutesNestedLayoutPages()
+ public async Task RenderAsync_ExecutesNestedLayoutPages()
{
// Arrange
var expected =
@@ -407,11 +403,8 @@ body-content";
var view = new RazorView(pageFactory.Object,
Mock.Of(),
- CreateViewStartProvider(),
- page)
- {
- ExecuteViewHierarchy = true
- };
+ CreateViewStartProvider());
+ view.Contextualize(page, isPartial: false);
var viewContext = CreateViewContext(view);
// Act