Allow to define section or/and body without the need to render it

This commit is contained in:
Shahriar Gholami 2016-02-01 14:08:35 +03:30 committed by Pranav K
parent 49ffeb16d2
commit 1e5b0b9bec
5 changed files with 161 additions and 23 deletions

View File

@ -219,7 +219,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
}
/// <summary>
/// {0} has not been called for the page at '{1}'.
/// {0} has not been called for the page at '{1}'. To ignore call {2}().
/// </summary>
internal static string RenderBodyNotCalled
{
@ -227,11 +227,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor
}
/// <summary>
/// {0} has not been called for the page at '{1}'.
/// {0} has not been called for the page at '{1}'. To ignore call {2}().
/// </summary>
internal static string FormatRenderBodyNotCalled(object p0, object p1)
internal static string FormatRenderBodyNotCalled(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("RenderBodyNotCalled"), p0, p1);
return string.Format(CultureInfo.CurrentCulture, GetString("RenderBodyNotCalled"), p0, p1, p2);
}
/// <summary>
@ -283,7 +283,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor
}
/// <summary>
/// The following sections have been defined but have not been rendered by the page at '{0}': '{1}'.
/// The following sections have been defined but have not been rendered by the page at '{0}': '{1}'. To ignore an unrendered section call {2}("sectionName").
/// </summary>
internal static string SectionsNotRendered
{
@ -291,11 +291,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor
}
/// <summary>
/// The following sections have been defined but have not been rendered by the page at '{0}': '{1}'.
/// The following sections have been defined but have not been rendered by the page at '{0}': '{1}'. To ignore an unrendered section call {2}("sectionName").
/// </summary>
internal static string FormatSectionsNotRendered(object p0, object p1)
internal static string FormatSectionsNotRendered(object p0, object p1, object p2)
{
return string.Format(CultureInfo.CurrentCulture, GetString("SectionsNotRendered"), p0, p1);
return string.Format(CultureInfo.CurrentCulture, GetString("SectionsNotRendered"), p0, p1, p2);
}
/// <summary>

View File

@ -12,7 +12,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Microsoft.AspNetCore.Mvc.Rendering;
@ -40,6 +39,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor
private TagHelperAttributeInfo _tagHelperAttributeInfo;
private HtmlContentWrapperTextWriter _valueBuffer;
private IViewBufferScope _bufferScope;
private bool _ignoreBody;
private HashSet<string> _ignoredSections;
public RazorPage()
{
@ -655,6 +656,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor
return BodyContent;
}
/// <summary>
/// In a Razor layout page, ignores rendering the portion of a content page that is not within a named section.
/// </summary>
public void IgnoreBody()
{
_ignoreBody = true;
}
/// <summary>
/// Creates a named content section in the page that can be invoked in a Layout page using
/// <see cref="RenderSection(string)"/> or <see cref="RenderSectionAsync(string, bool)"/>.
@ -815,6 +824,34 @@ namespace Microsoft.AspNetCore.Mvc.Razor
}
}
/// <summary>
/// In layout pages, ignores rendering the content of the section named <paramref name="sectionName"/>.
/// </summary>
/// <param name="sectionName">The section to ignore.</param>
public void IgnoreSection(string sectionName)
{
if (sectionName == null)
{
throw new ArgumentNullException(nameof(sectionName));
}
if (!PreviousSectionWriters.ContainsKey(sectionName))
{
// If the section is not defined, throw an error.
throw new InvalidOperationException(Resources.FormatSectionNotDefined(
ViewContext.ExecutingFilePath,
sectionName,
ViewContext.View.Path));
}
if (_ignoredSections == null)
{
_ignoredSections = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
_ignoredSections.Add(sectionName);
}
/// <summary>
/// Invokes <see cref="TextWriter.FlushAsync"/> on <see cref="Output"/> and <see cref="M:Stream.FlushAsync"/>
/// on the response stream, writing out any buffered content to the <see cref="HttpResponse.Body"/>.
@ -860,17 +897,27 @@ namespace Microsoft.AspNetCore.Mvc.Razor
_renderedSections,
StringComparer.OrdinalIgnoreCase);
if (sectionsNotRendered.Any())
string[] sectionsNotIgnored;
if (_ignoredSections != null)
{
var sectionNames = string.Join(", ", sectionsNotRendered);
throw new InvalidOperationException(Resources.FormatSectionsNotRendered(Path, sectionNames));
sectionsNotIgnored = sectionsNotRendered.Except(_ignoredSections, StringComparer.OrdinalIgnoreCase).ToArray();
}
else
{
sectionsNotIgnored = sectionsNotRendered.ToArray();
}
if (sectionsNotIgnored.Length > 0)
{
var sectionNames = string.Join(", ", sectionsNotIgnored);
throw new InvalidOperationException(Resources.FormatSectionsNotRendered(Path, sectionNames, nameof(IgnoreSection)));
}
}
else if (BodyContent != null && !_renderedBody)
else if (BodyContent != null && !_renderedBody && !_ignoreBody)
{
// 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), Path);
// If a body was defined and the body not ignored, then RenderBody should have been called.
var message = Resources.FormatRenderBodyNotCalled(nameof(RenderBody), Path, nameof(IgnoreBody));
throw new InvalidOperationException(message);
}
}

View File

