diff --git a/src/RazorBlade.Library/HtmlLayout.cs b/src/RazorBlade.Library/HtmlLayout.cs index 14d9981..bf20c03 100644 --- a/src/RazorBlade.Library/HtmlLayout.cs +++ b/src/RazorBlade.Library/HtmlLayout.cs @@ -1,5 +1,4 @@ using System; -using System.IO; using System.Threading; using System.Threading.Tasks; @@ -16,27 +15,14 @@ public abstract class HtmlLayout : HtmlTemplate, IRazorLayout async Task IRazorLayout.ExecuteLayoutAsync(IRazorLayout.IExecutionResult input) { - input.CancellationToken.ThrowIfCancellationRequested(); - var previousStatus = (Output, CancellationToken); - try { _layoutInput = input; - - var output = new StringWriter(); - - Output = output; - CancellationToken = input.CancellationToken; - // TODO fully reset/restore the state - - await ExecuteAsync().ConfigureAwait(false); - - return new ExecutionResult(this, output.GetStringBuilder()); + return await ExecuteAsyncCore(input.CancellationToken); } finally { _layoutInput = null; - (Output, CancellationToken) = previousStatus; } } diff --git a/src/RazorBlade.Library/RazorTemplate.cs b/src/RazorBlade.Library/RazorTemplate.cs index 87b7218..d0acd2f 100644 --- a/src/RazorBlade.Library/RazorTemplate.cs +++ b/src/RazorBlade.Library/RazorTemplate.cs @@ -27,7 +27,7 @@ public abstract class RazorTemplate : IEncodedContent /// /// The cancellation token. /// - protected internal CancellationToken CancellationToken { get; set; } + protected internal CancellationToken CancellationToken { get; private set; } /// /// The layout to use. @@ -116,41 +116,29 @@ private async Task RenderAsyncCore(CancellationToken cancellation { cancellationToken.ThrowIfCancellationRequested(); - var previousState = (_sections, Output, CancellationToken, Layout); + var executionResult = await ExecuteAsyncCore(cancellationToken); - try + while (executionResult.Layout is { } layout) { - var output = new StringWriter(); - - _sections = null; - Output = output; - CancellationToken = cancellationToken; - Layout = null; - - await ExecuteAsync().ConfigureAwait(false); - - if (Layout is null) - return output.GetStringBuilder(); + cancellationToken.ThrowIfCancellationRequested(); + executionResult = await layout.ExecuteLayoutAsync(executionResult).ConfigureAwait(false); + } - IRazorLayout.IExecutionResult executionResult = new ExecutionResult(this, output.GetStringBuilder()); + if (executionResult.Body is StringBuilderEncodedContent { StringBuilder: var outputStringBuilder }) + return outputStringBuilder; - while (executionResult.Layout is { } layout) - { - cancellationToken.ThrowIfCancellationRequested(); - executionResult = await layout.ExecuteLayoutAsync(executionResult).ConfigureAwait(false); - } + var outputStringWriter = new StringWriter(); + executionResult.Body.WriteTo(outputStringWriter); + return outputStringWriter.GetStringBuilder(); + } - if (executionResult.Body is StringBuilderEncodedContent { StringBuilder: var outputWithLayout }) - return outputWithLayout; + private protected async Task ExecuteAsyncCore(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - var outerBodyResult = new StringWriter(); - executionResult.Body.WriteTo(outerBodyResult); - return outerBodyResult.GetStringBuilder(); - } - finally - { - (_sections, Output, CancellationToken, Layout) = previousState; - } + using var executionScope = new ExecutionScope(this, cancellationToken); + await ExecuteAsync().ConfigureAwait(false); + return new ExecutionResult(this, executionScope.Output); } /// @@ -238,9 +226,10 @@ protected internal void DefineSection(string name, Func action) void IEncodedContent.WriteTo(TextWriter textWriter) => Render(textWriter, CancellationToken.None); - private protected class ExecutionResult : IRazorLayout.IExecutionResult + private class ExecutionResult : IRazorLayout.IExecutionResult { private readonly RazorTemplate _page; + private readonly IReadOnlyDictionary>? _sections; public IEncodedContent Body { get; } public IRazorLayout? Layout { get; } @@ -249,6 +238,7 @@ private protected class ExecutionResult : IRazorLayout.IExecutionResult public ExecutionResult(RazorTemplate page, StringBuilder body) { _page = page; + _sections = page._sections; Body = new StringBuilderEncodedContent(body); Layout = page.Layout; CancellationToken = page.CancellationToken; @@ -256,24 +246,12 @@ public ExecutionResult(RazorTemplate page, StringBuilder body) public async Task RenderSectionAsync(string name) { - if (!_page.Sections.TryGetValue(name, out var sectionAction)) + if (_sections is null || !_sections.TryGetValue(name, out var sectionAction)) return null; - var previousOutput = _page.Output; - - try - { - var output = new StringWriter(); - _page.Output = output; - - await sectionAction().ConfigureAwait(false); - - return new StringBuilderEncodedContent(output.GetStringBuilder()); - } - finally - { - _page.Output = previousOutput; - } + using var executionScope = new ExecutionScope(_page, CancellationToken); + await sectionAction().ConfigureAwait(false); + return new StringBuilderEncodedContent(executionScope.Output); } } @@ -292,4 +270,38 @@ public void WriteTo(TextWriter textWriter) public override string ToString() => StringBuilder.ToString(); } + + private class ExecutionScope : IDisposable + { + private readonly RazorTemplate _page; + + private readonly Dictionary>? _sections; + private readonly TextWriter _output; + private readonly CancellationToken _cancellationToken; + private readonly IRazorLayout? _layout; + + public StringBuilder Output { get; } + + public ExecutionScope(RazorTemplate page, CancellationToken cancellationToken) + { + _page = page; + + _sections = page._sections; + _output = page.Output; + _cancellationToken = page.CancellationToken; + _layout = page.Layout; + + Output = new StringBuilder(); + page.Output = new StringWriter(Output); + page.CancellationToken = cancellationToken; + } + + public void Dispose() + { + _page._sections = _sections; + _page.Output = _output; + _page.CancellationToken = _cancellationToken; + _page.Layout = _layout; + } + } } diff --git a/src/RazorBlade.Tests/HtmlTemplateTests.cs b/src/RazorBlade.Tests/HtmlTemplateTests.cs index 2419b3c..55c889e 100644 --- a/src/RazorBlade.Tests/HtmlTemplateTests.cs +++ b/src/RazorBlade.Tests/HtmlTemplateTests.cs @@ -108,10 +108,7 @@ private class Template : HtmlTemplate private readonly Action