Update `HelperResult` to take in an async func.
- The corresponding Razor change results in `HelperResult`s being rendered with async lambdas. - This change enables `TagHelper`s and other async code to exist inside of `HelperResult` blocks. - Added test to validate Templates (they generate `HelperResult`s) can utilize `TagHelper`s correctly. - Rename `RazorPage`s `RenderBodyDelegate` to `RenderBodyDelegateAsync`. aspnet/Razor#494
This commit is contained in:
parent
3041dee86d
commit
cc5c0d6cbe
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Html.Abstractions;
|
||||
using Microsoft.Framework.Internal;
|
||||
using Microsoft.Framework.WebEncoders;
|
||||
|
|
@ -14,24 +15,26 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// </summary>
|
||||
public class HelperResult : IHtmlContent
|
||||
{
|
||||
private readonly Action<TextWriter> _action;
|
||||
private readonly Func<TextWriter, Task> _asyncAction;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="HelperResult"/>.
|
||||
/// </summary>
|
||||
/// <param name="action">The delegate to invoke when
|
||||
/// <param name="asyncAction">The asynchronous delegate to invoke when
|
||||
/// <see cref="WriteTo(TextWriter, IHtmlEncoder)"/> is called.</param>
|
||||
public HelperResult([NotNull] Action<TextWriter> action)
|
||||
/// <remarks>Calls to <see cref="WriteTo(TextWriter, IHtmlEncoder)"/> result in a blocking invocation of
|
||||
/// <paramref name="asyncAction"/>.</remarks>
|
||||
public HelperResult([NotNull] Func<TextWriter, Task> asyncAction)
|
||||
{
|
||||
_action = action;
|
||||
_asyncAction = asyncAction;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the delegate to invoke when <see cref="WriteTo(TextWriter, IHtmlEncoder)"/> is called.
|
||||
/// Gets the asynchronous delegate to invoke when <see cref="WriteTo(TextWriter, IHtmlEncoder)"/> is called.
|
||||
/// </summary>
|
||||
public Action<TextWriter> WriteAction
|
||||
public Func<TextWriter, Task> WriteAction
|
||||
{
|
||||
get { return _action; }
|
||||
get { return _asyncAction; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -41,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <param name="encoder">The <see cref="IHtmlEncoder"/> to encode the content.</param>
|
||||
public virtual void WriteTo([NotNull] TextWriter writer, [NotNull] IHtmlEncoder encoder)
|
||||
{
|
||||
_action(writer);
|
||||
_asyncAction(writer).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <summary>
|
||||
/// Gets or sets the action invoked to render the body.
|
||||
/// </summary>
|
||||
Action<TextWriter> RenderBodyDelegate { get; set; }
|
||||
Func<TextWriter, Task> RenderBodyDelegateAsync { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a flag that determines if the layout of this page is being rendered.
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Action<TextWriter> RenderBodyDelegate { get; set; }
|
||||
public Func<TextWriter, Task> RenderBodyDelegateAsync { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsLayoutBeingRendered { get; set; }
|
||||
|
|
@ -685,14 +685,14 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
|
||||
protected virtual HelperResult RenderBody()
|
||||
{
|
||||
if (RenderBodyDelegate == null)
|
||||
if (RenderBodyDelegateAsync == null)
|
||||
{
|
||||
var message = Resources.FormatRazorPage_MethodCannotBeCalled(nameof(RenderBody), Path);
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
_renderedBody = true;
|
||||
return new HelperResult(RenderBodyDelegate);
|
||||
return new HelperResult(RenderBodyDelegateAsync);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -860,7 +860,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
throw new InvalidOperationException(Resources.FormatSectionsNotRendered(Path, sectionNames));
|
||||
}
|
||||
}
|
||||
else if (RenderBodyDelegate != null && !_renderedBody)
|
||||
else if (RenderBodyDelegateAsync != null && !_renderedBody)
|
||||
{
|
||||
// There are no sections defined, but RenderBody was NOT called.
|
||||
// If a body was defined, then RenderBody should have been called.
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
// in the layout.
|
||||
previousPage.IsLayoutBeingRendered = true;
|
||||
layoutPage.PreviousSectionWriters = previousPage.SectionWriters;
|
||||
layoutPage.RenderBodyDelegate = bodyWriter.CopyTo;
|
||||
layoutPage.RenderBodyDelegateAsync = bodyWriter.CopyToAsync;
|
||||
bodyWriter = await RenderPageAsync(layoutPage, context, executeViewStart: false);
|
||||
|
||||
renderedLayouts.Add(layoutPage);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,8 @@
|
|||
|
||||
|
||||
|
||||
|
||||
|
||||
<div>
|
||||
<p>This website has <em><strong style="font-size: 1.25em;text-decoration: underline;">not</strong></em> been approved yet. Visit <strong><a target="_blank" href="http://www.contoso.com">www.contoso.com</a></strong> for <strong>more</strong> information.</p>
|
||||
</div>
|
||||
|
|
@ -36,6 +38,8 @@
|
|||
<div>["Lorem","ipsum","dolor","sit","amet","consectetur","adipisicing","elit","sed","do","eiusmod","tempor","incididunt","ut","labore","et","dolore","magna","aliquaUt","enim"]</div>
|
||||
<h3 style="font-family: cursive;">Current Tag Cloud from ViewComponentHelper:</h3>
|
||||
<section><b>["Lorem","ipsum","dolor","sit","amet","consectetur","adipisicing","elit","sed","do","eiusmod","tempor","incididunt","ut","labore"]</b></section>
|
||||
<br /><p><em>Rendering Template:</em></p>
|
||||
<div><h3 style="font-family: cursive;">Tag Cloud from Template: </h3>["Lorem","ipsum","dolor","sit","amet","consectetur","adipisicing","elit","sed","do","eiusmod","tempor","incididunt","ut","labore","et","dolore","magna","aliquaUt","enim"]</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
{ "baz", _nullRenderAsyncDelegate }
|
||||
};
|
||||
page.RenderBodyDelegate = CreateBodyAction("body-content");
|
||||
page.RenderBodyDelegateAsync = CreateBodyAction("body-content");
|
||||
|
||||
// Act
|
||||
await page.ExecuteAsync();
|
||||
|
|
@ -325,7 +325,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
{ "baz", _nullRenderAsyncDelegate }
|
||||
};
|
||||
page.RenderBodyDelegate = CreateBodyAction("body-content");
|
||||
page.RenderBodyDelegateAsync = CreateBodyAction("body-content");
|
||||
|
||||
// Act
|
||||
await page.ExecuteAsync();
|
||||
|
|
@ -338,7 +338,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public async Task RenderSection_ThrowsIfSectionIsRenderedMoreThanOnce()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new HelperResult(action: null);
|
||||
var expected = new HelperResult(asyncAction: null);
|
||||
var page = CreatePage(v =>
|
||||
{
|
||||
v.Path = "/Views/TestPath/Test.cshtml";
|
||||
|
|
@ -362,7 +362,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public async Task RenderSectionAsync_ThrowsIfSectionIsRenderedMoreThanOnce()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new HelperResult(action: null);
|
||||
var expected = new HelperResult(asyncAction: null);
|
||||
var page = CreatePage(async v =>
|
||||
{
|
||||
v.Path = "/Views/TestPath/Test.cshtml";
|
||||
|
|
@ -386,7 +386,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public async Task RenderSectionAsync_ThrowsIfSectionIsRenderedMoreThanOnce_WithSyncMethod()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new HelperResult(action: null);
|
||||
var expected = new HelperResult(asyncAction: null);
|
||||
var page = CreatePage(async v =>
|
||||
{
|
||||
v.Path = "/Views/TestPath/Test.cshtml";
|
||||
|
|
@ -410,7 +410,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public async Task RenderSectionAsync_ThrowsIfNotInvokedFromLayoutPage()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new HelperResult(action: null);
|
||||
var expected = new HelperResult(asyncAction: null);
|
||||
var page = CreatePage(async v =>
|
||||
{
|
||||
v.Path = "/Views/TestPath/Test.cshtml";
|
||||
|
|
@ -434,7 +434,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
});
|
||||
page.Path = path;
|
||||
page.RenderBodyDelegate = CreateBodyAction("some content");
|
||||
page.RenderBodyDelegateAsync = CreateBodyAction("some content");
|
||||
|
||||
// Act
|
||||
await page.ExecuteAsync();
|
||||
|
|
@ -454,7 +454,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
});
|
||||
page.Path = path;
|
||||
page.RenderBodyDelegate = CreateBodyAction("some content");
|
||||
page.RenderBodyDelegateAsync = CreateBodyAction("some content");
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ sectionName, _nullRenderAsyncDelegate }
|
||||
|
|
@ -480,7 +480,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
v.RenderSection(sectionA);
|
||||
v.RenderSection(sectionB);
|
||||
});
|
||||
page.RenderBodyDelegate = CreateBodyAction("some content");
|
||||
page.RenderBodyDelegateAsync = CreateBodyAction("some content");
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{ sectionA, _nullRenderAsyncDelegate },
|
||||
|
|
@ -514,7 +514,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
v.Write(v.RenderSection("footer"));
|
||||
v.WriteLiteral("Layout end");
|
||||
});
|
||||
page.RenderBodyDelegate = CreateBodyAction("body content" + Environment.NewLine);
|
||||
page.RenderBodyDelegateAsync = CreateBodyAction("body content" + Environment.NewLine);
|
||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||
{
|
||||
{
|
||||
|
|
@ -1834,9 +1834,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
new HtmlHelperOptions());
|
||||
}
|
||||
|
||||
private static Action<TextWriter> CreateBodyAction(string value)
|
||||
private static Func<TextWriter, Task> CreateBodyAction(string value)
|
||||
{
|
||||
return (writer) => writer.Write(value);
|
||||
return async (writer) => await writer.WriteAsync(value);
|
||||
}
|
||||
|
||||
public abstract class TestableRazorPage : RazorPage
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
@using TagHelpersWebSite.Models
|
||||
@using Microsoft.AspNet.Mvc.Razor
|
||||
|
||||
@model WebsiteContext
|
||||
|
||||
@{
|
||||
|
|
@ -16,6 +18,15 @@
|
|||
</style>
|
||||
}
|
||||
|
||||
@functions {
|
||||
public void RenderTemplate(string title, Func<string, HelperResult> template)
|
||||
{
|
||||
Output.WriteLine("<br /><p><em>Rendering Template:</em></p>");
|
||||
var helperResult = template(title);
|
||||
helperResult.WriteTo(Output, HtmlEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
<div condition="!Model.Approved">
|
||||
<p>This website has <strong surround="em">not</strong> been approved yet. Visit www.contoso.com for <strong make-pretty="false">more</strong> information.</p>
|
||||
</div>
|
||||
|
|
@ -25,6 +36,11 @@
|
|||
<tag-cloud count="Model.TagsToShow" surround="div" />
|
||||
<h3>Current Tag Cloud from ViewComponentHelper:</h3>
|
||||
<section bold>@await Component.InvokeAsync("Tags", 15)</section>
|
||||
@{
|
||||
RenderTemplate(
|
||||
"Tag Cloud from Template: ",
|
||||
@<div condition="true"><h3>@item</h3><tag-cloud count="Model.TagsToShow"></tag-cloud></div>);
|
||||
}
|
||||
</div>
|
||||
|
||||
@section footerContent {
|
||||
|
|
|
|||
Loading…
Reference in New Issue