dotnet / dotnet/aspnetcore

IHtmlContent WriteTo is invoked after dispose of parent component scope

Open
#48,681 4 comments 0 reactions 0 assignees View on GitHub
area-mvc
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

I am trying to develop a fluent framework for bootstrap (https://github.com/burcinsahin/FluentBootstrapCore) and I have an example usage as below.
```
@using (Html.Bootstrap().Form().Begin())
{
@Html.Bootstrap().Input()
}
```
Here container form dispose is invoked before Input WriteTo method. Input is some class extends.NetCore Built-in IHtmlContent interface.
Begin writes to HtmlHelper ViewContext to manually and after dispose it ends the tag in the same way. Here the html output written to page is correct and I do not have any problem there.
```
public class ComponentBuilder : IDisposable
where TComponent : SingleComponent
{
protected readonly IHtmlHelper _htmlHelper;
protected readonly TComponent _component;

internal IHtmlHelper HtmlHelper => _htmlHelper;
internal TComponent Component => _component;

public ComponentBuilder(IHtmlHelper htmlHelper, TComponent component)
{
_htmlHelper = htmlHelper;
_component = component;
_htmlHelper.ViewContext.Writer.Write(_component.Begin());
_htmlHelper.ViewContext.Writer.Write(_component.Body());
}

public void Dispose()
{
Debug.WriteLine($"ComponentBuilder.Dispose for {typeof(TComponent).Name} invoked.");
_htmlHelper.ViewContext.Writer.Write(_component.End());
}
}
```
```
public class BootstrapContent : IHtmlContent
where TComponent : SingleComponent
{
private readonly TComponent _component;
private readonly IHtmlHelper _htmlHelper;

internal TComponent Component => _component;
internal IHtmlHelper HtmlHelper => _htmlHelper;

public BootstrapContent(IHtmlHelper htmlHelper, TComponent component)
{
_htmlHelper = htmlHelper;
_component = component;
}

public void WriteTo(TextWriter writer, HtmlEncoder encoder)
{
Debug.WriteLine($"BootstrapContent.WriteTo for {typeof(TComponent).Name} invoked.");
var html = _component.ToHtml();
writer.Write(html);
}

public ComponentBuilder Begin()
{
Debug.WriteLine($"BootstrapContent.Begin for {typeof(TComponent).Name} invoked.");
return new ComponentBuilder(_htmlHelper, _component);
}
}
```
Problem is: I have a stack for components and because of this interesting order of invokes, I can not push or pop the stack in the correct order. Parent is disposed and empties the stack then the component inside starts its operations and finds an empty stack when needed.

Output is as below:
```
BootstrapContent.Begin for Form invoked.
ComponentBuilder.Dispose for Form invoked.
BootstrapContent.WriteTo for Input invoked.
```
Here is the call stack (ExternalCode) where WriteTo method invoked:
[![screencaptured][1]][1]

Note: I use .Net (Core) 6 for application layer and .NetStandart 2.1 for class libraries, when I changed all them to .Net (Core) 7 it still behaves in the same way. I have created a branch named "issue/dispose" for anyone who wants to review this issue.

So, this problem seems as a big issue and blocks me. Anyone who knows why the dispose method is invoked before the component inside the "using" scope.

Thanks in advance.

[1]: https://i.stack.imgur.com/nT3vP.png

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the reported order using ComponentBuilder.Dispose, BootstrapContent.Begin, and BootstrapContent.WriteTo, then inspect the ViewContext.Writer calls and the provided call stack. Compare the observed lifecycle with the issue's using-scope example and determine whether the behavior is an ASP.NET Core defect or an expected rendering order; a useful outcome should document the cause or identify a narrowly scoped fix.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend, web-dev
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.