-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8a89e65
commit 162c1aa
Showing
4 changed files
with
242 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace RazorBlade; | ||
|
||
/// <summary> | ||
/// Base class for HTML layout pages. | ||
/// </summary> | ||
public abstract class HtmlLayout : HtmlTemplate, IRazorLayout | ||
{ | ||
private IRazorLayout.IExecutionResult? _layoutInput; | ||
|
||
private IRazorLayout.IExecutionResult LayoutInput => _layoutInput ?? throw new InvalidOperationException("No layout is being rendered."); | ||
|
||
async Task<IRazorLayout.IExecutionResult> IRazorLayout.RenderLayoutAsync(IRazorLayout.IExecutionResult input) | ||
{ | ||
input.CancellationToken.ThrowIfCancellationRequested(); | ||
var previousStatus = (Output, CancellationToken); | ||
|
||
try | ||
{ | ||
_layoutInput = input; | ||
|
||
var stringWriter = new StringWriter(); | ||
|
||
Output = stringWriter; | ||
CancellationToken = input.CancellationToken; | ||
|
||
await ExecuteAsync().ConfigureAwait(false); | ||
|
||
return new ExecutionResult | ||
{ | ||
Body = new StringBuilderEncodedContent(stringWriter.GetStringBuilder()), | ||
Layout = Layout, | ||
Sections = _sections, | ||
CancellationToken = CancellationToken | ||
}; | ||
} | ||
finally | ||
{ | ||
_layoutInput = null; | ||
(Output, CancellationToken) = previousStatus; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Returns the inner page body. | ||
/// </summary> | ||
protected IEncodedContent RenderBody() | ||
=> LayoutInput.Body; | ||
|
||
/// <summary> | ||
/// Renders a required section and returns the result as encoded content. | ||
/// </summary> | ||
/// <param name="name">The section name.</param> | ||
/// <returns>The content to write to the output.</returns> | ||
protected IEncodedContent RenderSection(string name) | ||
=> RenderSection(name, true); | ||
|
||
/// <summary> | ||
/// Renders a section and returns the result as encoded content. | ||
/// </summary> | ||
/// <param name="name">The section name.</param> | ||
/// <param name="required">Whether the section is required.</param> | ||
/// <returns>The content to write to the output.</returns> | ||
protected IEncodedContent RenderSection(string name, bool required) | ||
{ | ||
var renderTask = RenderSectionAsync(name, required); | ||
|
||
return renderTask.IsCompleted | ||
? renderTask.GetAwaiter().GetResult() | ||
: Task.Run(async () => await renderTask.ConfigureAwait(false)).GetAwaiter().GetResult(); | ||
} | ||
|
||
/// <summary> | ||
/// Renders a required section asynchronously and returns the result as encoded content. | ||
/// </summary> | ||
/// <param name="name">The section name.</param> | ||
/// <returns>The content to write to the output.</returns> | ||
protected Task<IEncodedContent> RenderSectionAsync(string name) | ||
=> RenderSectionAsync(name, true); | ||
|
||
/// <summary> | ||
/// Renders a section asynchronously and returns the result as encoded content. | ||
/// </summary> | ||
/// <param name="name">The section name.</param> | ||
/// <param name="required">Whether the section is required.</param> | ||
/// <returns>The content to write to the output.</returns> | ||
protected async Task<IEncodedContent> RenderSectionAsync(string name, bool required) | ||
{ | ||
if (!LayoutInput.Sections.TryGetValue(name, out var sectionAction)) | ||
{ | ||
if (required) | ||
throw new InvalidOperationException($"Section '{name}' is not defined."); | ||
|
||
return StringBuilderEncodedContent.Empty; | ||
} | ||
|
||
var previousOutput = Output; | ||
|
||
try | ||
{ | ||
var stringWriter = new StringWriter(); | ||
Output = stringWriter; | ||
|
||
await sectionAction.Invoke().ConfigureAwait(false); | ||
return new StringBuilderEncodedContent(stringWriter.GetStringBuilder()); | ||
} | ||
finally | ||
{ | ||
Output = previousOutput; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace RazorBlade; | ||
|
||
/// <summary> | ||
/// Represents a Razor layout page. | ||
/// </summary> | ||
public interface IRazorLayout | ||
{ | ||
/// <summary> | ||
/// Renders the layout for a given page. | ||
/// </summary> | ||
/// <param name="input">The input data.</param> | ||
/// <returns>The output data after rendering the layout, which can be used for the next layout.</returns> | ||
Task<IExecutionResult> RenderLayoutAsync(IExecutionResult input); | ||
|
||
/// <summary> | ||
/// The execution result of a page. | ||
/// </summary> | ||
public interface IExecutionResult | ||
{ | ||
/// <summary> | ||
/// The rendered body contents. | ||
/// </summary> | ||
IEncodedContent Body { get; } | ||
|
||
/// <summary> | ||
/// The layout this execution result needs to be wrapped in. | ||
/// </summary> | ||
IRazorLayout? Layout { get; } | ||
|
||
/// <summary> | ||
/// The sections this page has defined. | ||
/// </summary> | ||
IReadOnlyDictionary<string, Func<Task>> Sections { get; } | ||
|
||
/// <summary> | ||
/// The cancellation token. | ||
/// </summary> | ||
CancellationToken CancellationToken { get; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters