diff --git a/samples/MvcSample.Web/Views/Home/FlushPoint.cshtml b/samples/MvcSample.Web/Views/Home/FlushPoint.cshtml index 1f79342528..4e95f0aabf 100644 --- a/samples/MvcSample.Web/Views/Home/FlushPoint.cshtml +++ b/samples/MvcSample.Web/Views/Home/FlushPoint.cshtml @@ -45,10 +45,9 @@ Marketing: Marketing@example.com -@* 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)); }
diff --git a/samples/MvcSample.Web/Views/Shared/_Layout.cshtml b/samples/MvcSample.Web/Views/Shared/_Layout.cshtml index 767b0b972e..2e843399cb 100644 --- a/samples/MvcSample.Web/Views/Shared/_Layout.cshtml +++ b/samples/MvcSample.Web/Views/Shared/_Layout.cshtml @@ -11,7 +11,7 @@ body { padding-top: 0px; } } - @RenderSection("header", required: false) + @await RenderSectionAsync("header", required: false) - @RenderSection("footer", required: false) + @await RenderSectionAsync("footer", required: false) diff --git a/src/Microsoft.AspNet.Mvc.Core/Internal/TaskHelper.cs b/src/Microsoft.AspNet.Mvc.Common/TaskHelper.cs similarity index 56% rename from src/Microsoft.AspNet.Mvc.Core/Internal/TaskHelper.cs rename to src/Microsoft.AspNet.Mvc.Common/TaskHelper.cs index f12375b07b..b290361363 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Internal/TaskHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Common/TaskHelper.cs @@ -3,12 +3,12 @@ using System.Threading.Tasks; -namespace Microsoft.AspNet.Mvc.Internal +namespace Microsoft.AspNet.Mvc { /// /// Utility methods for dealing with . /// - public static class TaskHelper + internal static class TaskHelper { /// /// 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(); } + + /// + /// 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. + /// + /// + /// Invoking this method is equivalent to calling on the + /// if it is not completed. + /// + public static TVal WaitAndThrowIfFaulted(Task task) + { + return task.GetAwaiter().GetResult(); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs index 4b3bdbd373..db57d73146 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/IRazorPage.cs @@ -22,7 +22,6 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// Gets or sets the action invoked to render the body. /// - // TODO: https://github.com/aspnet/Mvc/issues/845 tracks making this async Action RenderBodyDelegate { get; set; } /// @@ -53,12 +52,12 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// Gets or sets the sections that can be rendered by this page. /// - Dictionary PreviousSectionWriters { get; set; } + Dictionary PreviousSectionWriters { get; set; } /// /// Gets the sections that are defined by this page. /// - Dictionary SectionWriters { get; } + Dictionary SectionWriters { get; } /// /// Renders the page and writes the output to the . diff --git a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs index b18e7753ce..4e6e08cd25 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Properties/Resources.Designer.cs @@ -205,17 +205,17 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// {0} can only be called from a layout page. /// - internal static string RenderBodyCannotBeCalled + internal static string RazorPage_MethodCannotBeCalled { - get { return GetString("RenderBodyCannotBeCalled"); } + get { return GetString("RazorPage_MethodCannotBeCalled"); } } /// /// {0} can only be called from a layout page. /// - 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); } /// @@ -251,7 +251,7 @@ namespace Microsoft.AspNet.Mvc.Razor } /// - /// {0} has already been called for the section named '{1}'. + /// The section named '{0}' has already been rendered. /// internal static string SectionAlreadyRendered { @@ -259,11 +259,11 @@ namespace Microsoft.AspNet.Mvc.Razor } /// - /// {0} has already been called for the section named '{1}'. + /// The section named '{0}' has already been rendered. /// - 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); } /// @@ -362,22 +362,6 @@ namespace Microsoft.AspNet.Mvc.Razor return string.Format(CultureInfo.CurrentCulture, GetString("ViewMustBeContextualized"), p0, p1); } - /// - /// The method '{0}' cannot be invoked by this view. - /// - internal static string View_MethodCannotBeCalled - { - get { return GetString("View_MethodCannotBeCalled"); } - } - - /// - /// The method '{0}' cannot be invoked by this view. - /// - 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); diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index 3b01f1e05a..bc46922d89 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.Razor public RazorPage() { - SectionWriters = new Dictionary(StringComparer.OrdinalIgnoreCase); + SectionWriters = new Dictionary(StringComparer.OrdinalIgnoreCase); _writerScopes = new Stack(); } @@ -105,10 +105,10 @@ namespace Microsoft.AspNet.Mvc.Razor public bool IsLayoutBeingRendered { get; set; } /// - public Dictionary PreviousSectionWriters { get; set; } + public Dictionary PreviousSectionWriters { get; set; } /// - public Dictionary SectionWriters { get; private set; } + public Dictionary SectionWriters { get; private set; } /// public abstract Task ExecuteAsync(); @@ -215,7 +215,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// 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 /// /// Creates a named content section in the page that can be invoked in a Layout page using - /// or . + /// or . /// /// The name of the section to create. - /// The to execute when rendering the section. - public void DefineSection(string name, HelperResult section) + /// The to execute when rendering the section. + 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) + /// + /// In layout pages, renders the content of the section named . + /// + /// The name of the section to render. + /// Returns a HtmlString that contains the rendered HTML. + public HtmlString RenderSection([NotNull] string name) { return RenderSection(name, required: true); } - public HelperResult RenderSection([NotNull] string name, bool required) + /// + /// In layout pages, renders the content of the section named . + /// + /// The section to render. + /// Indicates if this section must be rendered. + /// Returns a HtmlString that contains the rendered HTML. + 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); + } + + /// + /// In layout pages, asynchronously renders the content of the section named . + /// + /// The section to render. + /// A that on completion returns a containing + /// the rendered HTML. + public Task RenderSectionAsync([NotNull] string name) + { + return RenderSectionAsync(name, required: true); + } + + /// + /// In layout pages, asynchronously renders the content of the section named . + /// + /// The section to render. + /// A that on completion returns a containing + /// the rendered HTML. + /// if is true and the section + /// was not registered using the @section in the Razor page. + public async Task RenderSectionAsync([NotNull] string name, bool required) + { + EnsureMethodCanBeInvoked(nameof(RenderSectionAsync)); + return await RenderSectionAsyncCore(name, required); + } + + private async Task 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)); } } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/RenderAsyncDelegate.cs b/src/Microsoft.AspNet.Mvc.Razor/RenderAsyncDelegate.cs new file mode 100644 index 0000000000..1b1ba47fc0 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/RenderAsyncDelegate.cs @@ -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); +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx index 2492e79319..cca4dab354 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Razor/Resources.resx @@ -153,7 +153,7 @@ The {0} was unable to provide metadata for expression '{1}'. - + {0} can only be called from a layout page. @@ -163,7 +163,7 @@ Section '{0}' is already defined. - {0} has already been called for the section named '{1}'. + The section named '{0}' has already been rendered. Section '{0}' is not defined. @@ -183,7 +183,4 @@ The '{0}' method must be called before '{1}' can be invoked. - - The method '{0}' cannot be invoked by this view. - \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs index 7fa4921b1c..4ef8994fef 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs @@ -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 + page.PreviousSectionWriters = new Dictionary { - { "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 + page.PreviousSectionWriters = new Dictionary { - { "baz", expected } + { "baz", _nullRenderAsyncDelegate } }; // Act @@ -217,7 +217,7 @@ namespace Microsoft.AspNet.Mvc.Razor // Act and Assert ExceptionAssert.Throws(() => 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 + page.PreviousSectionWriters = new Dictionary { - { "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 + page.PreviousSectionWriters = new Dictionary { - { "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 + page.PreviousSectionWriters = new Dictionary { - { "header", new HelperResult(writer => { }) } + { "header", _nullRenderAsyncDelegate } }; // Act var ex = await Assert.ThrowsAsync(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 + { + { "header", _nullRenderAsyncDelegate } + }; + + // Act + var ex = await Assert.ThrowsAsync(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 + { + { "header", _nullRenderAsyncDelegate } + }; + + // Act + var ex = await Assert.ThrowsAsync(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(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 + page.PreviousSectionWriters = new Dictionary { - { "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 + page.PreviousSectionWriters = new Dictionary { { - "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(); 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(); 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 executeAction, ViewContext context = null) + { + return CreatePage(page => + { + executeAction(page); + return Task.FromResult(0); + }, context); + } + + + private static TestableRazorPage CreatePage(Func executeAction, + ViewContext context = null) { context = context ?? CreateViewContext(); var view = new Mock { 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(Output); + return writer.ToString(); + } + } + public HelperResult RenderBodyPublic() { return base.RenderBody(); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs index 5af5c8c47e..018e69444e 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs @@ -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"; }); diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithLayout.cshtml b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithLayout.cshtml index b038ad9f42..a0710f2a89 100644 --- a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithLayout.cshtml +++ b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithLayout.cshtml @@ -7,7 +7,7 @@ RenderBody content @section content { @{ - FlushAsync().Wait(); + await FlushAsync(); WaitService.WaitForClient(); } Content that takes time to produce diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml index b8f092fcd5..d83f5a82d2 100644 --- a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml +++ b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml @@ -7,9 +7,9 @@ RenderBody content @section content { @{ - FlushAsync().Wait(); + await FlushAsync(); WaitService.WaitForClient(); } - @Component.InvokeAsync("ComponentThatSetsTitle").Result + @await Component.InvokeAsync("ComponentThatSetsTitle") Content that takes time to produce } \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml index b6de9fc0cf..cb3d3ba7e1 100644 --- a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml +++ b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml @@ -5,7 +5,7 @@ WaitService.WaitForClient(); } @RenderBody() -@RenderSection("content") +@await RenderSectionAsync("content") @{ WaitService.NotifyClient(); } diff --git a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml index d425369da1..090234c40f 100644 --- a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml +++ b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml @@ -6,7 +6,7 @@ WaitService.WaitForClient(); } @await Html.PartialAsync("_PartialThatSetsTitle") -@RenderSection("content") +@await RenderSectionAsync("content") @{ WaitService.NotifyClient(); }