25

I have a helper called EditableArea which provides a user with a runtime-editable div (via JS). EditableArea helper checks if an editable area (not related to MVC's Area) with the specified ID exists in the DB, if so then it renders the area's HTML, otherwise it displays the default markup specified as a parameter of the helper:

@Html.EditableArea(someId, "<p>Click to edit contents</p>")

It all works ok, but I'd like to change it so that the default markup is specified not as a string but in razor syntax, something like:

@using (Html.EditableArea(someId))
{
    <p>Click to edit contents</p>
}

Or something similar, like the way @sections work in MVC3.

How can I achieve that?

I can make an IDisposable which in its Dispose closes the TagBuilder, etc., but using this approach the markup will still be rendered (I can clear the rendered contents in the Dispose() but the code block would still run unnecessarily, which I'd like to avoid).

Is there some other way to pass a razor block to the helper, which may or may not be actually rendered?

4 Answers 4

29

Here's an example I use to render jQuery Template markup by passing in a template Id and razor-style syntax for the template itself:

public static MvcHtmlString jQueryTmpl(this HtmlHelper htmlHelper, 
    string templateId, Func<object, HelperResult> template) 
{
    return MvcHtmlString.Create("<script id=\"" + templateId + 
        "\" type=\"x-jquery-tmpl\">" + template.Invoke(null) + "</script>");
}

and this would be called with

@Html.jQueryTmpl("templateId", @<text>any type of valid razor syntax here</text>)

Basically just use Func<object, HelperResult> as your parameter and template.Invoke(null) (with arguments if necessary) to render it. Obviously you can skip the call to .Invoke() to avoid rendering the "default" markup.

5
  • It doesn't work. The <text>...</text> syntax inside the function call isn't being recognized (invalid expression term '<')
    – Boris B.
    Commented Feb 6, 2012 at 11:13
  • 1
    Great, it works now. <text> tags aren't needed at all (@ is enough) unless the razor markup is in fact plain text without any tags, otherwise just @<tag> is enough.
    – Boris B.
    Commented Feb 6, 2012 at 11:27
  • 1
    That's correct about the <text> tags. I tend to use them just in case I ever change what goes in my templates - future proof it if you will. Commented Feb 6, 2012 at 11:29
  • Why is it passing null to the template function? What does the parameter do?
    – Triynko
    Commented Sep 27, 2016 at 18:40
  • 'IHtmlHelper<dynamic>' does not contain a definition for 'jQueryTmpl' and no accessible extension method 'jQueryTmpl' accepting a first argument of type 'IHtmlHelper<dynamic>' could be found (are you missing a using directive or an assembly reference?) no mvc core??
    – bh_earth0
    Commented May 10, 2019 at 9:17
3

Just to expand on the accepted answer, as it took me quite a while to resolve a similar problem and this is the question which popped up. What I really need was a @helper, which would accept razor text, as the template should contain quite some code. I played around for a long while trying to use several versions of type @helper item(Func<object, HelperResult> input), which I found on the web, with no success. Therefore I went for an approach like:

namespace project.MvcHtmlHelpers
{
    public static class HelperExtensions
    {
        public static MvcHtmlString RazorToMvcString(this HtmlHelper htmlHelper, Func<object, HelperResult> template)
        {
            return MvcHtmlString.Create(template.Invoke(null).ToString());
        }
    }
}

and

@project.MvcHtmlHelpers    
@helper item(other input, MvcHtmlString content)
    {
        <div class="item">
            ...other stuff... 
            <div class="content">
                @content
            </div>
        </div>
    }

and use this via

@item(other input, @Html.RazorToMvcString(@<text>this is a test</text>))

Now I can use the helper template for both Razor input, but I can also drop in partial views, which is handy at some points. As I am no expert there might be better options, but it seems like a flexible approach to me.

3

In case you're wondering this is how to do it in asp.net core 3.1

@{
    void TemplateFunc(Func<object, IHtmlContent> template)
    {
        <div>@template(null)</div>
    }
}

Then in markup you can use it as

<div>
@{TemplateFunc(@<div>123</div>);}
</div>
-1

Taking this further, it is possible to pass the markup directly to a helper, without an extension method.

@helper HelperWithChild(Func<object, HelperResult> renderChild)
{
    <div class="wrapper">
        @renderChild(this)
    </div>
}

@HelperWithChild(@<h1>Hello</h1>)

For multi-line markup <text> is required as well:

@HelperWithChild(@<text>
    @AnotherHelper()

    <h1>
        With more markup
    </h1>
</text>)

@helper AnotherHelper()
{
    <p>
        Another helper
    </p>
}

Though I'm not sure how this will play out with Model - my helpers only use their parameters.

Not the answer you're looking for? Browse other questions tagged or ask your own question.