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>
|
||||
/// RenderBody can only be called from a layout page.
|
||||
/// {0} can only be called from a layout page.
|
||||
/// </summary>
|
||||
internal static string RenderBodyCannotBeCalled
|
||||
{
|
||||
|
|
@ -99,11 +99,27 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// RenderBody can only be called from a layout page.
|
||||
/// {0} can only be called from a layout page.
|
||||
/// </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>
|
||||
|
|
@ -122,6 +138,22 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
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>
|
||||
/// Section '{0}' is not defined.
|
||||
/// </summary>
|
||||
|
|
@ -138,6 +170,22 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
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>
|
||||
/// The partial view '{0}' was not found. The following locations were searched:{1}
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -11,6 +12,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
public abstract class RazorView : IView
|
||||
{
|
||||
private readonly HashSet<string> _renderedSections = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
private bool _renderedBody;
|
||||
|
||||
public IViewComponentHelper Component
|
||||
{
|
||||
get { return Context == null ? null : Context.Component; }
|
||||
|
|
@ -59,6 +63,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
context.Writer = bodyWriter;
|
||||
await ExecuteAsync();
|
||||
|
||||
// Verify that RenderBody is called, or that RenderSection is called for all sections
|
||||
VerifyRenderedBodyOrSections();
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -245,8 +252,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
if (BodyContent == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.RenderBodyCannotBeCalled);
|
||||
throw new InvalidOperationException(Resources.FormatRenderBodyCannotBeCalled("RenderBody"));
|
||||
}
|
||||
_renderedBody = true;
|
||||
return new HtmlString(BodyContent);
|
||||
}
|
||||
|
||||
|
|
@ -273,10 +281,15 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public HelperResult RenderSection([NotNull] string name, bool required)
|
||||
{
|
||||
EnsureMethodCanBeInvoked("RenderSection");
|
||||
if (_renderedSections.Contains(name))
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatSectionAlreadyRendered("RenderSection", name));
|
||||
}
|
||||
|
||||
HelperResult action;
|
||||
if (PreviousSectionWriters.TryGetValue(name, out action))
|
||||
{
|
||||
_renderedSections.Add(name);
|
||||
return action;
|
||||
}
|
||||
else if (required)
|
||||
|
|
@ -298,5 +311,23 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
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>
|
||||
</data>
|
||||
<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 name="SectionAlreadyDefined" xml:space="preserve">
|
||||
<value>Section '{0}' is already defined.</value>
|
||||
</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">
|
||||
<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>
|
||||
</data>
|
||||
<data name="ViewEngine_PartialViewNotFound" xml:space="preserve">
|
||||
<value>The partial view '{0}' was not found. The following locations were searched:{1}</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -16,21 +16,19 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
public async Task DefineSection_ThrowsIfSectionIsAlreadyDefined()
|
||||
{
|
||||
// Arrange
|
||||
Exception ex = null;
|
||||
var view = CreateView(v =>
|
||||
{
|
||||
v.DefineSection("foo", new HelperResult(action: null));
|
||||
|
||||
ex = Assert.Throws<InvalidOperationException>(
|
||||
() => v.DefineSection("foo", new HelperResult(action: null)));
|
||||
v.DefineSection("qux", new HelperResult(action: null));
|
||||
v.DefineSection("qux", new HelperResult(action: null));
|
||||
});
|
||||
var viewContext = CreateViewContext(layoutView: null);
|
||||
|
||||
// Act
|
||||
await view.RenderAsync(viewContext);
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => view.RenderAsync(viewContext));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Section 'foo' is already defined.", ex.Message);
|
||||
Assert.Equal("Section 'qux' is already defined.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -47,6 +45,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
var layoutView = CreateView(v =>
|
||||
{
|
||||
actual = v.RenderSection("bar");
|
||||
v.RenderBodyPublic();
|
||||
});
|
||||
var viewContext = CreateViewContext(layoutView);
|
||||
|
||||
|
|
@ -81,7 +80,6 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
{
|
||||
// Arrange
|
||||
var expected = new HelperResult(action: null);
|
||||
Exception ex = null;
|
||||
var view = CreateView(v =>
|
||||
{
|
||||
v.DefineSection("baz", expected);
|
||||
|
|
@ -89,13 +87,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
});
|
||||
var layoutView = CreateView(v =>
|
||||
{
|
||||
ex = Assert.Throws<InvalidOperationException>(
|
||||
() => v.RenderSection("bar"));
|
||||
v.RenderSection("bar");
|
||||
});
|
||||
var viewContext = CreateViewContext(layoutView);
|
||||
|
||||
// Act
|
||||
await view.RenderAsync(viewContext);
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(() => view.RenderAsync(viewContext));
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Section 'bar' is not defined.", ex.Message);
|
||||
|
|
@ -126,6 +123,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
var layoutView = CreateView(v =>
|
||||
{
|
||||
actual = v.IsSectionDefined("foo");
|
||||
v.RenderSection("baz");
|
||||
v.RenderBodyPublic();
|
||||
});
|
||||
|
||||
// Act
|
||||
|
|
@ -142,12 +141,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
bool? actual = null;
|
||||
var view = CreateView(v =>
|
||||
{
|
||||
v.DefineSection("foo", new HelperResult(writer => { }));
|
||||
v.DefineSection("baz", new HelperResult(writer => { }));
|
||||
v.Layout = LayoutPath;
|
||||
});
|
||||
var layoutView = CreateView(v =>
|
||||
{
|
||||
actual = v.IsSectionDefined("foo");
|
||||
actual = v.IsSectionDefined("baz");
|
||||
v.RenderSection("baz");
|
||||
v.RenderBodyPublic();
|
||||
});
|
||||
|
||||
// Act
|
||||
|
|
@ -157,9 +158,125 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
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)
|
||||
{
|
||||
view.Setup(v => v.ExecuteAsync())
|
||||
|
|
@ -183,5 +300,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Test
|
|||
Writer = new StringWriter()
|
||||
};
|
||||
}
|
||||
|
||||
public abstract class TestableRazorView : RazorView
|
||||
{
|
||||
public HtmlString RenderBodyPublic()
|
||||
{
|
||||
return base.RenderBody();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue