Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 342
ChaseMedallion Avatar asked Jan 02 '13 22:01

ChaseMedallion


1 Answers

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;
        }
    }
}
like image 132
Sławomir Rosiek Avatar answered Oct 29 '22 19:10

Sławomir Rosiek