From cc5c0d6cbe5b50fc3939531a5f0f389a3d4842f0 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Mon, 24 Aug 2015 12:57:38 -0700 Subject: [PATCH] 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 --- .../HelperResult.cs | 19 ++++++++------- src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs | 2 +- src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs | 8 +++---- src/Microsoft.AspNet.Mvc.Razor/RazorView.cs | 2 +- .../TagHelpersWebSite.Home.Index.html | 4 ++++ .../RazorPageTest.cs | 24 +++++++++---------- .../TagHelpersWebSite/Views/Home/Index.cshtml | 16 +++++++++++++ 7 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Razor/HelperResult.cs b/src/Microsoft.AspNet.Mvc.Razor/HelperResult.cs index 3055cc0523..9abfa2d9d4 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/HelperResult.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/HelperResult.cs @@ -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 /// public class HelperResult : IHtmlContent { - private readonly Action _action; + private readonly Func _asyncAction; /// /// Creates a new instance of . /// - /// The delegate to invoke when + /// The asynchronous delegate to invoke when /// is called. - public HelperResult([NotNull] Action action) + /// Calls to result in a blocking invocation of + /// . + public HelperResult([NotNull] Func asyncAction) { - _action = action; + _asyncAction = asyncAction; } /// - /// Gets the delegate to invoke when is called. + /// Gets the asynchronous delegate to invoke when is called. /// - public Action WriteAction + public Func WriteAction { - get { return _action; } + get { return _asyncAction; } } /// @@ -41,7 +44,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// The to encode the content. public virtual void WriteTo([NotNull] TextWriter writer, [NotNull] IHtmlEncoder encoder) { - _action(writer); + _asyncAction(writer).GetAwaiter().GetResult(); } } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs index 2539835471..7dfc42c1ff 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// Gets or sets the action invoked to render the body. /// - Action RenderBodyDelegate { get; set; } + Func RenderBodyDelegateAsync { get; set; } /// /// Gets or sets a flag that determines if the layout of this page is being rendered. diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index 5c2b6088e9..bcac22e503 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -126,7 +126,7 @@ namespace Microsoft.AspNet.Mvc.Razor } /// - public Action RenderBodyDelegate { get; set; } + public Func RenderBodyDelegateAsync { get; set; } /// 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); } /// @@ -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. diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs index ac7228fffa..8c271c4f28 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs @@ -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); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html index 0b51dc9e33..23fa35dff9 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html @@ -27,6 +27,8 @@ + +

This website has not been approved yet. Visit www.contoso.com for more information.

@@ -36,6 +38,8 @@
["Lorem","ipsum","dolor","sit","amet","consectetur","adipisicing","elit","sed","do","eiusmod","tempor","incididunt","ut","labore","et","dolore","magna","aliquaUt","enim"]

Current Tag Cloud from ViewComponentHelper:

["Lorem","ipsum","dolor","sit","amet","consectetur","adipisicing","elit","sed","do","eiusmod","tempor","incididunt","ut","labore"]
+

Rendering Template:

+

Tag Cloud from Template:

["Lorem","ipsum","dolor","sit","amet","consectetur","adipisicing","elit","sed","do","eiusmod","tempor","incididunt","ut","labore","et","dolore","magna","aliquaUt","enim"]
diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs index 4d996fdc83..9a6ee2aa81 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs @@ -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 { { 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 { { 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 { { @@ -1834,9 +1834,9 @@ namespace Microsoft.AspNet.Mvc.Razor new HtmlHelperOptions()); } - private static Action CreateBodyAction(string value) + private static Func CreateBodyAction(string value) { - return (writer) => writer.Write(value); + return async (writer) => await writer.WriteAsync(value); } public abstract class TestableRazorPage : RazorPage diff --git a/test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml b/test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml index 6a26f0289c..158dbe506c 100644 --- a/test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml +++ b/test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml @@ -1,4 +1,6 @@ @using TagHelpersWebSite.Models +@using Microsoft.AspNet.Mvc.Razor + @model WebsiteContext @{ @@ -16,6 +18,15 @@ } +@functions { + public void RenderTemplate(string title, Func template) + { + Output.WriteLine("

Rendering Template:

"); + var helperResult = template(title); + helperResult.WriteTo(Output, HtmlEncoder); + } +} +

This website has not been approved yet. Visit www.contoso.com for more information.

@@ -25,6 +36,11 @@

Current Tag Cloud from ViewComponentHelper:

@await Component.InvokeAsync("Tags", 15)
+ @{ + RenderTemplate( + "Tag Cloud from Template: ", + @

@item

); + } @section footerContent {