diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
new file mode 100644
index 0000000000..bfa26714cd
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
@@ -0,0 +1,48 @@
+// 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 System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace Microsoft.AspNet.Mvc.Razor
+{
+ ///
+ /// Represents properties and methods that are used by for execution.
+ ///
+ public interface IRazorPage
+ {
+ ///
+ /// Gets or sets the view context of the renderign view.
+ ///
+ ViewContext ViewContext { get; set; }
+
+ string BodyContent { get; set; }
+
+ ///
+ /// Gets or sets the path of a layout page.
+ ///
+ string Layout { get; set; }
+
+ ///
+ /// Gets or sets the sections that can be rendered by this page.
+ ///
+ Dictionary PreviousSectionWriters { get; set; }
+
+ ///
+ /// Gets the sections that are defined by this page.
+ ///
+ Dictionary SectionWriters { get; }
+
+ ///
+ /// Renders the page and writes the output to the .
+ ///
+ /// A task representing the result of executing the page.
+ Task ExecuteAsync();
+
+ ///
+ /// Verifies that RenderBody is called and that RenderSection is called for all sections for a page that is
+ /// part of view execution hierarchy.
+ ///
+ void EnsureBodyAndSectionsWereRendered();
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorPageActivator.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorPageActivator.cs
index 99eb83c493..6faabe5be3 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/IRazorPageActivator.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/IRazorPageActivator.cs
@@ -4,7 +4,7 @@
namespace Microsoft.AspNet.Mvc.Razor
{
///
- /// Provides methods to activate properties on a instance.
+ /// Provides methods to activate properties on a instance.
///
public interface IRazorPageActivator
{
@@ -13,6 +13,6 @@ namespace Microsoft.AspNet.Mvc.Razor
///
/// The page to activate.
/// The for the executing view.
- void Activate(RazorPage page, ViewContext context);
+ void Activate(IRazorPage page, ViewContext context);
}
}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorPageFactory.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorPageFactory.cs
index 1953c83585..0e4ecbe9ce 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/IRazorPageFactory.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/IRazorPageFactory.cs
@@ -4,15 +4,15 @@
namespace Microsoft.AspNet.Mvc.Razor
{
///
- /// Defines methods that are used for creating instances at a given path.
+ /// Defines methods that are used for creating instances at a given path.
///
public interface IRazorPageFactory
{
///
- /// Creates a for the specified path.
+ /// Creates a for the specified path.
///
/// The path to locate the RazorPage.
- /// The RazorPage instance if it exists, null otherwise.
- RazorPage CreateInstance(string viewPath);
+ /// The IRazorPage instance if it exists, null otherwise.
+ IRazorPage CreateInstance(string viewPath);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Microsoft.AspNet.Mvc.Razor.kproj b/src/Microsoft.AspNet.Mvc.Razor/Microsoft.AspNet.Mvc.Razor.kproj
index 9d991f2d35..b3d6054eb8 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Microsoft.AspNet.Mvc.Razor.kproj
+++ b/src/Microsoft.AspNet.Mvc.Razor/Microsoft.AspNet.Mvc.Razor.kproj
@@ -29,7 +29,8 @@
-
+
+
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
index a869f01f8b..fda790a69b 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
@@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.Razor
///
/// Represents properties and methods that are needed in order to render a view that uses Razor syntax.
///
- public abstract class RazorPage
+ public abstract class RazorPage : IRazorPage
{
private readonly HashSet _renderedSections = new HashSet(StringComparer.OrdinalIgnoreCase);
private bool _renderedBody;
@@ -42,6 +42,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
+ ///
public ViewContext ViewContext { get; set; }
public string Layout { get; set; }
@@ -86,10 +87,13 @@ namespace Microsoft.AspNet.Mvc.Razor
public string BodyContent { get; set; }
+ ///
public Dictionary PreviousSectionWriters { get; set; }
+ ///
public Dictionary SectionWriters { get; private set; }
+ ///
public abstract Task ExecuteAsync();
public virtual void Write(object value)
@@ -292,10 +296,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
- ///
- /// Verifies that RenderBody is called and that RenderSection is called for all sections for a page that is
- /// part of view execution hierarchy.
- ///
+ ///
public void EnsureBodyAndSectionsWereRendered()
{
// If PreviousSectionWriters is set, ensure all defined sections were rendered.
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPageActivator.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPageActivator.cs
index ffb424c3e6..9572a19892 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorPageActivator.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPageActivator.cs
@@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
///
- public void Activate([NotNull] RazorPage page, [NotNull] ViewContext context)
+ public void Activate([NotNull] IRazorPage page, [NotNull] ViewContext context)
{
var activationInfo = _activationInfo.GetOrAdd(page.GetType(),
CreateViewActivationInfo);
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
new file mode 100644
index 0000000000..0982918fbc
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
@@ -0,0 +1,118 @@
+// 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 System;
+using System.IO;
+using System.Text;
+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.
+ ///
+ public class RazorView : IView
+ {
+ private readonly IRazorPageFactory _pageFactory;
+ private readonly IRazorPageActivator _pageActivator;
+ private readonly IRazorPage _page;
+ private readonly bool _executeViewHierarchy;
+
+ ///
+ /// Initializes a new instance of RazorView
+ ///
+ /// The page to execute
+ /// The view factory used to instantiate additional views.
+ /// The used to activate pages.
+ /// A value that indiciates whether the view hierarchy that involves
+ /// view start and layout pages are executed as part of the executing the page.
+ public RazorView([NotNull] IRazorPageFactory pageFactory,
+ [NotNull] IRazorPageActivator pageActivator,
+ [NotNull] IRazorPage page,
+ bool executeViewHierarchy)
+ {
+ _pageFactory = pageFactory;
+ _pageActivator = pageActivator;
+ _page = page;
+ _executeViewHierarchy = executeViewHierarchy;
+ }
+
+ ///
+ public async Task RenderAsync([NotNull] ViewContext context)
+ {
+ if (_executeViewHierarchy)
+ {
+ var bodyContent = await RenderPageAsync(_page, context);
+ await RenderLayoutAsync(context, bodyContent);
+ }
+ else
+ {
+ await RenderPageCoreAsync(_page, context);
+ }
+ }
+
+ private async Task RenderPageAsync(IRazorPage page, ViewContext context)
+ {
+ var contentBuilder = new StringBuilder(1024);
+ using (var bodyWriter = new StringWriter(contentBuilder))
+ {
+ // The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers
+ // and ViewComponents to reference it.
+ var oldWriter = context.Writer;
+ context.Writer = bodyWriter;
+ try
+ {
+ await RenderPageCoreAsync(page, context);
+ }
+ finally
+ {
+ context.Writer = oldWriter;
+ }
+ }
+
+ return contentBuilder.ToString();
+ }
+
+ private async Task RenderPageCoreAsync(IRazorPage page, ViewContext context)
+ {
+ // Activating a page might mutate the ViewContext (for instance ViewContext.ViewData) is mutated by
+ // RazorPageActivator. We'll instead pass in a copy of the ViewContext.
+ var pageViewContext = new ViewContext(context, context.View, context.ViewData, context.Writer);
+ page.ViewContext = pageViewContext;
+ _pageActivator.Activate(page, pageViewContext);
+
+ await page.ExecuteAsync();
+ }
+
+ private async Task RenderLayoutAsync(ViewContext context,
+ string bodyContent)
+ {
+ // 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;
+ while (!string.IsNullOrEmpty(previousPage.Layout))
+ {
+ var layoutPage = _pageFactory.CreateInstance(previousPage.Layout);
+ if (layoutPage == null)
+ {
+ var message = Resources.FormatLayoutCannotBeLocated(previousPage.Layout);
+ throw new InvalidOperationException(message);
+ }
+
+ layoutPage.PreviousSectionWriters = previousPage.SectionWriters;
+ layoutPage.BodyContent = bodyContent;
+
+ bodyContent = await RenderPageAsync(layoutPage, context);
+
+ // Verify that RenderBody is called, or that RenderSection is called for all sections
+ layoutPage.EnsureBodyAndSectionsWereRendered();
+
+ previousPage = layoutPage;
+ }
+
+ await context.Writer.WriteAsync(bodyContent);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs
index 081be65b5c..4d36cf0bde 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs
@@ -92,7 +92,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
- private ViewEngineResult CreateFoundResult(RazorPage page, string viewName, bool partial)
+ private ViewEngineResult CreateFoundResult(IRazorPage page, string viewName, bool partial)
{
var view = new RazorView(_pageFactory,
_viewActivator,
diff --git a/src/Microsoft.AspNet.Mvc.Razor/FileBasedRazorPageFactory.cs b/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs
similarity index 66%
rename from src/Microsoft.AspNet.Mvc.Razor/FileBasedRazorPageFactory.cs
rename to src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs
index 0cdd72eddf..aaeff501d3 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/FileBasedRazorPageFactory.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs
@@ -11,17 +11,17 @@ namespace Microsoft.AspNet.Mvc.Razor
/// Represents a that creates instances
/// from razor files in the file system.
///
- public class FileBasedRazorPageFactory : IRazorPageFactory
+ public class VirtualPathRazorPageFactory : IRazorPageFactory
{
private readonly IRazorCompilationService _compilationService;
private readonly ITypeActivator _activator;
private readonly IServiceProvider _serviceProvider;
private readonly IFileInfoCache _fileInfoCache;
- public FileBasedRazorPageFactory(IRazorCompilationService compilationService,
- ITypeActivator typeActivator,
- IServiceProvider serviceProvider,
- IFileInfoCache fileInfoCache)
+ public VirtualPathRazorPageFactory(IRazorCompilationService compilationService,
+ ITypeActivator typeActivator,
+ IServiceProvider serviceProvider,
+ IFileInfoCache fileInfoCache)
{
_compilationService = compilationService;
_activator = typeActivator;
@@ -30,14 +30,14 @@ namespace Microsoft.AspNet.Mvc.Razor
}
///
- public RazorPage CreateInstance([NotNull] string viewPath)
+ public IRazorPage CreateInstance([NotNull] string viewPath)
{
- var fileInfo = _fileInfoCache.GetFileInfo(viewPath.TrimStart('~'));
+ var fileInfo = _fileInfoCache.GetFileInfo(viewPath);
if (fileInfo != null)
{
var result = _compilationService.Compile(fileInfo);
- var page = (RazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
+ var page = (IRazorPage)_activator.CreateInstance(_serviceProvider, result.CompiledType);
return page;
}
diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs
index f4927cc027..5b356f40a5 100644
--- a/src/Microsoft.AspNet.Mvc/MvcServices.cs
+++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs
@@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Mvc
yield return describe.Singleton();
// Virtual path view factory needs to stay scoped so views can get get scoped services.
- yield return describe.Scoped();
+ yield return describe.Scoped();
yield return describe.Singleton();
yield return describe.Transient,
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs
index ee350c61c1..dc4fb8deba 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs
@@ -60,7 +60,7 @@ namespace Microsoft.AspNet.Mvc.Razor
var expectedViewData = viewContext.ViewData;
var expectedWriter = viewContext.Writer;
activator.Setup(a => a.Activate(page, It.IsAny()))
- .Callback((RazorPage p, ViewContext c) =>
+ .Callback((IRazorPage p, ViewContext c) =>
{
Assert.NotSame(c, viewContext);
c.ViewData = viewData;
diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.cshtml b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.cshtml
index 0c334f0585..00b84b6ee8 100644
--- a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.cshtml
+++ b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.cshtml
@@ -1,4 +1,4 @@
@{
- Layout = "~/Views/Shared/_Layout.cshtml";
+ Layout = "/Views/Shared/_Layout.cshtml";
}
ViewWithFullPath-content
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithLayout.cshtml b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithLayout.cshtml
index 4904ee6996..917dcb1283 100644
--- a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithLayout.cshtml
+++ b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithLayout.cshtml
@@ -1,4 +1,4 @@
@{
- Layout = "~/Views/Shared/_Layout.cshtml";
+ Layout = "/Views/Shared/_Layout.cshtml";
}
ViewWithLayout-Content
\ No newline at end of file
diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithNestedLayout.cshtml b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithNestedLayout.cshtml
index 93aa57fcfa..476c051ead 100644
--- a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithNestedLayout.cshtml
+++ b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithNestedLayout.cshtml
@@ -1,4 +1,4 @@
@{
- Layout = "~/Views/ViewEngine/_NestedLayout.cshtml";
+ Layout = "/Views/ViewEngine/_NestedLayout.cshtml";
}
ViewWithNestedLayout-Content
\ No newline at end of file