OrchardCMS/OrchardCore · error · InvalidOperationException
Buffer has already been rendered
Error message
Buffer has already been rendered
What it means
ViewBufferTextWriterContent wraps pooled ViewBufferTextWriter builders. Once the content has been written to a final TextWriter (rendered), the internal builder is released back to the pool and set to null. Writing or serializing the same content again is invalid, so WriteTo throws to prevent use-after-return of pooled memory.
Solutions
- Render the content once per output writer; re-execute the Liquid template or shape for each additional output
- If caching is needed, cache the rendered string (e.g. stringWriter output) rather than the IHtmlContent buffer
- Check for code paths that serialize content more than once (logging + response writing) and remove the duplicate
Example fix
// before _htmlContentCache[key] = liquidResult; // rendered once later twice // after _htmlContentCache[key] = htmlEncoder.Encode-free string rendered via StringWriter; var sw = new StringWriter(); liquidResult.WriteTo(sw, HtmlEncoder.Default); _htmlContentCache[key] = sw.ToString();
Defensive patterns
Strategy: try-catch
Validate before calling
bool canWrite = content is ViewBufferTextWriterContent v && v.ToString() != null; // prefer avoiding re-use entirely: cache strings instead of buffers
Type guard
bool IsRenderedOnce(IHtmlContent c) => c is ViewBufferTextWriterContent vbc && vbc.GetType().GetProperty("_builder", BindingFlags.NonPublic | BindingFlags.Instance) == null; Try / catch
try { content.WriteTo(writer, encoder); } catch (InvalidOperationException ex) when (ex.Message == "Buffer has already been rendered") { // re-render instead of reusing _logger.LogWarning("Buffer re-use detected; re-rendering"); content = await RenderFreshAsync(); content.WriteTo(writer, encoder); } Prevention
- Never cache IHtmlContent from Liquid/shape rendering — cache rendered strings
- Render each shape exactly once per request
- Watch for double writes (log + response) of the same shape instance
When it happens
Trigger: Calling WriteTo(writer, encoder) — typically from HtmlString/serialization code — on a ViewBufferTextWriterContent instance that was already rendered once; re-rendering a cached liquid shape/tagBuilder output; holding onto the IHtmlContent produced by a Liquid expression and re-emitting it.
Common situations: Caching an IHtmlContent result (e.g. in a dictionary or memoized tag helper) and writing it twice; double-rendering the same shape in a layout and a partial; async races where a render is attempted while/after the buffer was flushed.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Failed to retrieve the current endpoint route builder.
- DisplayAsync requires an instance of IShape
- Zone not found:
- ' . ' must not be empty. At least one ' ' is required to…
- Unable to find view ' '. The following locations were…
AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13).
Data as JSON: /api/errors/34dec29bf8f1ab8c.
Report an issue: GitHub.
Appendix: source
Thrown at src/OrchardCore/OrchardCore.DisplayManagement.Liquid/ViewBufferTextWriterContent.cs:212
public override void Write(StringBuilder value)
{
if (value != null)
{
foreach (var chunk in value.GetChunks())
{
if (!chunk.IsEmpty)
{
Write(chunk.Span);
}
}
}
}
public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{
if (_builder == null)
{
throw new InvalidOperationException("Buffer has already been rendered");
}
if (_previousPooledBuilders != null)
{
foreach (var pooledBuilder in _previousPooledBuilders)
{
foreach (var chunk in pooledBuilder.Builder.GetChunks())
{
if (!chunk.IsEmpty)
{
writer.Write(chunk.Span);
}
}
}
}
foreach (var chunk in _builder.GetChunks())
{View on GitHub (pinned to 4306c0717f)