From 052ad3e95f2f8ae72c97d12eb4f5d118ae110af9 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 4 Nov 2014 16:03:55 -0800 Subject: [PATCH] RenderSection \ RenderSectionAsync does not work in sections Fixes #1509 --- src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs | 37 ++++++++++++------- .../FlushPointTest.cs | 32 ++++++++++------ .../RazorWebSite/Controllers/FlushPoint.cs | 7 ++++ .../Services/TaskReturningService.cs | 16 ++++++++ test/WebSites/RazorWebSite/Startup.cs | 1 + .../PageWithPartialsAndViewComponents.cshtml | 8 +++- ...SectionInvokedViaRenderSectionAsync.cshtml | 19 ++++++++++ .../Views/Shared/_LayoutWithFlush.cshtml | 3 -- .../Shared/_LayoutWithPartialAndFlush.cshtml | 4 +- .../_LayoutWithRenderSectionAsync.cshtml | 10 +++++ 10 files changed, 105 insertions(+), 32 deletions(-) create mode 100644 test/WebSites/RazorWebSite/Services/TaskReturningService.cs create mode 100644 test/WebSites/RazorWebSite/Views/FlushPoint/PageWithSectionInvokedViaRenderSectionAsync.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionAsync.cshtml diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index 2db0e165da..870503d01b 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -451,7 +451,11 @@ namespace Microsoft.AspNet.Mvc.Razor /// 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. + /// Returns to allow the call to + /// succeed. + /// The method writes to the and the value returned is a token + /// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the + /// value does not represent the rendered content. public HtmlString RenderSection([NotNull] string name) { return RenderSection(name, required: true); @@ -462,7 +466,11 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// The section to render. /// Indicates if this section must be rendered. - /// Returns a HtmlString that contains the rendered HTML. + /// Returns to allow the call to + /// succeed. + /// The method writes to the and the value returned is a token + /// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the + /// value does not represent the rendered content. public HtmlString RenderSection([NotNull] string name, bool required) { EnsureMethodCanBeInvoked(nameof(RenderSection)); @@ -475,8 +483,11 @@ namespace Microsoft.AspNet.Mvc.Razor /// 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. + /// A that on completion returns that + /// allows the call to succeed. + /// The method writes to the and the value returned is a token + /// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the + /// value does not represent the rendered content. public Task RenderSectionAsync([NotNull] string name) { return RenderSectionAsync(name, required: true); @@ -486,8 +497,11 @@ namespace Microsoft.AspNet.Mvc.Razor /// 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. + /// A that on completion returns that + /// allows the call to succeed. + /// The method writes to the and the value returned is a token + /// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the + /// value does not represent the rendered content. /// 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) @@ -508,14 +522,11 @@ namespace Microsoft.AspNet.Mvc.Razor if (PreviousSectionWriters.TryGetValue(sectionName, out renderDelegate)) { _renderedSections.Add(sectionName); + await renderDelegate(Output); - using (var writer = new StringCollectionTextWriter(Output.Encoding)) - { - await renderDelegate(writer); - - // Returning a disposed StringCollectionTextWriter is safe. - return new HtmlString(writer); - } + // Return a token value that allows the Write call that wraps the RenderSection \ RenderSectionAsync + // to succeed. + return HtmlString.Empty; } else if (required) { diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/FlushPointTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/FlushPointTest.cs index 00410ab04c..27f3315c91 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/FlushPointTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/FlushPointTest.cs @@ -67,8 +67,10 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("Final content", GetTrimmedString(stream)); } - [Fact] - public async Task FlushPointsAreExecutedForPagesWithComponentsAndPartials() + [Theory] + [InlineData("PageWithPartialsAndViewComponents", "FlushAsync invoked inside RenderSection")] + [InlineData("PageWithRenderSectionAsync", "FlushAsync invoked inside RenderSectionAsync")] + public async Task FlushPointsAreExecutedForPagesWithComponentsPartialsAndSections(string action, string title) { var waitService = new WaitService(); var serviceProvider = GetServiceProvider(waitService); @@ -77,23 +79,31 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); // Act - var stream = await client.GetStreamAsync("http://localhost/FlushPoint/PageWithPartialsAndViewComponents"); + var stream = await client.GetStreamAsync("http://localhost/FlushPoint/" + action); // Assert - 1 - Assert.Equal( -@"Page With Components and Partials - -RenderBody content", GetTrimmedString(stream)); + Assert.Equal(string.Join(Environment.NewLine, + "" + title + "", + "", + "RenderBody content"), GetTrimmedString(stream)); waitService.WaitForServer(); // Assert - 2 - Assert.Equal("partial-content", GetTrimmedString(stream)); + Assert.Equal(string.Join( + Environment.NewLine, + "partial-content", + "", + "Value from TaskReturningString", + "

section-content

"), GetTrimmedString(stream)); waitService.WaitForServer(); // Assert - 3 - Assert.Equal( -@"component-content - Content that takes time to produce", GetTrimmedString(stream)); + Assert.Equal(string.Join( + Environment.NewLine, + "component-content", + " Content that takes time to produce", + "", + "More content from layout"), GetTrimmedString(stream)); } private IServiceProvider GetServiceProvider(WaitService waitService) diff --git a/test/WebSites/RazorWebSite/Controllers/FlushPoint.cs b/test/WebSites/RazorWebSite/Controllers/FlushPoint.cs index 021ca21a6f..f8f47ebb23 100644 --- a/test/WebSites/RazorWebSite/Controllers/FlushPoint.cs +++ b/test/WebSites/RazorWebSite/Controllers/FlushPoint.cs @@ -17,9 +17,16 @@ namespace RazorWebSite return View(); } + // This uses RenderSection to render the section that contains a FlushAsync call public ViewResult PageWithPartialsAndViewComponents() { return View(); } + + // This uses RenderSectionAsync to render the section that contains a FlushAsync call + public ViewResult PageWithRenderSectionAsync() + { + return View("PageWithSectionInvokedViaRenderSectionAsync"); + } } } \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Services/TaskReturningService.cs b/test/WebSites/RazorWebSite/Services/TaskReturningService.cs new file mode 100644 index 0000000000..fc68ea6573 --- /dev/null +++ b/test/WebSites/RazorWebSite/Services/TaskReturningService.cs @@ -0,0 +1,16 @@ +// 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.Threading.Tasks; + +namespace RazorWebSite +{ + public class TaskReturningService + { + public async Task GetValueAsync() + { + await Task.Delay(100); + return "Value from TaskReturningString"; + } + } +} \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Startup.cs b/test/WebSites/RazorWebSite/Startup.cs index 51cbc1681c..14086728dc 100644 --- a/test/WebSites/RazorWebSite/Startup.cs +++ b/test/WebSites/RazorWebSite/Startup.cs @@ -17,6 +17,7 @@ namespace RazorWebSite // Add MVC services to the services container services.AddMvc(configuration); services.AddTransient(); + services.AddTransient(); services.Configure(options => { var expander = new LanguageViewLocationExpander( diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml index d83f5a82d2..0b979014f9 100644 --- a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml +++ b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml @@ -1,15 +1,19 @@ @inject WaitService WaitService +@inject TaskReturningService TaskReturningService + @{ Layout = "/Views/Shared/_LayoutWithPartialAndFlush.cshtml"; - ViewBag.Title = "Page With Components and Partials"; + ViewBag.Title = "FlushAsync invoked inside RenderSection"; } RenderBody content @section content { +@await TaskReturningService.GetValueAsync() +

section-content

@{ await FlushAsync(); WaitService.WaitForClient(); } @await Component.InvokeAsync("ComponentThatSetsTitle") Content that takes time to produce -} \ No newline at end of file +} diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithSectionInvokedViaRenderSectionAsync.cshtml b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithSectionInvokedViaRenderSectionAsync.cshtml new file mode 100644 index 0000000000..660e934810 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithSectionInvokedViaRenderSectionAsync.cshtml @@ -0,0 +1,19 @@ +@inject WaitService WaitService +@inject TaskReturningService TaskReturningService + +@{ + Layout = "/Views/Shared/_LayoutWithRenderSectionAsync.cshtml"; + ViewBag.Title = "FlushAsync invoked inside RenderSectionAsync"; +} +RenderBody content +@section content +{ +@await TaskReturningService.GetValueAsync() +

section-content

+ @{ + await FlushAsync(); + WaitService.WaitForClient(); + } + @await Component.InvokeAsync("ComponentThatSetsTitle") + Content that takes time to produce +} diff --git a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml index cb3d3ba7e1..646d08f7bb 100644 --- a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml +++ b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml @@ -6,6 +6,3 @@ } @RenderBody() @await RenderSectionAsync("content") -@{ - WaitService.NotifyClient(); -} diff --git a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml index 090234c40f..8dfc88f054 100644 --- a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml +++ b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml @@ -7,6 +7,4 @@ } @await Html.PartialAsync("_PartialThatSetsTitle") @await RenderSectionAsync("content") -@{ - WaitService.NotifyClient(); -} +More content from layout diff --git a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionAsync.cshtml b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionAsync.cshtml new file mode 100644 index 0000000000..8dfc88f054 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionAsync.cshtml @@ -0,0 +1,10 @@ +@inject WaitService WaitService +@ViewBag.Title +@RenderBody() +@{ + await FlushAsync(); + WaitService.WaitForClient(); +} +@await Html.PartialAsync("_PartialThatSetsTitle") +@await RenderSectionAsync("content") +More content from layout