Ability to cache and reuse HtmlRenderer instances #431

Closed
opened 2026-01-29 14:36:31 +00:00 by claunia · 6 comments
Owner

Originally created by @AndreyChechel on GitHub (Feb 1, 2021).

There are some scenarios requiring ability to implement a custom cache of HtmlRenderer objects.
One scenario is when a MarkdownDocument needs to be parsed and processed first before rendering it into a string:

// 1. Parse and process a Markdown document:
var document = Markdown.Parse(text, _pipeline);
/* process the document here */

// 2. Render the document to a string:
//      it'd be nice to use a cached renderer,
//      instead of creating a new one each time.
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
_pipeline.Setup(renderer);

var renderedStr = renderer.Render(document);
// ...

Another scenario includes ability to create and cache HtmlRenderer objects having custom settings. Like the ones stripping Html from inlines implemented here.

The problem with caching is that a HtmlRenderer object needs to be reset after usage, but the TextRendererBase.Reset method is declared as internal, what makes reuse impossible. Do you think accessibility of the method can be revised to allow protected, or even public access?

Originally created by @AndreyChechel on GitHub (Feb 1, 2021). There are some scenarios requiring ability to implement a custom cache of `HtmlRenderer` objects. One scenario is when a `MarkdownDocument` needs to be parsed and processed first before rendering it into a string: ```cs // 1. Parse and process a Markdown document: var document = Markdown.Parse(text, _pipeline); /* process the document here */ // 2. Render the document to a string: // it'd be nice to use a cached renderer, // instead of creating a new one each time. var writer = new StringWriter(); var renderer = new HtmlRenderer(writer); _pipeline.Setup(renderer); var renderedStr = renderer.Render(document); // ... ``` Another scenario includes ability to create and cache `HtmlRenderer` objects having custom settings. Like the ones stripping Html from inlines implemented [here](https://github.com/xoofx/markdig/blob/3030b72f781f902cb947a173853de95de8924c6c/src/Markdig/Extensions/AutoIdentifiers/AutoIdentifierExtension.cs#L197-L202). The problem with caching is that a `HtmlRenderer` object needs to be reset after usage, but the [`TextRendererBase.Reset`](https://github.com/xoofx/markdig/blob/3030b72f781f902cb947a173853de95de8924c6c/src/Markdig/Renderers/TextRendererBase.cs#L93-L107) method is declared as `internal`, what makes reuse impossible. Do you think accessibility of the method can be revised to allow `protected`, or even `public` access?
claunia added the questionenhancement labels 2026-01-29 14:36:31 +00:00
Author
Owner

@xoofx commented on GitHub (Feb 2, 2021):

The problem with caching is that a HtmlRenderer object needs to be reset after usage, but the TextRendererBase.Reset method is declared as internal, what makes reuse impossible. Do you think accessibility of the method can be revised to allow protected, or even public access?

Could be. I don't have a look at its usage, but if it looks reasonable, PR welcome.

@xoofx commented on GitHub (Feb 2, 2021): > The problem with caching is that a `HtmlRenderer` object needs to be reset after usage, but the [`TextRendererBase.Reset`](https://github.com/xoofx/markdig/blob/3030b72f781f902cb947a173853de95de8924c6c/src/Markdig/Renderers/TextRendererBase.cs#L93-L107) method is declared as `internal`, what makes reuse impossible. Do you think accessibility of the method can be revised to allow `protected`, or even `public` access? Could be. I don't have a look at its usage, but if it looks reasonable, PR welcome.
Author
Owner

@MihaZupan commented on GitHub (Feb 2, 2021):

I think it's something that could be exposed. I'd prefer to change it to protected rather than public, as it can't always be used (depending on the writer) and custom renderer implementations must take care to reset any state they might have added.

@MihaZupan commented on GitHub (Feb 2, 2021): I think it's something that could be exposed. I'd prefer to change it to **protected** rather than public, as it can't always be used (depending on the writer) and custom renderer implementations must take care to reset any state they might have added.
Author
Owner

@liciniomendes commented on GitHub (Feb 22, 2021):

var pipeline = new MarkdownPipelineBuilder().Build();
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
if (baseUrl != null)
    renderer.BaseUrl = new Uri(baseUrl);

pipeline.Setup(renderer);

var html = Markdown.ToHtml(markdown, writer, pipeline);

Shouldn't we reuse and cache the render defined in the pipeline? If we set a value in BaseUrl we can't use the ToHtml static method.

@liciniomendes commented on GitHub (Feb 22, 2021): ```c# var pipeline = new MarkdownPipelineBuilder().Build(); var writer = new StringWriter(); var renderer = new HtmlRenderer(writer); if (baseUrl != null) renderer.BaseUrl = new Uri(baseUrl); pipeline.Setup(renderer); var html = Markdown.ToHtml(markdown, writer, pipeline); ``` Shouldn't we reuse and cache the render defined in the pipeline? If we set a value in `BaseUrl` we can't use the `ToHtml` static method.
Author
Owner

@AndreyChechel commented on GitHub (Feb 23, 2021):

@liciniomendes if I understand your question right, this issue makes it possible to implement a custom cache similar to how it's done here. Static ToHtml method works with internal renderers only (they're cached too).

@AndreyChechel commented on GitHub (Feb 23, 2021): @liciniomendes if I understand your question right, this issue makes it possible to implement a custom cache similar to how it's done [here](https://github.com/xoofx/markdig/blob/3030b72f781f902cb947a173853de95de8924c6c/src/Markdig/Extensions/AutoIdentifiers/AutoIdentifierExtension.cs#L192-L210). Static `ToHtml` method works with internal renderers only (they're cached too).
Author
Owner

