Skip to content

Commit

Permalink
Ensure layouts are not executed without a contents page
Browse files Browse the repository at this point in the history
  • Loading branch information
ltrzesniewski committed Dec 2, 2023
1 parent ba36837 commit 1c62de5
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 1 deletion.
53 changes: 53 additions & 0 deletions src/RazorBlade.Library/HtmlLayout.cs
@@ -1,4 +1,7 @@
using System;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -9,6 +12,8 @@ namespace RazorBlade;
/// </summary>
public abstract class HtmlLayout : HtmlTemplate, IRazorLayout
{
private const string _contentsRequiredErrorMessage = "Layout pages can only be rendered when associated with a contents page.";

private IRazorExecutionResult? _layoutInput;

async Task<IRazorExecutionResult> IRazorLayout.ExecuteLayoutAsync(IRazorExecutionResult input)
Expand All @@ -24,6 +29,54 @@ async Task<IRazorExecutionResult> IRazorLayout.ExecuteLayoutAsync(IRazorExecutio
}
}

private protected override Task<IRazorExecutionResult> ExecuteAsyncCore(CancellationToken cancellationToken)
{
if (_layoutInput is null)
throw new InvalidOperationException(_contentsRequiredErrorMessage);

return base.ExecuteAsyncCore(cancellationToken);
}

/// <summary>
/// Should not be used on layout pages.
/// </summary>
[Obsolete(_contentsRequiredErrorMessage, true)]
[EditorBrowsable(EditorBrowsableState.Never)]
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
[SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")]
public new void Render(CancellationToken cancellationToken = default)
=> throw new InvalidOperationException(_contentsRequiredErrorMessage);

/// <summary>
/// Should not be used on layout pages.
/// </summary>
[Obsolete(_contentsRequiredErrorMessage, true)]
[EditorBrowsable(EditorBrowsableState.Never)]
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
[SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")]
public new void Render(TextWriter textWriter, CancellationToken cancellationToken = default)
=> throw new InvalidOperationException(_contentsRequiredErrorMessage);

/// <summary>
/// Should not be used on layout pages.
/// </summary>
[Obsolete(_contentsRequiredErrorMessage, true)]
[EditorBrowsable(EditorBrowsableState.Never)]
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
[SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")]
public new Task<string> RenderAsync(CancellationToken cancellationToken = default)
=> throw new InvalidOperationException(_contentsRequiredErrorMessage);

/// <summary>
/// Should not be used on layout pages.
/// </summary>
[Obsolete(_contentsRequiredErrorMessage, true)]
[EditorBrowsable(EditorBrowsableState.Never)]
[SuppressMessage("ReSharper", "UnusedParameter.Global")]
[SuppressMessage("ReSharper", "MemberCanBeMadeStatic.Global")]
public new Task RenderAsync(TextWriter textWriter, CancellationToken cancellationToken = default)
=> throw new InvalidOperationException(_contentsRequiredErrorMessage);

/// <summary>
/// Returns the inner page body.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/RazorBlade.Library/RazorTemplate.cs
Expand Up @@ -139,7 +139,7 @@ private async Task<StringBuilder> RenderAsyncCore(CancellationToken cancellation
/// <summary>
/// Calls the <see cref="ExecuteAsync"/> method in a new <see cref="ExecutionScope"/>.
/// </summary>
private protected async Task<IRazorExecutionResult> ExecuteAsyncCore(CancellationToken cancellationToken)
private protected virtual async Task<IRazorExecutionResult> ExecuteAsyncCore(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();

Expand Down
14 changes: 14 additions & 0 deletions src/RazorBlade.Tests/HtmlLayoutTests.cs
@@ -1,4 +1,6 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using RazorBlade.Tests.Support;
Expand Down Expand Up @@ -170,6 +172,18 @@ public void should_throw_when_duplicate_section_is_defined()
Assert.Throws<InvalidOperationException>(() => page.Render());
}

[Test, Obsolete]
public void should_throw_on_render()
{
var layout = new Layout(_ => { });

Assert.Throws<InvalidOperationException>(() => layout.Render(CancellationToken.None));
Assert.Throws<InvalidOperationException>(() => layout.Render(TextWriter.Null, CancellationToken.None));
Assert.Throws<InvalidOperationException>(() => layout.RenderAsync(CancellationToken.None).GetAwaiter().GetResult());
Assert.Throws<InvalidOperationException>(() => layout.RenderAsync(TextWriter.Null, CancellationToken.None).GetAwaiter().GetResult());
Assert.Throws<InvalidOperationException>(() => ((RazorTemplate)layout).Render(CancellationToken.None));
}

private class Template : HtmlTemplate
{
private readonly Action<Template> _executeAction;
Expand Down

0 comments on commit 1c62de5

Please sign in to comment.