@ -157,7 +157,7 @@
<value>{0} invocation in '{1}' is invalid. {0} can only be called from a layout page.</value>
</data>
<data name="RenderBodyNotCalled" xml:space="preserve">
<value>{0} has not been called for the page at '{1}'.</value>
<value>{0} has not been called for the page at '{1}'. To ignore call {2}().</value>
</data>
<data name="SectionAlreadyDefined" xml:space="preserve">
<value>Section '{0}' is already defined.</value>
@ -169,7 +169,7 @@
<value>The layout page '{0}' cannot find the section '{1}' in the content page '{2}'.</value>
</data>
<data name="SectionsNotRendered" xml:space="preserve">
<value>The following sections have been defined but have not been rendered by the page at '{0}': '{1}'.</value>
<value>The following sections have been defined but have not been rendered by the page at '{0}': '{1}'. To ignore an unrendered section call {2}("sectionName").</value>
</data>
<data name="ViewCannotBeActivated" xml:space="preserve">
<value>View of type '{0}' cannot be activated by '{1}'.</value>

View File

@ -302,6 +302,29 @@ namespace Microsoft.AspNetCore.Mvc.Razor
Assert.Equal(message, ex.Message);
}
[Fact]
public async Task IgnoreSection_ThrowsIfSectionIsNotFound()
{
// Arrange
var context = CreateViewContext(viewPath: "/Views/TestPath/Test.cshtml");
context.ExecutingFilePath = "/Views/Shared/_Layout.cshtml";
var page = CreatePage(v =>
{
v.Path = "/Views/TestPath/Test.cshtml";
v.IgnoreSection("bar");
}, context);
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
{
{ "baz", _nullRenderAsyncDelegate }
};
// Act & Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => page.ExecuteAsync());
var message = $"The layout page '/Views/Shared/_Layout.cshtml' cannot find the section 'bar'" +
" in the content page '/Views/TestPath/Test.cshtml'.";
Assert.Equal(message, ex.Message);
}
[Fact]
public void IsSectionDefined_ThrowsIfPreviousSectionWritersIsNotRegistered()
{
@ -471,7 +494,24 @@ namespace Microsoft.AspNetCore.Mvc.Razor
// Act & Assert
var ex = Assert.Throws<InvalidOperationException>(() => page.EnsureRenderedBodyOrSections());
Assert.Equal($"RenderBody has not been called for the page at '{path}'.", ex.Message);
Assert.Equal($"RenderBody has not been called for the page at '{path}'. To ignore call IgnoreBody().", ex.Message);
}
[Fact]
public async Task EnsureRenderedBodyOrSections_SucceedsIfRenderBodyIsNotCalledFromPage_AndNoSectionsAreDefined_AndBodyIgnored()
{
// Arrange
var path = "page-path";
var page = CreatePage(v =>
{
});
page.Path = path;
page.BodyContent = new HtmlString("some content");
page.IgnoreBody();
// Act & Assert (does not throw)
await page.ExecuteAsync();
page.EnsureRenderedBodyOrSections();
}
[Fact]
@ -495,10 +535,57 @@ namespace Microsoft.AspNetCore.Mvc.Razor
var ex = Assert.Throws<InvalidOperationException>(() => page.EnsureRenderedBodyOrSections());
Assert.Equal(
"The following sections have been defined but have not been rendered by the page at " +
$"'{path}': '{sectionName}'.",
$"'{path}': '{sectionName}'. To ignore an unrendered section call IgnoreSection(\"sectionName\").",
ex.Message);
}
[Fact]
public async Task EnsureRenderedBodyOrSections_SucceedsIfDefinedSectionsAreNotRendered_AndIgnored()
{
// Arrange
var path = "page-path";
var sectionName = "sectionA";
var page = CreatePage(v =>
{
});
page.Path = path;
page.BodyContent = new HtmlString("some content");
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
{
{ sectionName, _nullRenderAsyncDelegate }
};
page.IgnoreSection(sectionName);
// Act & Assert (does not throw)
await page.ExecuteAsync();
page.EnsureRenderedBodyOrSections();
}
[Fact]
public async Task ExecuteAsync_RendersSectionsThatAreNotIgnored()
{
// Arrange
var path = "page-path";
var page = CreatePage(async p =>
{
p.IgnoreSection("ignored");
p.Write(await p.RenderSectionAsync("not-ignored-section"));
});
page.Path = path;
page.BodyContent = new HtmlString("some content");
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
{
{ "ignored", _nullRenderAsyncDelegate },
{ "not-ignored-section", writer => writer.WriteAsync("not-ignored-section-content") }
};
// Act
await page.ExecuteAsync();
// Assert
Assert.Equal("not-ignored-section-content", page.RenderedContent);
}
[Fact]
public async Task EnsureRenderedBodyOrSections_SucceedsIfRenderBodyIsNotCalled_ButAllDefinedSectionsAreRendered()
{

View File

@ -684,7 +684,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
Assert.Equal("The following sections have been defined but have not been rendered by the page "
+ $"at '{LayoutPath}': 'head, foot'.", ex.Message);
+ $"at '{LayoutPath}': 'head, foot'. To ignore an unrendered section call IgnoreSection(\"sectionName\").",
ex.Message);
}
[Fact]
@ -879,7 +880,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
Assert.Equal("The following sections have been defined but have not been rendered by the page at "
+ "'/Shared/Layout1.cshtml': 'foo'.", ex.Message);
+ "'/Shared/Layout1.cshtml': 'foo'. To ignore an unrendered section call IgnoreSection(\"sectionName\").",
ex.Message);
}
[Fact]
@ -946,7 +948,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
Assert.Equal("The following sections have been defined but have not been rendered by the page at " +
"'/Shared/Layout1.cshtml': 'foo'.", ex.Message);
"'/Shared/Layout1.cshtml': 'foo'. To ignore an unrendered section call IgnoreSection(\"sectionName\").",
ex.Message);
}
[Fact]
@ -978,7 +981,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor
// Act and Assert
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
Assert.Equal($"RenderBody has not been called for the page at '{LayoutPath}'.", ex.Message);
Assert.Equal($"RenderBody has not been called for the page at '{LayoutPath}'. To ignore call IgnoreBody().",
ex.Message);
}
[Fact]