@MihaZupan commented on GitHub (Feb 23, 2021):

@liciniomendes Markdown gives you a lot of extensibility with parsing and rendering, so you can potentially change a lot of things. This is why we can't try to cache custom renderers unless we're sure they are reset properly.

If you're using a custom renderer, you can't use the static ToHtml helper.

In your example, you'd have to do what ToHtml does internally:

// ... creating/getting the pipeline and renderer
pipeline.Setup(renderer);

// Parse and Render
var document = Parse(markdown, pipeline);
renderer.Render(document);
renderer.Writer.Flush();

string html = renderer.Writer.ToString();

// Reset and cache the pipeline/renderer

As @AndreyChechel mentioned, if you wanted to cache things, you would need a custom Renderer implementation (can just be a tiny wrapper over HtmlRenderer) to be able to call the protected Reset method.

The only way I can think of that Markdig could help with caching is if you had factory callbacks for the renderer

interface IResettableRenderer
{
    void Reset();
}

static string ToHtml(string markdown, MarkdownPipeline pipeline, Func<MarkdownPipeline, IMarkdownRenderer> rendererFactory, MarkdownParserContext context, out MarkdownDocument document)
{
    // I would be comfortable caching renderers returned by the factory here if they implemented IResettableRenderer
}
@MihaZupan commented on GitHub (Feb 23, 2021): @liciniomendes Markdown gives you a lot of extensibility with parsing and rendering, so you can potentially change a lot of things. This is why we can't try to cache custom renderers unless we're sure they are reset properly. If you're using a custom renderer, you can't use the static `ToHtml` helper. In your example, you'd have to do what `ToHtml` does internally: ```c# // ... creating/getting the pipeline and renderer pipeline.Setup(renderer); // Parse and Render var document = Parse(markdown, pipeline); renderer.Render(document); renderer.Writer.Flush(); string html = renderer.Writer.ToString(); // Reset and cache the pipeline/renderer ``` As @AndreyChechel mentioned, if you wanted to cache things, you would need a custom Renderer implementation (can just be a tiny wrapper over HtmlRenderer) to be able to call the protected Reset method. The only way I can think of that Markdig could help with caching is if you had factory callbacks for the renderer ```c# interface IResettableRenderer { void Reset(); } static string ToHtml(string markdown, MarkdownPipeline pipeline, Func<MarkdownPipeline, IMarkdownRenderer> rendererFactory, MarkdownParserContext context, out MarkdownDocument document) { // I would be comfortable caching renderers returned by the factory here if they implemented IResettableRenderer } ```
Author
Owner

@liciniomendes commented on GitHub (Feb 23, 2021):

Thank you both for the answers.

@MihaZupan that was what I ended up implementing using an extension method. Your solution for the static method is what I was thinking about. It simplifies/removes the boilerplate code that we will need to do if we have a scenario like this.

@liciniomendes commented on GitHub (Feb 23, 2021): Thank you both for the answers. @MihaZupan that was what I ended up implementing using an extension method. Your solution for the static method is what I was thinking about. It simplifies/removes the boilerplate code that we will need to do if we have a scenario like this.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/markdig#431