diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
index 1b65fec46e..11a7e5a092 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
@@ -72,14 +72,11 @@ namespace Microsoft.AspNet.Mvc.Razor
Task ExecuteAsync();
///
- /// Verifies that RenderBody is called for the page that is
- /// part of view execution hierarchy.
+ /// Verifies that all sections defined in were rendered, or
+ /// the body was rendered if no sections were defined.
///
- void EnsureBodyWasRendered();
-
- ///
- /// Gets the sections that are rendered in the page.
- ///
- IEnumerable RenderedSections { get; }
+ /// if one or more sections were not rendered or if no sections were
+ /// defined and the body was not rendered.
+ void EnsureRenderedBodyOrSections();
}
}
\ 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 692a9fc7b4..fee66c9588 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs
@@ -219,7 +219,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
///
- /// {0} must be called from a layout page.
+ /// {0} has not been called for the page '{1}'.
///
internal static string RenderBodyNotCalled
{
@@ -227,11 +227,11 @@ namespace Microsoft.AspNet.Mvc.Razor
}
///
- /// {0} must be called from a layout page.
+ /// {0} has not been called for the page '{1}'.
///
- internal static string FormatRenderBodyNotCalled(object p0)
+ internal static string FormatRenderBodyNotCalled(object p0, object p1)
{
- return string.Format(CultureInfo.CurrentCulture, GetString("RenderBodyNotCalled"), p0);
+ return string.Format(CultureInfo.CurrentCulture, GetString("RenderBodyNotCalled"), p0, p1);
}
///
@@ -283,7 +283,7 @@ namespace Microsoft.AspNet.Mvc.Razor
}
///
- /// The following sections have been defined but have not been rendered: '{0}'.
+ /// The following sections have been defined but have not been rendered for the page '{0}': '{1}'.
///
internal static string SectionsNotRendered
{
@@ -291,11 +291,11 @@ namespace Microsoft.AspNet.Mvc.Razor
}
///
- /// The following sections have been defined but have not been rendered: '{0}'.
+ /// The following sections have been defined but have not been rendered for the page '{0}': '{1}'.
///
- internal static string FormatSectionsNotRendered(object p0)
+ internal static string FormatSectionsNotRendered(object p0, object p1)
{
- return string.Format(CultureInfo.CurrentCulture, GetString("SectionsNotRendered"), p0);
+ return string.Format(CultureInfo.CurrentCulture, GetString("SectionsNotRendered"), p0, p1);
}
///
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
index 50a53ec79c..a9ad88ad98 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
+using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
@@ -50,15 +51,6 @@ namespace Microsoft.AspNet.Mvc.Razor
}
}
- ///
- public IEnumerable RenderedSections
- {
- get
- {
- return _renderedSections;
- }
- }
-
///
public string Path { get; set; }
@@ -724,13 +716,27 @@ namespace Microsoft.AspNet.Mvc.Razor
}
///
- public void EnsureBodyWasRendered()
+ public void EnsureRenderedBodyOrSections()
{
- // If BodyContent is set, ensure it was rendered.
- if (RenderBodyDelegate != null && !_renderedBody)
+ // a) all sections defined for this page are rendered.
+ // b) if no sections are defined, then the body is rendered if it's available.
+ if (PreviousSectionWriters != null && PreviousSectionWriters.Count > 0)
{
+ var sectionsNotRendered = PreviousSectionWriters.Keys.Except(
+ _renderedSections,
+ StringComparer.OrdinalIgnoreCase);
+
+ if (sectionsNotRendered.Any())
+ {
+ var sectionNames = string.Join(", ", sectionsNotRendered);
+ throw new InvalidOperationException(Resources.FormatSectionsNotRendered(Path, sectionNames));
+ }
+ }
+ else if (RenderBodyDelegate != null && !_renderedBody)
+ {
+ // There are no sections defined, but RenderBody was NOT called.
// If a body was defined, then RenderBody should have been called.
- var message = Resources.FormatRenderBodyNotCalled(nameof(RenderBody));
+ var message = Resources.FormatRenderBodyNotCalled(nameof(RenderBody), Path);
throw new InvalidOperationException(message);
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
index 1ff1cfe197..7a574b2093 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
+++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Rendering;
using Microsoft.AspNet.PageExecutionInstrumentation;
@@ -170,8 +169,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 = RazorPage;
- var unrenderedSections = new HashSet(StringComparer.OrdinalIgnoreCase);
-
+ var renderedLayouts = new List();
while (!string.IsNullOrEmpty(previousPage.Layout))
{
if (!bodyWriter.IsBuffering)
@@ -194,20 +192,14 @@ namespace Microsoft.AspNet.Mvc.Razor
layoutPage.RenderBodyDelegate = bodyWriter.CopyTo;
bodyWriter = await RenderPageAsync(layoutPage, context, executeViewStart: false);
- // Verify that RenderBody is called
- layoutPage.EnsureBodyWasRendered();
-
- unrenderedSections.UnionWith(layoutPage.PreviousSectionWriters.Keys);
- unrenderedSections.ExceptWith(layoutPage.RenderedSections);
-
+ renderedLayouts.Add(layoutPage);
previousPage = layoutPage;
}
- // If not all sections are rendered, throw.
- if (unrenderedSections.Any())
+ // Ensure all defined sections were rendered or RenderBody was invoked for page without defined sections.
+ foreach (var layoutPage in renderedLayouts)
{
- var sectionNames = string.Join(", ", unrenderedSections);
- throw new InvalidOperationException(Resources.FormatSectionsNotRendered(sectionNames));
+ layoutPage.EnsureRenderedBodyOrSections();
}
if (bodyWriter.IsBuffering)
diff --git a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
index 4085f8eda8..101065801c 100644
--- a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx
@@ -157,7 +157,7 @@
{0} can only be called from a layout page.
- {0} must be called from a layout page.
+ {0} has not been called for the page at '{1}'.Section '{0}' is already defined.
@@ -169,7 +169,7 @@
Section '{0}' is not defined.
- The following sections have been defined but have not been rendered: '{0}'.
+ The following sections have been defined but have not been rendered by the page at '{0}': '{1}'.View of type '{0}' cannot be activated by '{1}'.
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
index 859029ea65..5e6264af23 100644
--- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
@@ -403,21 +403,70 @@ namespace Microsoft.AspNet.Mvc.Razor
}
[Fact]
- public async Task EnsureBodyWasRendered_ThrowsIfRenderBodyIsNotCalledFromPage()
+ public async Task EnsureRenderedBodyOrSections_ThrowsIfRenderBodyIsNotCalledFromPage_AndNoSectionsAreDefined()
{
// Arrange
- var expected = new HelperResult(action: null);
+ var path = "page-path";
var page = CreatePage(v =>
{
});
+ page.Path = path;
page.RenderBodyDelegate = CreateBodyAction("some content");
// Act
await page.ExecuteAsync();
- var ex = Assert.Throws(() => page.EnsureBodyWasRendered());
+ var ex = Assert.Throws(() => page.EnsureRenderedBodyOrSections());
// Assert
- Assert.Equal("RenderBody must be called from a layout page.", ex.Message);
+ Assert.Equal($"RenderBody has not been called for the page at '{path}'.", ex.Message);
+ }
+
+ [Fact]
+ public async Task EnsureRenderedBodyOrSections_ThrowsIfDefinedSectionsAreNotRendered()
+ {
+ // Arrange
+ var path = "page-path";
+ var sectionName = "sectionA";
+ var page = CreatePage(v =>
+ {
+ });
+ page.Path = path;
+ page.RenderBodyDelegate = CreateBodyAction("some content");
+ page.PreviousSectionWriters = new Dictionary
+ {
+ { sectionName, _nullRenderAsyncDelegate }
+ };
+
+ // Act
+ await page.ExecuteAsync();
+ var ex = Assert.Throws(() => page.EnsureRenderedBodyOrSections());
+
+ // Assert
+ Assert.Equal("The following sections have been defined but have not been rendered by the page at " +
+ $"'{path}': '{sectionName}'.", ex.Message);
+ }
+
+ [Fact]
+ public async Task EnsureRenderedBodyOrSections_SucceedsIfRenderBodyIsNotCalled_ButAllDefinedSectionsAreRendered()
+ {
+ // Arrange
+ var sectionA = "sectionA";
+ var sectionB = "sectionB";
+ var page = CreatePage(v =>
+ {
+ v.RenderSection(sectionA);
+ v.RenderSection(sectionB);
+ });
+ page.RenderBodyDelegate = CreateBodyAction("some content");
+ page.PreviousSectionWriters = new Dictionary
+ {
+ { sectionA, _nullRenderAsyncDelegate },
+ { sectionB, _nullRenderAsyncDelegate },
+ };
+
+ // Act and Assert
+ await page.ExecuteAsync();
+ page.EnsureRenderedBodyOrSections();
}
[Fact]
@@ -801,7 +850,8 @@ namespace Microsoft.AspNet.Mvc.Razor
selfClosing: false,
items: new Dictionary