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;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.Html.Abstractions;
|
using Microsoft.AspNet.Html.Abstractions;
|
||||||
using Microsoft.Framework.Internal;
|
using Microsoft.Framework.Internal;
|
||||||
using Microsoft.Framework.WebEncoders;
|
using Microsoft.Framework.WebEncoders;
|
||||||
|
|
@ -14,24 +15,26 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class HelperResult : IHtmlContent
|
public class HelperResult : IHtmlContent
|
||||||
{
|
{
|
||||||
private readonly Action<TextWriter> _action;
|
private readonly Func<TextWriter, Task> _asyncAction;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new instance of <see cref="HelperResult"/>.
|
/// Creates a new instance of <see cref="HelperResult"/>.
|
||||||
/// </summary>
|
/// </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>
|
/// <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>
|
/// <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>
|
/// </summary>
|
||||||
public Action<TextWriter> WriteAction
|
public Func<TextWriter, Task> WriteAction
|
||||||
{
|
{
|
||||||
get { return _action; }
|
get { return _asyncAction; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -41,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
/// <param name="encoder">The <see cref="IHtmlEncoder"/> to encode the content.</param>
|
/// <param name="encoder">The <see cref="IHtmlEncoder"/> to encode the content.</param>
|
||||||
public virtual void WriteTo([NotNull] TextWriter writer, [NotNull] IHtmlEncoder encoder)
|
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>
|
/// <summary>
|
||||||
/// Gets or sets the action invoked to render the body.
|
/// Gets or sets the action invoked to render the body.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Action<TextWriter> RenderBodyDelegate { get; set; }
|
Func<TextWriter, Task> RenderBodyDelegateAsync { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets a flag that determines if the layout of this page is being rendered.
|
/// 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 />
|
/// <inheritdoc />
|
||||||
public Action<TextWriter> RenderBodyDelegate { get; set; }
|
public Func<TextWriter, Task> RenderBodyDelegateAsync { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public bool IsLayoutBeingRendered { get; set; }
|
public bool IsLayoutBeingRendered { get; set; }
|
||||||
|
|
@ -685,14 +685,14 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
|
||||||
protected virtual HelperResult RenderBody()
|
protected virtual HelperResult RenderBody()
|
||||||
{
|
{
|
||||||
if (RenderBodyDelegate == null)
|
if (RenderBodyDelegateAsync == null)
|
||||||
{
|
{
|
||||||
var message = Resources.FormatRazorPage_MethodCannotBeCalled(nameof(RenderBody), Path);
|
var message = Resources.FormatRazorPage_MethodCannotBeCalled(nameof(RenderBody), Path);
|
||||||
throw new InvalidOperationException(message);
|
throw new InvalidOperationException(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
_renderedBody = true;
|
_renderedBody = true;
|
||||||
return new HelperResult(RenderBodyDelegate);
|
return new HelperResult(RenderBodyDelegateAsync);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -860,7 +860,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
throw new InvalidOperationException(Resources.FormatSectionsNotRendered(Path, sectionNames));
|
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.
|
// There are no sections defined, but RenderBody was NOT called.
|
||||||
// If a body was defined, then RenderBody should have been called.
|
// If a body was defined, then RenderBody should have been called.
|
||||||
|
|
|
||||||
|
|
@ -189,7 +189,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
// in the layout.
|
// in the layout.
|
||||||
previousPage.IsLayoutBeingRendered = true;
|
previousPage.IsLayoutBeingRendered = true;
|
||||||
layoutPage.PreviousSectionWriters = previousPage.SectionWriters;
|
layoutPage.PreviousSectionWriters = previousPage.SectionWriters;
|
||||||
layoutPage.RenderBodyDelegate = bodyWriter.CopyTo;
|
layoutPage.RenderBodyDelegateAsync = bodyWriter.CopyToAsync;
|
||||||
bodyWriter = await RenderPageAsync(layoutPage, context, executeViewStart: false);
|
bodyWriter = await RenderPageAsync(layoutPage, context, executeViewStart: false);
|
||||||
|
|
||||||
renderedLayouts.Add(layoutPage);
|
renderedLayouts.Add(layoutPage);
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<div>
|
<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>
|
<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>
|
</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>
|
<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>
|
<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>
|
<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>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -301,7 +301,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
{ "baz", _nullRenderAsyncDelegate }
|
{ "baz", _nullRenderAsyncDelegate }
|
||||||
};
|
};
|
||||||
page.RenderBodyDelegate = CreateBodyAction("body-content");
|
page.RenderBodyDelegateAsync = CreateBodyAction("body-content");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await page.ExecuteAsync();
|
await page.ExecuteAsync();
|
||||||
|
|
@ -325,7 +325,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
{ "baz", _nullRenderAsyncDelegate }
|
{ "baz", _nullRenderAsyncDelegate }
|
||||||
};
|
};
|
||||||
page.RenderBodyDelegate = CreateBodyAction("body-content");
|
page.RenderBodyDelegateAsync = CreateBodyAction("body-content");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await page.ExecuteAsync();
|
await page.ExecuteAsync();
|
||||||
|
|
@ -338,7 +338,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
public async Task RenderSection_ThrowsIfSectionIsRenderedMoreThanOnce()
|
public async Task RenderSection_ThrowsIfSectionIsRenderedMoreThanOnce()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = new HelperResult(action: null);
|
var expected = new HelperResult(asyncAction: null);
|
||||||
var page = CreatePage(v =>
|
var page = CreatePage(v =>
|
||||||
{
|
{
|
||||||
v.Path = "/Views/TestPath/Test.cshtml";
|
v.Path = "/Views/TestPath/Test.cshtml";
|
||||||
|
|
@ -362,7 +362,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
public async Task RenderSectionAsync_ThrowsIfSectionIsRenderedMoreThanOnce()
|
public async Task RenderSectionAsync_ThrowsIfSectionIsRenderedMoreThanOnce()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = new HelperResult(action: null);
|
var expected = new HelperResult(asyncAction: null);
|
||||||
var page = CreatePage(async v =>
|
var page = CreatePage(async v =>
|
||||||
{
|
{
|
||||||
v.Path = "/Views/TestPath/Test.cshtml";
|
v.Path = "/Views/TestPath/Test.cshtml";
|
||||||
|
|
@ -386,7 +386,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
public async Task RenderSectionAsync_ThrowsIfSectionIsRenderedMoreThanOnce_WithSyncMethod()
|
public async Task RenderSectionAsync_ThrowsIfSectionIsRenderedMoreThanOnce_WithSyncMethod()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = new HelperResult(action: null);
|
var expected = new HelperResult(asyncAction: null);
|
||||||
var page = CreatePage(async v =>
|
var page = CreatePage(async v =>
|
||||||
{
|
{
|
||||||
v.Path = "/Views/TestPath/Test.cshtml";
|
v.Path = "/Views/TestPath/Test.cshtml";
|
||||||
|
|
@ -410,7 +410,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
public async Task RenderSectionAsync_ThrowsIfNotInvokedFromLayoutPage()
|
public async Task RenderSectionAsync_ThrowsIfNotInvokedFromLayoutPage()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expected = new HelperResult(action: null);
|
var expected = new HelperResult(asyncAction: null);
|
||||||
var page = CreatePage(async v =>
|
var page = CreatePage(async v =>
|
||||||
{
|
{
|
||||||
v.Path = "/Views/TestPath/Test.cshtml";
|
v.Path = "/Views/TestPath/Test.cshtml";
|
||||||
|
|
@ -434,7 +434,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
});
|
});
|
||||||
page.Path = path;
|
page.Path = path;
|
||||||
page.RenderBodyDelegate = CreateBodyAction("some content");
|
page.RenderBodyDelegateAsync = CreateBodyAction("some content");
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
await page.ExecuteAsync();
|
await page.ExecuteAsync();
|
||||||
|
|
@ -454,7 +454,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
});
|
});
|
||||||
page.Path = path;
|
page.Path = path;
|
||||||
page.RenderBodyDelegate = CreateBodyAction("some content");
|
page.RenderBodyDelegateAsync = CreateBodyAction("some content");
|
||||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||||
{
|
{
|
||||||
{ sectionName, _nullRenderAsyncDelegate }
|
{ sectionName, _nullRenderAsyncDelegate }
|
||||||
|
|
@ -480,7 +480,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
v.RenderSection(sectionA);
|
v.RenderSection(sectionA);
|
||||||
v.RenderSection(sectionB);
|
v.RenderSection(sectionB);
|
||||||
});
|
});
|
||||||
page.RenderBodyDelegate = CreateBodyAction("some content");
|
page.RenderBodyDelegateAsync = CreateBodyAction("some content");
|
||||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||||
{
|
{
|
||||||
{ sectionA, _nullRenderAsyncDelegate },
|
{ sectionA, _nullRenderAsyncDelegate },
|
||||||
|
|
@ -514,7 +514,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
v.Write(v.RenderSection("footer"));
|
v.Write(v.RenderSection("footer"));
|
||||||
v.WriteLiteral("Layout end");
|
v.WriteLiteral("Layout end");
|
||||||
});
|
});
|
||||||
page.RenderBodyDelegate = CreateBodyAction("body content" + Environment.NewLine);
|
page.RenderBodyDelegateAsync = CreateBodyAction("body content" + Environment.NewLine);
|
||||||
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
page.PreviousSectionWriters = new Dictionary<string, RenderAsyncDelegate>
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
|
|
@ -1834,9 +1834,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
new HtmlHelperOptions());
|
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
|
public abstract class TestableRazorPage : RazorPage
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
@using TagHelpersWebSite.Models
|
@using TagHelpersWebSite.Models
|
||||||
|
@using Microsoft.AspNet.Mvc.Razor
|
||||||
|
|
||||||
@model WebsiteContext
|
@model WebsiteContext
|
||||||
|
|
||||||
@{
|
@{
|
||||||
|
|
@ -16,6 +18,15 @@
|
||||||
</style>
|
</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">
|
<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>
|
<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>
|
</div>
|
||||||
|
|
@ -25,6 +36,11 @@
|
||||||
<tag-cloud count="Model.TagsToShow" surround="div" />
|
<tag-cloud count="Model.TagsToShow" surround="div" />
|
||||||
<h3>Current Tag Cloud from ViewComponentHelper:</h3>
|
<h3>Current Tag Cloud from ViewComponentHelper:</h3>
|
||||||
<section bold>@await Component.InvokeAsync("Tags", 15)</section>
|
<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>
|
</div>
|
||||||
|
|
||||||
@section footerContent {
|
@section footerContent {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue