Allow to define section or/and body without the need to render it
This commit is contained in:
parent
49ffeb16d2
commit
1e5b0b9bec
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue