Add writing scopes to RazorPage.
- RazorPage now has the ability to use writing scopes to control where things are written. - This enables RazorPages to use these writing scopes with TagHelpers. TagHelpers use them to buffer attributes that have C# contained within them and to also buffer content of TagHelpers whos ContentBehavior is Modify. - Added RazorPage tests to validate their functionality. #1102
This commit is contained in:
parent
e995e7a3e2
commit
082512f63c
|
|
@ -76,6 +76,7 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
DefaultBaseClass = BaseType + '<' + DefaultModel + '>';
|
||||
DefaultNamespace = "Asp";
|
||||
GeneratedClassContext = new GeneratedClassContext(
|
||||
|
||||
executeMethodName: "ExecuteAsync",
|
||||
writeMethodName: "Write",
|
||||
writeLiteralMethodName: "WriteLiteral",
|
||||
|
|
@ -87,7 +88,9 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
RunnerTypeName = typeof(TagHelperRunner).FullName,
|
||||
ScopeManagerTypeName = typeof(TagHelperScopeManager).FullName,
|
||||
ExecutionContextTypeName = typeof(TagHelpersExecutionContext).FullName
|
||||
ExecutionContextTypeName = typeof(TagHelpersExecutionContext).FullName,
|
||||
StartWritingScopeMethodName = "StartWritingScope",
|
||||
EndWritingScopeMethodName = "EndWritingScope"
|
||||
})
|
||||
{
|
||||
ResolveUrlMethodName = "Href",
|
||||
|
|
|
|||
|
|
@ -154,6 +154,38 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
return string.Format(CultureInfo.CurrentCulture, GetString("MvcRazorCodeParser_OnlyOneModelStatementIsAllowed"), p0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There is no active writing scope to end.
|
||||
/// </summary>
|
||||
internal static string RazorPage_ThereIsNoActiveWritingScopeToEnd
|
||||
{
|
||||
get { return GetString("RazorPage_ThereIsNoActiveWritingScopeToEnd"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// There is no active writing scope to end.
|
||||
/// </summary>
|
||||
internal static string FormatRazorPage_ThereIsNoActiveWritingScopeToEnd()
|
||||
{
|
||||
return GetString("RazorPage_ThereIsNoActiveWritingScopeToEnd");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// You cannot flush while inside a writing scope.
|
||||
/// </summary>
|
||||
internal static string RazorPage_YouCannotFlushWhileInAWritingScope
|
||||
{
|
||||
get { return GetString("RazorPage_YouCannotFlushWhileInAWritingScope"); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// You cannot flush while inside a writing scope.
|
||||
/// </summary>
|
||||
internal static string FormatRazorPage_YouCannotFlushWhileInAWritingScope()
|
||||
{
|
||||
return GetString("RazorPage_YouCannotFlushWhileInAWritingScope");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// {0} can only be called from a layout page.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -21,12 +21,16 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
public abstract class RazorPage : IRazorPage
|
||||
{
|
||||
private readonly HashSet<string> _renderedSections = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
private readonly Stack<TextWriter> _writerScopes;
|
||||
private TextWriter _originalWriter;
|
||||
private IUrlHelper _urlHelper;
|
||||
private bool _renderedBody;
|
||||
|
||||
public RazorPage()
|
||||
{
|
||||
SectionWriters = new Dictionary<string, HelperResult>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
_writerScopes = new Stack<TextWriter>();
|
||||
}
|
||||
|
||||
public HttpContext Context
|
||||
|
|
@ -107,6 +111,57 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <inheritdoc />
|
||||
public abstract Task ExecuteAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new writing scope.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// All writes to the <see cref="Output"/> or <see cref="ViewContext.Writer"/> after calling this method will
|
||||
/// be buffered until <see cref="EndWritingScope"/> is called.
|
||||
/// </remarks>
|
||||
public void StartWritingScope()
|
||||
{
|
||||
// If there isn't a base writer take the ViewContext.Writer
|
||||
if (_originalWriter == null)
|
||||
{
|
||||
_originalWriter = ViewContext.Writer;
|
||||
}
|
||||
|
||||
// We need to replace the ViewContext's Writer to ensure that all content (including content written
|
||||
// from HTML helpers) is redirected.
|
||||
ViewContext.Writer = new StringWriter();
|
||||
|
||||
_writerScopes.Push(ViewContext.Writer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the current writing scope that was started by calling <see cref="StartWritingScope"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="TextWriter"/> that contains the content written to the <see cref="Output"/> or
|
||||
/// <see cref="ViewContext.Writer"/> during the writing scope.</returns>
|
||||
public TextWriter EndWritingScope()
|
||||
{
|
||||
if (_writerScopes.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.RazorPage_ThereIsNoActiveWritingScopeToEnd);
|
||||
}
|
||||
|
||||
var writer = _writerScopes.Pop();
|
||||
|
||||
if (_writerScopes.Count > 0)
|
||||
{
|
||||
ViewContext.Writer = _writerScopes.Peek();
|
||||
}
|
||||
else
|
||||
{
|
||||
ViewContext.Writer = _originalWriter;
|
||||
|
||||
// No longer a base writer
|
||||
_originalWriter = null;
|
||||
}
|
||||
|
||||
return writer;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified <paramref name="value"/> with HTML encoding to <see cref="Output"/>.
|
||||
/// </summary>
|
||||
|
|
@ -395,6 +450,13 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
/// <returns>A <see cref="Task"/> that represents the asynchronous flush operation.</returns>
|
||||
public Task FlushAsync()
|
||||
{
|
||||
// If there are active writing scopes then we should throw. Cannot flush content that has the potential to
|
||||
// change.
|
||||
if (_writerScopes.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException(Resources.RazorPage_YouCannotFlushWhileInAWritingScope);
|
||||
}
|
||||
|
||||
// Calls to Flush are allowed if the page does not specify a Layout or if it is executing a section in the
|
||||
// Layout.
|
||||
if (!IsLayoutBeingRendered && !string.IsNullOrEmpty(Layout))
|
||||
|
|
|
|||
|
|
@ -144,6 +144,12 @@
|
|||
<data name="MvcRazorCodeParser_OnlyOneModelStatementIsAllowed" xml:space="preserve">
|
||||
<value>Only one '{0}' statement is allowed in a file.</value>
|
||||
</data>
|
||||
<data name="RazorPage_ThereIsNoActiveWritingScopeToEnd" xml:space="preserve">
|
||||
<value>There is no active writing scope to end.</value>
|
||||
</data>
|
||||
<data name="RazorPage_YouCannotFlushWhileInAWritingScope" xml:space="preserve">
|
||||
<value>You cannot flush while inside a writing scope.</value>
|
||||
</data>
|
||||
<data name="RenderBodyCannotBeCalled" xml:space="preserve">
|
||||
<value>{0} can only be called from a layout page.</value>
|
||||
</data>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,117 @@ namespace Microsoft.AspNet.Mvc.Razor
|
|||
{
|
||||
public class RazorPageTest
|
||||
{
|
||||
[Fact]
|
||||
public async Task WritingScopesRedirectContentWrittenToViewContextWriter()
|
||||
{
|
||||
// Arrange
|
||||
var viewContext = CreateViewContext();
|
||||
var page = CreatePage(v =>
|
||||
{
|
||||
v.Write("Hello Prefix");
|
||||
v.StartWritingScope();
|
||||
v.Write("Hello from Output");
|
||||
v.ViewContext.Writer.Write("Hello from view context writer");
|
||||
var scopeValue = v.EndWritingScope();
|
||||
v.Write("From Scope: " + scopeValue.ToString());
|
||||
});
|
||||
|
||||
// Act
|
||||
await page.ExecuteAsync();
|
||||
var pageOutput = page.Output.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Hello PrefixFrom Scope: Hello from OutputHello from view context writer", pageOutput);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WritingScopesRedirectsContentWrittenToOutput()
|
||||
{
|
||||
// Arrange
|
||||
var viewContext = CreateViewContext();
|
||||
var page = CreatePage(v =>
|
||||
{
|
||||
v.Write("Hello Prefix");
|
||||
v.StartWritingScope();
|
||||
v.Write("Hello In Scope");
|
||||
var scopeValue = v.EndWritingScope();
|
||||
v.Write("From Scope: " + scopeValue.ToString());
|
||||
});
|
||||
|
||||
// Act
|
||||
await page.ExecuteAsync();
|
||||
var pageOutput = page.Output.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Hello PrefixFrom Scope: Hello In Scope", pageOutput);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WritingScopesCanNest()
|
||||
{
|
||||
// Arrange
|
||||
var viewContext = CreateViewContext();
|
||||
var page = CreatePage(v =>
|
||||
{
|
||||
v.Write("Hello Prefix");
|
||||
v.StartWritingScope();
|
||||
v.Write("Hello In Scope Pre Nest");
|
||||
|
||||
v.StartWritingScope();
|
||||
v.Write("Hello In Nested Scope");
|
||||
var scopeValue1 = v.EndWritingScope();
|
||||
|
||||
v.Write("Hello In Scope Post Nest");
|
||||
var scopeValue2 = v.EndWritingScope();
|
||||
|
||||
v.Write("From Scopes: " + scopeValue2.ToString() + scopeValue1.ToString());
|
||||
});
|
||||
|
||||
// Act
|
||||
await page.ExecuteAsync();
|
||||
var pageOutput = page.Output.ToString();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Hello PrefixFrom Scopes: Hello In Scope Pre NestHello In Scope Post NestHello In Nested Scope", pageOutput);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartNewWritingScope_CannotFlushInWritingScope()
|
||||
{
|
||||
// Arrange
|
||||
var viewContext = CreateViewContext();
|
||||
var page = CreatePage(v =>
|
||||
{
|
||||
v.StartWritingScope();
|
||||
v.FlushAsync();
|
||||
});
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => page.ExecuteAsync());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("You cannot flush while inside a writing scope.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StartNewWritingScope_CannotEndWritingScopeWhenNoWritingScope()
|
||||
{
|
||||
// Arrange
|
||||
var viewContext = CreateViewContext();
|
||||
var page = CreatePage(v =>
|
||||
{
|
||||
v.EndWritingScope();
|
||||
});
|
||||
|
||||
// Act
|
||||
var ex = await Assert.ThrowsAsync<InvalidOperationException>(
|
||||
() => page.ExecuteAsync());
|
||||
|
||||
// Assert
|
||||
Assert.Equal("There is no active writing scope to end.", ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DefineSection_ThrowsIfSectionIsAlreadyDefined()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue