Adding exceptions for RenderSection, DefineSection and RenderBody
This commit is contained in:
parent
1cd15fbf60
commit
931d18b851
|
|
@ -91,7 +91,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// RenderBody can only be called from a layout page.
|
/// {0} can only be called from a layout page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string RenderBodyCannotBeCalled
|
internal static string RenderBodyCannotBeCalled
|
||||||
{
|
{
|
||||||
|
|
@ -99,11 +99,27 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// RenderBody can only be called from a layout page.
|
/// {0} can only be called from a layout page.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static string FormatRenderBodyCannotBeCalled()
|
internal static string FormatRenderBodyCannotBeCalled(object p0)
|
||||||
{
|
{
|
||||||
return GetString("RenderBodyCannotBeCalled");
|
return string.Format(CultureInfo.CurrentCulture, GetString("RenderBodyCannotBeCalled"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// {0} must be called from a layout page.
|
||||||
|
/// </summary>
|
||||||
|
internal static string RenderBodyNotCalled
|
||||||
|
{
|
||||||
|
get { return GetString("RenderBodyNotCalled"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// {0} must be called from a layout page.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatRenderBodyNotCalled(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("RenderBodyNotCalled"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -122,6 +138,22 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("SectionAlreadyDefined"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("SectionAlreadyDefined"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// {0} has already been called for the section named '{1}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SectionAlreadyRendered
|
||||||
|
{
|
||||||
|
get { return GetString("SectionAlreadyRendered"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// {0} has already been called for the section named '{1}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatSectionAlreadyRendered(object p0, object p1)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("SectionAlreadyRendered"), p0, p1);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Section '{0}' is not defined.
|
/// Section '{0}' is not defined.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -138,6 +170,22 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
return string.Format(CultureInfo.CurrentCulture, GetString("SectionNotDefined"), p0);
|
return string.Format(CultureInfo.CurrentCulture, GetString("SectionNotDefined"), p0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The following sections have been defined but have not been rendered: '{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string SectionsNotRendered
|
||||||
|
{
|
||||||
|
get { return GetString("SectionsNotRendered"); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The following sections have been defined but have not been rendered: '{0}'.
|
||||||
|
/// </summary>
|
||||||
|
internal static string FormatSectionsNotRendered(object p0)
|
||||||
|
{
|
||||||
|
return string.Format(CultureInfo.CurrentCulture, GetString("SectionsNotRendered"), p0);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The partial view '{0}' was not found. The following locations were searched:{1}
|
/// The partial view '{0}' was not found. The following locations were searched:{1}
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -11,6 +12,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
public abstract class RazorView : IView
|
public abstract class RazorView : IView
|
||||||
{
|
{
|
||||||
|
private readonly HashSet<string> _renderedSections = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
private bool _renderedBody;
|
||||||
|
|
||||||
public IViewComponentHelper Component
|
public IViewComponentHelper Component
|
||||||
{
|
{
|
||||||
get { return Context == null ? null : Context.Component; }
|
get { return Context == null ? null : Context.Component; }
|
||||||
|
|
@ -59,6 +63,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
context.Writer = bodyWriter;
|
context.Writer = bodyWriter;
|
||||||
await ExecuteAsync();
|
await ExecuteAsync();
|
||||||
|
|
||||||
|
// Verify that RenderBody is called, or that RenderSection is called for all sections
|
||||||
|
VerifyRenderedBodyOrSections();
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -245,8 +252,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
if (BodyContent == null)
|
if (BodyContent == null)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(Resources.RenderBodyCannotBeCalled);
|
throw new InvalidOperationException(Resources.FormatRenderBodyCannotBeCalled("RenderBody"));
|
||||||
}
|
}
|
||||||
|
_renderedBody = true;
|
||||||
return new HtmlString(BodyContent);
|
return new HtmlString(BodyContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,10 +281,15 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
public HelperResult RenderSection([NotNull] string name, bool required)
|
public HelperResult RenderSection([NotNull] string name, bool required)
|
||||||
{
|
{
|
||||||
EnsureMethodCanBeInvoked("RenderSection");
|
EnsureMethodCanBeInvoked("RenderSection");
|
||||||
|
if (_renderedSections.Contains(name))
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException(Resources.FormatSectionAlreadyRendered("RenderSection", name));
|
||||||
|
}
|
||||||
|
|
||||||
HelperResult action;
|
HelperResult action;
|
||||||
if (PreviousSectionWriters.TryGetValue(name, out action))
|
if (PreviousSectionWriters.TryGetValue(name, out action))
|
||||||
{
|
{
|
||||||
|
_renderedSections.Add(name);
|
||||||
return action;
|
return action;
|
||||||
}
|
}
|
||||||
else if (required)
|
else if (required)
|
||||||
|
|
@ -298,5 +311,23 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
throw new InvalidOperationException(Resources.FormatView_MethodCannotBeCalled(methodName));
|
throw new InvalidOperationException(Resources.FormatView_MethodCannotBeCalled(methodName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void VerifyRenderedBodyOrSections()
|
||||||
|
{
|
||||||
|
if (BodyContent != null)
|
||||||
|
{
|
||||||
|
var sectionsNotRendered = PreviousSectionWriters.Keys.Except(_renderedSections, StringComparer.OrdinalIgnoreCase);
|
||||||
|
if (sectionsNotRendered.Any())
|
||||||
|
{
|
||||||
|
var sectionNames = String.Join(", ", sectionsNotRendered);
|
||||||
|
throw new InvalidOperationException(Resources.FormatSectionsNotRendered(sectionNames));
|
||||||
|
}
|
||||||
|
else if (!_renderedBody)
|
||||||
|
{
|
||||||
|
// If a body was defined, then RenderBody should have been called.
|
||||||
|
throw new InvalidOperationException(Resources.FormatRenderBodyNotCalled("RenderBody"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -133,14 +133,23 @@
|
||||||
<value>Only one '{0}' statement is allowed in a file.</value>
|
<value>Only one '{0}' statement is allowed in a file.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RenderBodyCannotBeCalled" xml:space="preserve">
|
<data name="RenderBodyCannotBeCalled" xml:space="preserve">
|
||||||
<value>RenderBody can only be called from a layout page.</value>
|
<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>
|
||||||
</data>
|
</data>
|
||||||
<data name="SectionAlreadyDefined" xml:space="preserve">
|
<data name="SectionAlreadyDefined" xml:space="preserve">
|
||||||
<value>Section '{0}' is already defined.</value>
|
<value>Section '{0}' is already defined.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SectionAlreadyRendered" xml:space="preserve">
|
||||||
|
<value>{0} has already been called for the section named '{1}'.</value>
|
||||||
|
</data>
|
||||||
<data name="SectionNotDefined" xml:space="preserve">
|
<data name="SectionNotDefined" xml:space="preserve">
|
||||||
<value>Section '{0}' is not defined.</value>
|
<value>Section '{0}' is not defined.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SectionsNotRendered" xml:space="preserve">
|
||||||
|
<value>The following sections have been defined but have not been rendered: '{0}'.</value>
|
||||||
|
</data>
|
||||||
<data name="ViewEngine_PartialViewNotFound" xml:space="preserve">
|
<data name="ViewEngine_PartialViewNotFound" xml:space="preserve">
|
||||||
<value>The partial view '{0}' was not found. The following locations were searched:{1}</value>
|
<value>The partial view '{0}' was not found. The following locations were searched:{1}</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
||||||
|
|
@ -16,21 +16,19 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
public async Task DefineSection_ThrowsIfSectionIsAlreadyDefined()
|
public async Task DefineSection_ThrowsIfSectionIsAlreadyDefined()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
Exception ex = null;
|
|
||||||
var view = CreateView(v =>
|
var view = CreateView(v =>
|
||||||
{
|
{
|
||||||
v.DefineSection("foo", new HelperResult(action: null));
|
v.DefineSection("qux", new HelperResult(action: null));
|
||||||
|
v.DefineSection("qux", new HelperResult(action: null));
|
||||||
ex = Assert.Throws<InvalidOperationException>(
|
|
||||||
() => v.DefineSection("foo", new HelperResult(action: null)));
|
|
||||||
});
|
});
|
||||||
var viewContext = CreateViewContext(layoutView: null);
|
var viewContext = CreateViewContext(layoutView: null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await view.RenderAsync(viewContext);
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||||
|
() => view.RenderAsync(viewContext));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("Section 'foo' is already defined.", ex.Message);
|
Assert.Equal("Section 'qux' is already defined.", ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
@ -47,6 +45,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
var layoutView = CreateView(v =>
|
var layoutView = CreateView(v =>
|
||||||
{
|
{
|
||||||
actual = v.RenderSection("bar");
|
actual = v.RenderSection("bar");
|
||||||
|
v.RenderBodyPublic();
|
||||||
});
|
});
|
||||||
var viewContext = CreateViewContext(layoutView);
|
var viewContext = CreateViewContext(layoutView);
|
||||||
|
|
||||||
|
|
@ -81,7 +80,6 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = new HelperResult(action: null);
|
var expected = new HelperResult(action: null);
|
||||||
Exception ex = null;
|
|
||||||
var view = CreateView(v =>
|
var view = CreateView(v =>
|
||||||
{
|
{
|
||||||
v.DefineSection("baz", expected);
|
v.DefineSection("baz", expected);
|
||||||
|
|
@ -89,13 +87,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
});
|
});
|
||||||
var layoutView = CreateView(v =>
|
var layoutView = CreateView(v =>
|
||||||
{
|
{
|
||||||
ex = Assert.Throws<InvalidOperationException>(
|
v.RenderSection("bar");
|
||||||
() => v.RenderSection("bar"));
|
|
||||||
});
|
});
|
||||||
var viewContext = CreateViewContext(layoutView);
|
var viewContext = CreateViewContext(layoutView);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await view.RenderAsync(viewContext);
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
Assert.Equal("Section 'bar' is not defined.", ex.Message);
|
Assert.Equal("Section 'bar' is not defined.", ex.Message);
|
||||||
|
|
@ -126,6 +123,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
var layoutView = CreateView(v =>
|
var layoutView = CreateView(v =>
|
||||||
{
|
{
|
||||||
actual = v.IsSectionDefined("foo");
|
actual = v.IsSectionDefined("foo");
|
||||||
|
v.RenderSection("baz");
|
||||||
|
v.RenderBodyPublic();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -142,12 +141,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
bool? actual = null;
|
bool? actual = null;
|
||||||
var view = CreateView(v =>
|
var view = CreateView(v =>
|
||||||
{
|
{
|
||||||
v.DefineSection("foo", new HelperResult(writer => { }));
|
v.DefineSection("baz", new HelperResult(writer => { }));
|
||||||
v.Layout = LayoutPath;
|
v.Layout = LayoutPath;
|
||||||
});
|
});
|
||||||
var layoutView = CreateView(v =>
|
var layoutView = CreateView(v =>
|
||||||
{
|
{
|
||||||
actual = v.IsSectionDefined("foo");
|
actual = v.IsSectionDefined("baz");
|
||||||
|
v.RenderSection("baz");
|
||||||
|
v.RenderBodyPublic();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
|
@ -157,9 +158,125 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
Assert.Equal(true, actual);
|
Assert.Equal(true, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RazorView CreateView(Action<RazorView> executeAction)
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderSection_ThrowsIfSectionIsRenderedMoreThanOnce()
|
||||||
{
|
{
|
||||||
var view = new Mock<RazorView> { CallBase = true };
|
// Arrange
|
||||||
|
var expected = new HelperResult(action: null);
|
||||||
|
var view = CreateView(v =>
|
||||||
|
{
|
||||||
|
v.DefineSection("header", expected);
|
||||||
|
v.Layout = LayoutPath;
|
||||||
|
});
|
||||||
|
var layoutView = CreateView(v =>
|
||||||
|
{
|
||||||
|
v.RenderSection("header");
|
||||||
|
v.RenderSection("header");
|
||||||
|
});
|
||||||
|
var viewContext = CreateViewContext(layoutView);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("RenderSection has already been called for the section named 'header'.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_ThrowsIfDefinedSectionIsNotRendered()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = new HelperResult(action: null);
|
||||||
|
var view = CreateView(v =>
|
||||||
|
{
|
||||||
|
v.DefineSection("header", expected);
|
||||||
|
v.DefineSection("footer", expected);
|
||||||
|
v.DefineSection("sectionA", expected);
|
||||||
|
v.Layout = LayoutPath;
|
||||||
|
});
|
||||||
|
var layoutView = CreateView(v =>
|
||||||
|
{
|
||||||
|
v.RenderSection("sectionA");
|
||||||
|
v.RenderBodyPublic();
|
||||||
|
});
|
||||||
|
var viewContext = CreateViewContext(layoutView);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("The following sections have been defined but have not been rendered: 'header, footer'.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_ThrowsIfRenderBodyIsNotCalledFromPage()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = new HelperResult(action: null);
|
||||||
|
var view = CreateView(v =>
|
||||||
|
{
|
||||||
|
v.Layout = LayoutPath;
|
||||||
|
});
|
||||||
|
var layoutView = CreateView(v =>
|
||||||
|
{
|
||||||
|
});
|
||||||
|
var viewContext = CreateViewContext(layoutView);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
Assert.Equal("RenderBody must be called from a layout page.", ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task RenderAsync_RendersSectionsAndBody()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var expected = @"Layout start
|
||||||
|
Header section
|
||||||
|
body content
|
||||||
|
Footer section
|
||||||
|
Layout end
|
||||||
|
";
|
||||||
|
var view = CreateView(v =>
|
||||||
|
{
|
||||||
|
v.Layout = LayoutPath;
|
||||||
|
v.WriteLiteral("body content" + Environment.NewLine);
|
||||||
|
|
||||||
|
v.DefineSection("footer", new HelperResult(writer =>
|
||||||
|
{
|
||||||
|
writer.WriteLine("Footer section");
|
||||||
|
}));
|
||||||
|
|
||||||
|
v.DefineSection("header", new HelperResult(writer =>
|
||||||
|
{
|
||||||
|
writer.WriteLine("Header section");
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
var layoutView = CreateView(v =>
|
||||||
|
{
|
||||||
|
v.WriteLiteral("Layout start" + Environment.NewLine);
|
||||||
|
v.Write(v.RenderSection("header"));
|
||||||
|
v.Write(v.RenderBodyPublic());
|
||||||
|
v.Write(v.RenderSection("footer"));
|
||||||
|
v.WriteLiteral("Layout end" + Environment.NewLine);
|
||||||
|
|
||||||
|
});
|
||||||
|
var viewContext = CreateViewContext(layoutView);
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await view.RenderAsync(viewContext);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
var actual = ((StringWriter)viewContext.Writer).ToString();
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TestableRazorView CreateView(Action<TestableRazorView> executeAction)
|
||||||
|
{
|
||||||
|
var view = new Mock<TestableRazorView> { CallBase = true };
|
||||||
if (executeAction != null)
|
if (executeAction != null)
|
||||||
{
|
{
|
||||||
view.Setup(v => v.ExecuteAsync())
|
view.Setup(v => v.ExecuteAsync())
|
||||||
|
|
@ -183,5 +300,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
||||||
Writer = new StringWriter()
|
Writer = new StringWriter()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract class TestableRazorView : RazorView
|
||||||
|
{
|
||||||
|
public HtmlString RenderBodyPublic()
|
||||||
|
{
|
||||||
|
return base.RenderBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue