RenderSection \ RenderSectionAsync does not work in sections

Fixes #1509
This commit is contained in:
Pranav K 2014-11-04 16:03:55 -08:00
parent dd1500dcfb
commit 052ad3e95f
10 changed files with 105 additions and 32 deletions

View File

@ -451,7 +451,11 @@ namespace Microsoft.AspNet.Mvc.Razor
/// In layout pages, renders the content of the section named <paramref name="name"/>.
/// </summary>
/// <param name="name">The name of the section to render.</param>
/// <returns>Returns a HtmlString that contains the rendered HTML.</returns>
/// <returns>Returns <see cref="HtmlString.Empty"/> to allow the <see cref="Write(object)"/> call to
/// succeed.</returns>
/// <remarks>The method writes to the <see cref="Output"/> 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.</remarks>
public HtmlString RenderSection([NotNull] string name)
{
return RenderSection(name, required: true);
@ -462,7 +466,11 @@ namespace Microsoft.AspNet.Mvc.Razor
/// </summary>
/// <param name="name">The section to render.</param>
/// <param name="required">Indicates if this section must be rendered.</param>
/// <returns>Returns a HtmlString that contains the rendered HTML.</returns>
/// <returns>Returns <see cref="HtmlString.Empty"/> to allow the <see cref="Write(object)"/> call to
/// succeed.</returns>
/// <remarks>The method writes to the <see cref="Output"/> 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.</remarks>
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 <paramref name="name"/>.
/// </summary>
/// <param name="name">The section to render.</param>
/// <returns>A <see cref="Task{TResult}"/> that on completion returns a <see cref="HtmlString"/> containing
/// the rendered HTML.</returns>
/// <returns>A <see cref="Task{HtmlString}"/> that on completion returns <see cref="HtmlString.Empty"/> that
/// allows the <see cref="Write(object)"/> call to succeed.</returns>
/// <remarks>The method writes to the <see cref="Output"/> 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.</remarks>
public Task<HtmlString> 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 <paramref name="name"/>.
/// </summary>
/// <param name="name">The section to render.</param>
/// <returns>A <see cref="Task{TResult}"/> that on completion returns a <see cref="HtmlString"/> containing
/// the rendered HTML.</returns>
/// <returns>A <see cref="Task{HtmlString}"/> that on completion returns <see cref="HtmlString.Empty"/> that
/// allows the <see cref="Write(object)"/> call to succeed.</returns>
/// <remarks>The method writes to the <see cref="Output"/> 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.</remarks>
/// <exception cref="InvalidOperationException">if <paramref name="required"/> is <c>true</c> and the section
/// was not registered using the <c>@section</c> in the Razor page.</exception>
public async Task<HtmlString> 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)
{

View File

@ -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(
@"<title>Page With Components and Partials</title>
RenderBody content", GetTrimmedString(stream));
Assert.Equal(string.Join(Environment.NewLine,
"<title>" + title + "</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",
"<p>section-content</p>"), GetTrimmedString(stream));
waitService.WaitForServer();
// Assert - 3
Assert.Equal(
@"component-content
<span>Content that takes time to produce</span>", GetTrimmedString(stream));
Assert.Equal(string.Join(
Environment.NewLine,
"component-content",
" <span>Content that takes time to produce</span>",
"",
"More content from layout"), GetTrimmedString(stream));
}
private IServiceProvider GetServiceProvider(WaitService waitService)

View File

@ -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");
}
}
}

View File

@ -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<string> GetValueAsync()
{
await Task.Delay(100);
return "Value from TaskReturningString";
}
}
}

View File

@ -17,6 +17,7 @@ namespace RazorWebSite
// Add MVC services to the services container
services.AddMvc(configuration);
services.AddTransient<InjectedHelper>();
services.AddTransient<TaskReturningService>();
services.Configure<RazorViewEngineOptions>(options =>
{
var expander = new LanguageViewLocationExpander(

View File

@ -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()
<p>section-content</p>
@{
await FlushAsync();
WaitService.WaitForClient();
}
@await Component.InvokeAsync("ComponentThatSetsTitle")
<span>Content that takes time to produce</span>
}
}

View File

@ -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()
<p>section-content</p>
@{
await FlushAsync();
WaitService.WaitForClient();
}
@await Component.InvokeAsync("ComponentThatSetsTitle")
<span>Content that takes time to produce</span>
}

View File

@ -6,6 +6,3 @@
}
@RenderBody()
@await RenderSectionAsync("content")
@{
WaitService.NotifyClient();
}

View File

@ -7,6 +7,4 @@
}
@await Html.PartialAsync("_PartialThatSetsTitle")
@await RenderSectionAsync("content")
@{
WaitService.NotifyClient();
}
More content from layout

View File

@ -0,0 +1,10 @@
@inject WaitService WaitService
<title>@ViewBag.Title</title>
@RenderBody()
@{
await FlushAsync();
WaitService.WaitForClient();
}
@await Html.PartialAsync("_PartialThatSetsTitle")
@await RenderSectionAsync("content")
More content from layout