Razor: custom BeginForm()-like Razor disposable block doesn't work in some cases
Asked Answered
C

2

9

I have a custom implementation of a block that works much like Html.BeginForm(). The implementation is basically as follows:

public class MyBlock : IDisposable {
    private readonly HtmlHelper _html;

    public MyBlock(HtmlHelper hml) {
        this._html.ViewContext.Writer.WriteLine("BEGIN");
    }

    public void Dispose() {
        this._html.ViewContext.Writer.WriteLine("END");
    }
}

Then in my view I can do:

@using (new MyBlock(Html)) {
    @: some content
}

To get:

BEGIN
some content
END

This all works fine. However, I run into trouble when using my block inside a "razor snippet", e. g. when passing some razor content to a function which takes a Func<object, HelperResult> as an argument. For example, I have another HtmlHelper function defined as follows:

public static IHtmlString Content(this HtmlHelper @this, Func<object, HelperResult> razor) {
    return razor(null);
}

@* use in a view as: *@
@{
    var razorContent = Html.Content(@<div>Some razor content</div>);
}
@razorContent

When I do the following, though, the inner content renders without the outer content:

@{ 
    var content =Html.Content(
        @<text>
            @using (new MyBlock(Html)) {
                @: some content 2
            }
        <text>
    );
}
@content

I think the issue is that "Html" still refers to the HtmlHelper of the outer context, and thus BEGIN and END are sent to a different writer than "some content 2", however, I'm not sure that this is the case.

Does anyone know (1) what is going wrong and (2) how I can fix it?

Carincarina answered 2/1, 2013 at 22:1 Comment(0)
R
2

Partial solution of your problem is to invoke WriteTo method of HelperResult. You can change Content method to something like this:

public static void Content(this HtmlHelper @this, Func<object, HelperResult> razor)
{
    razor(null).WriteTo(@this.ViewContext.Writer);
}

and then use it that way:

@{ Html.Content(
        @<text>
            @using (new MyBlock(Html)) {
                @: some content 2
            }
        </text>
    );
}

EDIT

If you would like to return value as IHtmlString or any other string you can do this:

public static IHtmlString Content(this HtmlHelper @this, Func<object, HelperResult> razor)
{
    using (MemoryStream ms = new MemoryStream())
    using (TextWriter tw = new StreamWriter(ms))
    {
        Delegate @delegate = (Delegate)razor;
        WebViewPage target = (WebViewPage)@delegate.Target;
        TextWriter tmp = target.Html.ViewContext.Writer;
        try
        {
            target.Html.ViewContext.Writer = tw;
            razor(null).WriteTo(tw);
            tw.Flush();
            ms.Seek(0, SeekOrigin.Begin);
            TextReader tr = new StreamReader(ms);

            return MvcHtmlString.Create(tr.ReadToEnd());
        }
        finally
        {
            target.Html.ViewContext.Writer = tmp;
        }
    }
}
Regina answered 10/1, 2013 at 19:34 Comment(1)
My goal is to capture the content variable in an IHtmlString that can then be passed off somewhere else. It doesn't look like this solves that problem, right?Carincarina
M
0

This has been troubling me for some time too. It's a razor bug due to MyBlock and the HtmlHelper using different Writers. A reasonable workaround is to pass the HtlmHelper's __razor_template_writer into MyBlock. (Hopefully this will be fixed in vnext)

public class MyBlock : IDisposable {
    private readonly TextWriter _writer;

    public MyBlock(TextWriter writer) {
        _writer = writer;
        _writer.WriteLine("BEGIN");
    }

    public void Dispose() {
        _writer.WriteLine("END");
    }
}

@{ 
    var content =Html.Content(
        @<text>
            @using (new MyBlock(__razor_template_writer)) {
                @: some content 2
            }
        <text>
    );
}
@content
Moppet answered 1/9, 2014 at 19:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.