parent
39376617cc
commit
4ec6da1ed3
|
|
@ -45,10 +45,9 @@
|
|||
<strong>Marketing:</strong> <a href="mailto:Marketing@example.com">Marketing@example.com</a>
|
||||
</address>
|
||||
|
||||
@* Remove the Wait() calls once we add support for async sections *@
|
||||
@{
|
||||
FlushAsync().Wait();
|
||||
Task.Delay(TimeSpan.FromSeconds(1)).Wait();
|
||||
await FlushAsync();
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
body { padding-top: 0px; }
|
||||
}
|
||||
</style>
|
||||
@RenderSection("header", required: false)
|
||||
@await RenderSectionAsync("header", required: false)
|
||||
</head>
|
||||
<body>
|
||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||
|
|
@ -37,6 +37,6 @@
|
|||
<p>© @DateTime.Now.Year - My ASP.NET Application</p>
|
||||
</footer>
|
||||
</div>
|
||||
@RenderSection("footer", required: false)
|
||||
@await RenderSectionAsync("footer", required: false)
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Internal
|
||||
namespace Microsoft.AspNet.Mvc
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility methods for dealing with <see cref="Task"/>.
|
||||
/// </summary>
|
||||
public static class TaskHelper
|
||||
internal static class TaskHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Waits for the task to complete and throws the first faulting exception if the task is faulted.
|
||||
|
|
@ -21,5 +21,18 @@ namespace Microsoft.AspNet.Mvc.Internal
|
|||
{
|
||||
task.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits for the task to complete and throws the first faulting exception if the task is faulted.
|
||||
/// It preserves the original stack trace when throwing the exception.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Invoking this method is equivalent to calling <see cref="Task{TResult}.Result"/> on the
|
||||
/// <paramref name="task"/> if it is not completed.
|
||||
/// </remarks>
|
||||
public static TVal WaitAndThrowIfFaulted<TVal>(Task<TVal> task)
|
||||
{
|
||||
return task.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,7 +22,6 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <summary>
|
||||
/// Gets or sets the action invoked to render the body.
|
||||
/// </summary>
|
||||
// TODO: https://github.com/aspnet/Mvc/issues/845 tracks making this async
|
||||
Action<TextWriter> RenderBodyDelegate { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -53,12 +52,12 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <summary>
|
||||
/// Gets or sets the sections that can be rendered by this page.
|
||||
/// </summary>
|
||||
Dictionary<string, HelperResult> PreviousSectionWriters { get; set; }
|
||||
Dictionary<string, RenderAsyncDelegate> PreviousSectionWriters { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sections that are defined by this page.
|
||||
/// </summary>
|
||||
Dictionary<string, HelperResult> SectionWriters { get; }
|
||||
Dictionary<string, RenderAsyncDelegate> SectionWriters { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Renders the page and writes the output to the <see cref="ViewContext.Writer"/>.
|
||||
|
|
|
|||
|
|
@ -205,17 +205,17 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <summary>
|
||||
/// {0} can only be called from a layout page.
|
||||
/// </summary>
|
||||
internal static string RenderBodyCannotBeCalled
|
||||
internal static string RazorPage_MethodCannotBeCalled
|
||||
{
|
||||
get { return GetString("RenderBodyCannotBeCalled"); }
|
||||
get { return GetString("RazorPage_MethodCannotBeCalled"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// {0} can only be called from a layout page.
|
||||
/// </summary>
|
||||
internal static string FormatRenderBodyCannotBeCalled(object p0)
|
||||
internal static string FormatRazorPage_MethodCannotBeCalled(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("RenderBodyCannotBeCalled"), p0);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("RazorPage_MethodCannotBeCalled"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -251,7 +251,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// {0} has already been called for the section named '{1}'.
|
||||
/// The section named '{0}' has already been rendered.
|
||||
/// </summary>
|
||||
internal static string SectionAlreadyRendered
|
||||
{
|
||||
|
|
@ -259,11 +259,11 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// {0} has already been called for the section named '{1}'.
|
||||
/// The section named '{0}' has already been rendered.
|
||||
/// </summary>
|
||||
internal static string FormatSectionAlreadyRendered(object p0, object p1)
|
||||
internal static string FormatSectionAlreadyRendered(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("SectionAlreadyRendered"), p0, p1);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("SectionAlreadyRendered"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -362,22 +362,6 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("ViewMustBeContextualized"), p0, p1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method '{0}' cannot be invoked by this view.
|
||||
/// </summary>
|
||||
internal static string View_MethodCannotBeCalled
|
||||
{
|
||||
get { return GetString("View_MethodCannotBeCalled"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method '{0}' cannot be invoked by this view.
|
||||
/// </summary>
|
||||
internal static string FormatView_MethodCannotBeCalled(object p0)
|
||||
{
|
||||
return string.Format(CultureInfo.CurrentCulture, GetString("View_MethodCannotBeCalled"), p0);
|
||||
}
|
||||
|
||||
private static string GetString(string name, params string[] formatterNames)
|
||||
{
|
||||
var value = _resourceManager.GetString(name);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
public RazorPage()
|
||||
{
|
||||
SectionWriters = new Dictionary<string, HelperResult>(StringComparer.OrdinalIgnoreCase);
|
||||
SectionWriters = new Dictionary<string, RenderAsyncDelegate>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
_writerScopes = new Stack<TextWriter>();
|
||||
}
|
||||
|
|
@ -105,10 +105,10 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public bool IsLayoutBeingRendered { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, HelperResult> PreviousSectionWriters { get; set; }
|
||||
public Dictionary<string, RenderAsyncDelegate> PreviousSectionWriters { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Dictionary<string, HelperResult> SectionWriters { get; private set; }
|
||||
public Dictionary<string, RenderAsyncDelegate> SectionWriters { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task ExecuteAsync();
|
||||
|
|
@ -215,7 +215,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// </remarks>
|
||||
public virtual void WriteTo(TextWriter writer, object value)
|
||||
{
|
||||
if (value != null)
|
||||
if (value != null && value != HtmlString.Empty)
|
||||
{
|
||||
var helperResult = value as HelperResult;
|
||||
if (helperResult != null)
|
||||
|
|
@ -415,7 +415,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
if (RenderBodyDelegate == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatRenderBodyCannotBeCalled("RenderBody"));
|
||||
var message = Resources.FormatRazorPage_MethodCannotBeCalled(nameof(RenderBody));
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
_renderedBody = true;
|
||||
|
|
@ -424,11 +425,11 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
/// <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="RenderSection(string, bool)"/>.
|
||||
/// <see cref="RenderSection(string)"/> or <see cref="RenderSectionAsync(string, bool)"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the section to create.</param>
|
||||
/// <param name="section">The <see cref="HelperResult"/> to execute when rendering the section.</param>
|
||||
public void DefineSection(string name, HelperResult section)
|
||||
/// <param name="section">The <see cref="RenderAsyncDelegate"/> to execute when rendering the section.</param>
|
||||
public void DefineSection(string name, RenderAsyncDelegate section)
|
||||
{
|
||||
if (SectionWriters.ContainsKey(name))
|
||||
{
|
||||
|
|
@ -439,33 +440,84 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
public bool IsSectionDefined([NotNull] string name)
|
||||
{
|
||||
EnsureMethodCanBeInvoked("IsSectionDefined");
|
||||
EnsureMethodCanBeInvoked(nameof(IsSectionDefined));
|
||||
return PreviousSectionWriters.ContainsKey(name);
|
||||
}
|
||||
|
||||
public HelperResult RenderSection([NotNull] string name)
|
||||
/// <summary>
|
||||
/// In layout pages, renders the content of the section named <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the section to render.</param>
|
||||
/// <returns>Returns a HtmlString that contains the rendered HTML.</returns>
|
||||
public HtmlString RenderSection([NotNull] string name)
|
||||
{
|
||||
return RenderSection(name, required: true);
|
||||
}
|
||||
|
||||
public HelperResult RenderSection([NotNull] string name, bool required)
|
||||
/// <summary>
|
||||
/// In layout pages, renders the content of the section named <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The section to render.</param>
|
||||
/// <param name="required">Indicates if this section must be rendered.</param>
|
||||
/// <returns>Returns a HtmlString that contains the rendered HTML.</returns>
|
||||
public HtmlString RenderSection([NotNull] string name, bool required)
|
||||
{
|
||||
EnsureMethodCanBeInvoked("RenderSection");
|
||||
if (_renderedSections.Contains(name))
|
||||
EnsureMethodCanBeInvoked(nameof(RenderSection));
|
||||
|
||||
var task = RenderSectionAsyncCore(name, required);
|
||||
return TaskHelper.WaitAndThrowIfFaulted(task);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In layout pages, asynchronously renders the content of the section named <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The section to render.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> that on completion returns a <see cref="HtmlString"/> containing
|
||||
/// the rendered HTML.</returns>
|
||||
public Task<HtmlString> RenderSectionAsync([NotNull] string name)
|
||||
{
|
||||
return RenderSectionAsync(name, required: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// In layout pages, asynchronously renders the content of the section named <paramref name="name"/>.
|
||||
/// </summary>
|
||||
/// <param name="name">The section to render.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> that on completion returns a <see cref="HtmlString"/> containing
|
||||
/// the rendered HTML.</returns>
|
||||
/// <exception cref="InvalidOperationException">if <paramref name="required"/> is <c>true</c> and the section
|
||||
/// was not registered using the <c>@section</c> in the Razor page.</exception>
|
||||
public async Task<HtmlString> RenderSectionAsync([NotNull] string name, bool required)
|
||||
{
|
||||
EnsureMethodCanBeInvoked(nameof(RenderSectionAsync));
|
||||
return await RenderSectionAsyncCore(name, required);
|
||||
}
|
||||
|
||||
private async Task<HtmlString> RenderSectionAsyncCore(string sectionName, bool required)
|
||||
{
|
||||
if (_renderedSections.Contains(sectionName))
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatSectionAlreadyRendered("RenderSection", name));
|
||||
var message = Resources.FormatSectionAlreadyRendered(sectionName);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
HelperResult action;
|
||||
if (PreviousSectionWriters.TryGetValue(name, out action))
|
||||
RenderAsyncDelegate renderDelegate;
|
||||
if (PreviousSectionWriters.TryGetValue(sectionName, out renderDelegate))
|
||||
{
|
||||
_renderedSections.Add(name);
|
||||
return action;
|
||||
_renderedSections.Add(sectionName);
|
||||
|
||||
using (var writer = new StringCollectionTextWriter(Output.Encoding))
|
||||
{
|
||||
await renderDelegate(writer);
|
||||
|
||||
// Returning a disposed StringCollectionTextWriter is safe.
|
||||
return new HtmlString(writer);
|
||||
}
|
||||
}
|
||||
else if (required)
|
||||
{
|
||||
// If the section is not found, and it is not optional, throw an error.
|
||||
throw new InvalidOperationException(Resources.FormatSectionNotDefined(name));
|
||||
throw new InvalidOperationException(Resources.FormatSectionNotDefined(sectionName));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -518,7 +570,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
if (RenderBodyDelegate != null && !_renderedBody)
|
||||
{
|
||||
// If a body was defined, then RenderBody should have been called.
|
||||
throw new InvalidOperationException(Resources.FormatRenderBodyNotCalled("RenderBody"));
|
||||
var message = Resources.FormatRenderBodyNotCalled(nameof(RenderBody));
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -536,7 +589,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
if (PreviousSectionWriters == null)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.FormatView_MethodCannotBeCalled(methodName));
|
||||
throw new InvalidOperationException(Resources.FormatRazorPage_MethodCannotBeCalled(methodName));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Razor
|
||||
{
|
||||
public delegate Task RenderAsyncDelegate(TextWriter writer);
|
||||
}
|
||||
|
|
@ -153,7 +153,7 @@
|
|||
<data name="RazorPage_NullModelMetadata" xml:space="preserve">
|
||||
<value>The {0} was unable to provide metadata for expression '{1}'.</value>
|
||||
</data>
|
||||
<data name="RenderBodyCannotBeCalled" xml:space="preserve">
|
||||
<data name="RazorPage_MethodCannotBeCalled" xml:space="preserve">
|
||||
<value>{0} can only be called from a layout page.</value>
|
||||
</data>
|
||||
<data name="RenderBodyNotCalled" xml:space="preserve">
|
||||
|
|
@ -163,7 +163,7 @@
|
|||
<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>
|
||||
<value>The section named '{0}' has already been rendered.</value>
|
||||
</data>
|
||||
<data name="SectionNotDefined" xml:space="preserve">
|
||||
<value>Section '{0}' is not defined.</value>
|
||||
|
|
@ -183,7 +183,4 @@
|
|||
<data name="ViewMustBeContextualized" xml:space="preserve">
|
||||
<value>The '{0}' method must be called before '{1}' can be invoked.</value>
|
||||
</data>
|
||||
<data name="View_MethodCannotBeCalled" xml:space="preserve">
|
||||
<value>The method '{0}' cannot be invoked by this view.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
@ -17,6 +17,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
public class RazorPageTest
|
||||
{
|
||||
private readonly RenderAsyncDelegate _nullRenderAsyncDelegate = async writer => { };
|
||||
|
||||
[Fact]
|
||||
public async Task WritingScopesRedirectContentWrittenToViewContextWriter()
|
||||
{
|
||||
|
|
@ -135,8 +137,8 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
var viewContext = CreateViewContext();
|
||||
var page = CreatePage(v =>
|
||||
{
|
||||
v.DefineSection("qux", new HelperResult(action: null));
|
||||
v.DefineSection("qux", new HelperResult(action: null));
|
||||
v.DefineSection("qux", _nullRenderAsyncDelegate);
|
||||
v.DefineSection("qux", _nullRenderAsyncDelegate);
|
||||
});
|
||||
|
||||
// Act
|
||||
|
|
@ -151,23 +153,22 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public async Task RenderSection_RendersSectionFromPreviousPage()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new HelperResult(action: null);
|
||||
var expected = "Hello world";
|
||||
var viewContext = CreateViewContext();
|
||||
HelperResult actual = null;
|
||||
var page = CreatePage(v =>
|
||||
{
|
||||
actual = v.RenderSection("bar");
|
||||
v.Write(v.RenderSection("bar"));
|
||||
});
|
||||
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ "bar", expected }
|
||||
{ "bar", writer => writer.WriteAsync(expected) }
|
||||
};
|
||||
|
||||
// Act
|
||||
await page.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Same(actual, expected);
|
||||
Assert.Equal(expected, page.RenderedContent);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -184,7 +185,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
await page.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The method 'RenderSection' cannot be invoked by this view.",
|
||||
Assert.Equal("RenderSection can only be called from a layout page.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
|
|
@ -192,14 +193,13 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public async Task RenderSection_ThrowsIfRequiredSectionIsNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new HelperResult(action: null);
|
||||
var page = CreatePage(v =>
|
||||
{
|
||||
v.RenderSection("bar");
|
||||
});
|
||||
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ "baz", expected }
|
||||
{ "baz", _nullRenderAsyncDelegate }
|
||||
};
|
||||
|
||||
// Act
|
||||
|
|
@ -217,7 +217,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
// Act and Assert
|
||||
ExceptionAssert.Throws<InvalidOperationException>(() => page.IsSectionDefined("foo"),
|
||||
"The method 'IsSectionDefined' cannot be invoked by this view.");
|
||||
"IsSectionDefined can only be called from a layout page.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -231,9 +231,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
v.RenderSection("baz");
|
||||
v.RenderBodyPublic();
|
||||
});
|
||||
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ "baz", new HelperResult(writer => { }) }
|
||||
{ "baz", _nullRenderAsyncDelegate }
|
||||
};
|
||||
page.RenderBodyDelegate = CreateBodyAction("body-content");
|
||||
|
||||
|
|
@ -255,9 +255,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
v.RenderSection("baz");
|
||||
v.RenderBodyPublic();
|
||||
});
|
||||
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ "baz", new HelperResult(writer => { }) }
|
||||
{ "baz", _nullRenderAsyncDelegate }
|
||||
};
|
||||
page.RenderBodyDelegate = CreateBodyAction("body-content");
|
||||
|
||||
|
|
@ -278,32 +278,92 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
v.RenderSection("header");
|
||||
v.RenderSection("header");
|
||||
});
|
||||
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ "header", new HelperResult(writer => { }) }
|
||||
{ "header", _nullRenderAsyncDelegate }
|
||||
};
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(page.ExecuteAsync);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("RenderSection has already been called for the section named 'header'.", ex.Message);
|
||||
Assert.Equal("The section named 'header' has already been rendered.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RenderSectionAsync_ThrowsIfSectionIsRenderedMoreThanOnce()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new HelperResult(action: null);
|
||||
var page = CreatePage(async v =>
|
||||
{
|
||||
await v.RenderSectionAsync("header");
|
||||
await v.RenderSectionAsync("header");
|
||||
});
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ "header", _nullRenderAsyncDelegate }
|
||||
};
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(page.ExecuteAsync);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The section named 'header' has already been rendered.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RenderSectionAsync_ThrowsIfSectionIsRenderedMoreThanOnce_WithSyncMethod()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new HelperResult(action: null);
|
||||
var page = CreatePage(async v =>
|
||||
{
|
||||
v.RenderSection("header");
|
||||
await v.RenderSectionAsync("header");
|
||||
});
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ "header", _nullRenderAsyncDelegate }
|
||||
};
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(page.ExecuteAsync);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("The section named 'header' has already been rendered.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RenderSectionAsync_ThrowsIfNotInvokedFromLayoutPage()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new HelperResult(action: null);
|
||||
var page = CreatePage(async v =>
|
||||
{
|
||||
await v.RenderSectionAsync("header");
|
||||
});
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(page.ExecuteAsync);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("RenderSectionAsync can only be called from a layout page.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EnsureBodyAndSectionsWereRendered_ThrowsIfDefinedSectionIsNotRendered()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new HelperResult(action: null);
|
||||
var page = CreatePage(v =>
|
||||
{
|
||||
v.RenderSection("sectionA");
|
||||
});
|
||||
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ "header", expected },
|
||||
{ "footer", expected },
|
||||
{ "sectionA", expected },
|
||||
{ "header", _nullRenderAsyncDelegate },
|
||||
{ "footer", _nullRenderAsyncDelegate },
|
||||
{ "sectionA", _nullRenderAsyncDelegate },
|
||||
};
|
||||
|
||||
// Act
|
||||
|
|
@ -337,35 +397,38 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public async Task ExecuteAsync_RendersSectionsAndBody()
|
||||
{
|
||||
// Arrange
|
||||
var expected = @"Layout start
|
||||
Header section
|
||||
body content
|
||||
Footer section
|
||||
Layout end
|
||||
";
|
||||
var page = CreatePage(v =>
|
||||
var expected = string.Join(Environment.NewLine,
|
||||
"Layout start",
|
||||
"Header section",
|
||||
"Async Header section",
|
||||
"body content",
|
||||
"Async Footer section",
|
||||
"Footer section",
|
||||
"Layout end");
|
||||
var page = CreatePage(async v =>
|
||||
{
|
||||
v.WriteLiteral("Layout start" + Environment.NewLine);
|
||||
v.Write(v.RenderSection("header"));
|
||||
v.Write(await v.RenderSectionAsync("async-header"));
|
||||
v.Write(v.RenderBodyPublic());
|
||||
v.Write(await v.RenderSectionAsync("async-footer"));
|
||||
v.Write(v.RenderSection("footer"));
|
||||
v.WriteLiteral("Layout end" + Environment.NewLine);
|
||||
|
||||
v.WriteLiteral("Layout end");
|
||||
});
|
||||
page.RenderBodyDelegate = CreateBodyAction("body content" + Environment.NewLine);
|
||||
page.PreviousSectionWriters = new Dictionary<string, HelperResult>
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{
|
||||
"footer", new HelperResult(writer =>
|
||||
{
|
||||
writer.WriteLine("Footer section");
|
||||
})
|
||||
"footer", writer => writer.WriteLineAsync("Footer section")
|
||||
},
|
||||
{
|
||||
"header", new HelperResult(writer =>
|
||||
{
|
||||
writer.WriteLine("Header section");
|
||||
})
|
||||
"header", writer => writer.WriteLineAsync("Header section")
|
||||
},
|
||||
{
|
||||
"async-header", writer => writer.WriteLineAsync("Async Header section")
|
||||
},
|
||||
{
|
||||
"async-footer", writer => writer.WriteLineAsync("Async Footer section")
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -373,7 +436,7 @@ Layout end
|
|||
await page.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
var actual = ((StringWriter)page.Output).ToString();
|
||||
var actual = page.RenderedContent;
|
||||
Assert.Equal(expected, actual);
|
||||
}
|
||||
|
||||
|
|
@ -399,7 +462,7 @@ Layout end
|
|||
await page.ExecuteAsync();
|
||||
|
||||
// Assert
|
||||
var actual = ((StringWriter)page.Output).ToString();
|
||||
var actual = page.RenderedContent;
|
||||
Assert.Equal(expected, actual);
|
||||
helper.Verify();
|
||||
}
|
||||
|
|
@ -410,9 +473,9 @@ Layout end
|
|||
// Arrange
|
||||
var writer = new Mock<TextWriter>();
|
||||
var context = CreateViewContext(writer.Object);
|
||||
var page = CreatePage(p =>
|
||||
var page = CreatePage(async p =>
|
||||
{
|
||||
p.FlushAsync().Wait();
|
||||
await p.FlushAsync();
|
||||
}, context);
|
||||
|
||||
// Act
|
||||
|
|
@ -429,10 +492,10 @@ Layout end
|
|||
var expected = @"A layout page cannot be rendered after 'FlushAsync' has been invoked.";
|
||||
var writer = new Mock<TextWriter>();
|
||||
var context = CreateViewContext(writer.Object);
|
||||
var page = CreatePage(p =>
|
||||
var page = CreatePage(async p =>
|
||||
{
|
||||
p.Layout = "foo";
|
||||
p.FlushAsync().Wait();
|
||||
await p.FlushAsync();
|
||||
}, context);
|
||||
|
||||
// Act and Assert
|
||||
|
|
@ -449,10 +512,10 @@ Layout end
|
|||
var page = CreatePage(p =>
|
||||
{
|
||||
p.Layout = "bar";
|
||||
p.DefineSection("test-section", new HelperResult(_ =>
|
||||
p.DefineSection("test-section", async _ =>
|
||||
{
|
||||
p.FlushAsync().Wait();
|
||||
}));
|
||||
await p.FlushAsync();
|
||||
});
|
||||
}, context);
|
||||
|
||||
// Act
|
||||
|
|
@ -460,7 +523,8 @@ Layout end
|
|||
page.IsLayoutBeingRendered = true;
|
||||
|
||||
// Assert
|
||||
Assert.DoesNotThrow(() => page.SectionWriters["test-section"].WriteTo(TextWriter.Null));
|
||||
var renderAsyncDelegate = page.SectionWriters["test-section"];
|
||||
await Assert.DoesNotThrowAsync(() => renderAsyncDelegate(TextWriter.Null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -553,14 +617,27 @@ Layout end
|
|||
|
||||
private static TestableRazorPage CreatePage(Action<TestableRazorPage> executeAction,
|
||||
ViewContext context = null)
|
||||
{
|
||||
return CreatePage(page =>
|
||||
{
|
||||
executeAction(page);
|
||||
return Task.FromResult(0);
|
||||
}, context);
|
||||
}
|
||||
|
||||
|
||||
private static TestableRazorPage CreatePage(Func<TestableRazorPage, Task> executeAction,
|
||||
ViewContext context = null)
|
||||
{
|
||||
context = context ?? CreateViewContext();
|
||||
var view = new Mock<TestableRazorPage> { CallBase = true };
|
||||
if (executeAction != null)
|
||||
{
|
||||
view.Setup(v => v.ExecuteAsync())
|
||||
.Callback(() => executeAction(view.Object))
|
||||
.Returns(Task.FromResult(0));
|
||||
.Returns(() =>
|
||||
{
|
||||
return executeAction(view.Object);
|
||||
});
|
||||
}
|
||||
|
||||
view.Object.ViewContext = context;
|
||||
|
|
@ -585,6 +662,15 @@ Layout end
|
|||
|
||||
public abstract class TestableRazorPage : RazorPage
|
||||
{
|
||||
public string RenderedContent
|
||||
{
|
||||
get
|
||||
{
|
||||
var writer = Assert.IsType<StringWriter>(Output);
|
||||
return writer.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
public HelperResult RenderBodyPublic()
|
||||
{
|
||||
return base.RenderBody();
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public class RazorViewTest
|
||||
{
|
||||
private const string LayoutPath = "~/Shared/_Layout.cshtml";
|
||||
private readonly RenderAsyncDelegate _nullRenderAsyncDelegate = async writer => { };
|
||||
|
||||
[Fact]
|
||||
public async Task RenderAsync_ThrowsIfContextualizeHasNotBeenInvoked()
|
||||
|
|
@ -264,14 +265,14 @@ foot-content";
|
|||
{
|
||||
v.WriteLiteral("body-content");
|
||||
v.Layout = LayoutPath;
|
||||
v.DefineSection("head", new HelperResult(writer =>
|
||||
v.DefineSection("head", async writer =>
|
||||
{
|
||||
writer.Write("head-content");
|
||||
}));
|
||||
v.DefineSection("foot", new HelperResult(writer =>
|
||||
await writer.WriteAsync("head-content");
|
||||
});
|
||||
v.DefineSection("foot", async writer =>
|
||||
{
|
||||
writer.Write("foot-content");
|
||||
}));
|
||||
await writer.WriteAsync("foot-content");
|
||||
});
|
||||
});
|
||||
var layout = new TestableRazorPage(v =>
|
||||
{
|
||||
|
|
@ -312,9 +313,9 @@ foot-content";
|
|||
// Arrange
|
||||
var page = new TestableRazorPage(v =>
|
||||
{
|
||||
v.DefineSection("head", new HelperResult(writer => { }));
|
||||
v.DefineSection("head", _nullRenderAsyncDelegate);
|
||||
v.Layout = LayoutPath;
|
||||
v.DefineSection("foot", new HelperResult(writer => { }));
|
||||
v.DefineSection("foot", _nullRenderAsyncDelegate);
|
||||
});
|
||||
var layout = new TestableRazorPage(v =>
|
||||
{
|
||||
|
|
@ -374,10 +375,10 @@ body-content";
|
|||
|
||||
var page = new TestableRazorPage(v =>
|
||||
{
|
||||
v.DefineSection("foo", new HelperResult(writer =>
|
||||
v.DefineSection("foo", async writer =>
|
||||
{
|
||||
writer.WriteLine("foo-content");
|
||||
}));
|
||||
await writer.WriteLineAsync("foo-content");
|
||||
});
|
||||
v.Layout = "~/Shared/Layout1.cshtml";
|
||||
v.WriteLiteral("body-content");
|
||||
});
|
||||
|
|
@ -385,10 +386,7 @@ body-content";
|
|||
{
|
||||
v.Write("layout-1" + Environment.NewLine);
|
||||
v.Write(v.RenderSection("foo"));
|
||||
v.DefineSection("bar", new HelperResult(writer =>
|
||||
{
|
||||
writer.WriteLine("bar-content");
|
||||
}));
|
||||
v.DefineSection("bar", writer => writer.WriteLineAsync("bar-content"));
|
||||
v.RenderBodyPublic();
|
||||
v.Layout = "~/Shared/Layout2.cshtml";
|
||||
});
|
||||
|
|
@ -431,12 +429,12 @@ section-content-2";
|
|||
{
|
||||
v.Layout = "layout-1";
|
||||
v.WriteLiteral("body content" + Environment.NewLine);
|
||||
v.DefineSection("foo", new HelperResult(_ =>
|
||||
v.DefineSection("foo", async _ =>
|
||||
{
|
||||
v.WriteLiteral("section-content-1" + Environment.NewLine);
|
||||
v.FlushAsync().Wait();
|
||||
await v.FlushAsync();
|
||||
v.WriteLiteral("section-content-2");
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
var layout1 = new TestableRazorPage(v =>
|
||||
|
|
@ -475,12 +473,12 @@ section-content-2";
|
|||
var page = new TestableRazorPage(v =>
|
||||
{
|
||||
v.Layout = "layout-1";
|
||||
v.DefineSection("foo", new HelperResult(_ =>
|
||||
v.DefineSection("foo", async _ =>
|
||||
{
|
||||
v.WriteLiteral("section-content-1" + Environment.NewLine);
|
||||
v.FlushAsync().Wait();
|
||||
await v.FlushAsync();
|
||||
v.WriteLiteral("section-content-2");
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
var layout1 = new TestableRazorPage(v =>
|
||||
|
|
@ -538,11 +536,11 @@ section-content-2";
|
|||
var expected = @"A layout page cannot be rendered after 'FlushAsync' has been invoked.";
|
||||
var page = new TestableRazorPage(v =>
|
||||
{
|
||||
v.DefineSection("foo", new HelperResult(writer =>
|
||||
v.DefineSection("foo", async writer =>
|
||||
{
|
||||
writer.WriteLine("foo-content");
|
||||
v.FlushAsync().Wait();
|
||||
}));
|
||||
await v.FlushAsync();
|
||||
});
|
||||
v.Layout = "~/Shared/Layout1.cshtml";
|
||||
v.WriteLiteral("body-content");
|
||||
});
|
||||
|
|
@ -550,10 +548,7 @@ section-content-2";
|
|||
{
|
||||
v.Write("layout-1" + Environment.NewLine);
|
||||
v.Write(v.RenderSection("foo"));
|
||||
v.DefineSection("bar", new HelperResult(writer =>
|
||||
{
|
||||
writer.WriteLine("bar-content");
|
||||
}));
|
||||
v.DefineSection("bar", writer => writer.WriteLineAsync("bar-content"));
|
||||
v.RenderBodyPublic();
|
||||
v.Layout = "~/Shared/Layout2.cshtml";
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ RenderBody content
|
|||
@section content
|
||||
{
|
||||
@{
|
||||
FlushAsync().Wait();
|
||||
await FlushAsync();
|
||||
WaitService.WaitForClient();
|
||||
}
|
||||
<span>Content that takes time to produce</span>
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ RenderBody content
|
|||
@section content
|
||||
{
|
||||
@{
|
||||
FlushAsync().Wait();
|
||||
await FlushAsync();
|
||||
WaitService.WaitForClient();
|
||||
}
|
||||
@Component.InvokeAsync("ComponentThatSetsTitle").Result
|
||||
@await Component.InvokeAsync("ComponentThatSetsTitle")
|
||||
<span>Content that takes time to produce</span>
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@
|
|||
WaitService.WaitForClient();
|
||||
}
|
||||
@RenderBody()
|
||||
@RenderSection("content")
|
||||
@await RenderSectionAsync("content")
|
||||
@{
|
||||
WaitService.NotifyClient();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
WaitService.WaitForClient();
|
||||
}
|
||||
@await Html.PartialAsync("_PartialThatSetsTitle")
|
||||
@RenderSection("content")
|
||||
@await RenderSectionAsync("content")
|
||||
@{
|
||||
WaitService.NotifyClient();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue