Skip to content
This repository has been archived by the owner on Dec 14, 2018. It is now read-only.

Commit

Permalink
Unrendered sections does not throw when redefined and rendered in nested
Browse files Browse the repository at this point in the history
layout.

Fixes #2252
  • Loading branch information
pranavkm authored and kirthik committed Apr 6, 2015
1 parent 214ed70 commit db1beee
Show file tree
Hide file tree
Showing 7 changed files with 300 additions and 57 deletions.
13 changes: 5 additions & 8 deletions src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,11 @@ public interface IRazorPage
Task ExecuteAsync();

/// <summary>
/// Verifies that RenderBody is called for the page that is
/// part of view execution hierarchy.
/// Verifies that all sections defined in <see cref="PreviousSectionWriters"/> were rendered, or
/// the body was rendered if no sections were defined.
/// </summary>
void EnsureBodyWasRendered();

/// <summary>
/// Gets the sections that are rendered in the page.
/// </summary>
IEnumerable<string> RenderedSections { get; }
/// <exception cref="InvalidOperationException">if one or more sections were not rendered or if no sections were
/// defined and the body was not rendered.</exception>
void EnsureRenderedBodyOrSections();
}
}
16 changes: 8 additions & 8 deletions src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 19 additions & 13 deletions src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
Expand Down Expand Up @@ -50,15 +51,6 @@ public HttpContext Context
}
}

/// <inheritdoc />
public IEnumerable<string> RenderedSections
{
get
{
return _renderedSections;
}
}

/// <inheritdoc />
public string Path { get; set; }

Expand Down Expand Up @@ -724,13 +716,27 @@ public async Task<HtmlString> FlushAsync()
}

/// <inheritdoc />
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);
}
}
Expand Down
18 changes: 5 additions & 13 deletions src/Microsoft.AspNet.Mvc.Razor/RazorView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -170,8 +169,7 @@ private async Task RenderLayoutAsync(ViewContext context,
// 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<string>(StringComparer.OrdinalIgnoreCase);

var renderedLayouts = new List<IRazorPage>();
while (!string.IsNullOrEmpty(previousPage.Layout))
{
if (!bodyWriter.IsBuffering)
Expand All @@ -194,20 +192,14 @@ private async Task RenderLayoutAsync(ViewContext context,
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)
Expand Down
4 changes: 2 additions & 2 deletions src/Microsoft.AspNet.Mvc.Razor/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@
<value>{0} can only be called from a layout page.</value>
</data>
<data name="RenderBodyNotCalled" xml:space="preserve">
<value>{0} must be called from a layout page.</value>
<value>{0} has not been called for the page at '{1}'.</value>
</data>
<data name="SectionAlreadyDefined" xml:space="preserve">
<value>Section '{0}' is already defined.</value>
Expand All @@ -169,7 +169,7 @@
<value>Section '{0}' is not defined.</value>
</data>
<data name="SectionsNotRendered" xml:space="preserve">
<value>The following sections have been defined but have not been rendered: '{0}'.</value>
<value>The following sections have been defined but have not been rendered by the page at '{0}': '{1}'.</value>
</data>
<data name="ViewCannotBeActivated" xml:space="preserve">
<value>View of type '{0}' cannot be activated by '{1}'.</value>
Expand Down
60 changes: 55 additions & 5 deletions test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -403,21 +403,70 @@ public async Task RenderSectionAsync_ThrowsIfNotInvokedFromLayoutPage()
}

[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<InvalidOperationException>(() => page.EnsureBodyWasRendered());
var ex = Assert.Throws<InvalidOperationException>(() => page.EnsureRenderedBodyOrSections());

// Assert
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<string, RenderAsyncDelegate>
{
{ sectionName, _nullRenderAsyncDelegate }
};

// Act
await page.ExecuteAsync();
var ex = Assert.Throws<InvalidOperationException>(() => page.EnsureRenderedBodyOrSections());

// Assert
Assert.Equal("RenderBody must be called from a layout page.", ex.Message);
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<string, RenderAsyncDelegate>
{
{ sectionA, _nullRenderAsyncDelegate },
{ sectionB, _nullRenderAsyncDelegate },
};

// Act and Assert
await page.ExecuteAsync();
page.EnsureRenderedBodyOrSections();
}

[Fact]
Expand Down Expand Up @@ -801,7 +850,8 @@ public async Task WriteTagHelperAsync_WritesContentAppropriately(
selfClosing: false,
items: new Dictionary<object, object>(),
uniqueId: string.Empty,
executeChildContentAsync: () => {
executeChildContentAsync: () =>
{
defaultTagHelperContent.SetContent(input);
return Task.FromResult(result: true);
},
Expand Down
Loading

0 comments on commit db1beee

Please sign in to comment.