As per my comment above, it appears you need to manually keep OutputStack.Peek() and ViewContext.Writer in sync. The following helper method will do that:
private class WriterScope : IDisposable
{
private HtmlHelper _html;
private TextWriter _previous;
public WriterScope(HtmlHelper html, TextWriter writer)
{
_html = html;
_previous = _html.ViewContext.Writer;
_html.ViewContext.Writer = writer;
((WebPageBase)_html.ViewDataContainer).OutputStack.Push(writer);
}
public void Dispose()
{
var stack = ((WebPageBase) _html.ViewDataContainer).OutputStack;
if (stack.Peek() == _html.ViewContext.Writer)
_html.ViewContext.Writer = _previous;
stack.Pop();
_html = null;
_previous = null;
}
}
public static IDisposable Scope(this HtmlHelper html, TextWriter writer)
{
return new WriterScope(html, writer);
}
In the view it can be used like this:
@{
var sw = new StringWriter();
using (Html.Scope(sw))
{
<p>Line 2</p>
<p>@Html.Raw("Line 3")</p>
<p>Line 4</p>
}
}
<p>Line 1</p>
@Html.Raw(sw.ToString())
The page outputs the following as expected:
Line 1
Line 2
Line 3
Line 4