ASP.Net MVC3 - Pass razor markup as a parameter

2019-02-04 06:23发布

问题:

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 default markup specified as a parameter to 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 it's Dispose closes the TagBuilder, etc., but I cannot avoid rendering the inner Razor markup even if the helper finds an area with an ID (I can clear the rendered contents in Dispose() but the code inside the { } would still run, which I'd like to skip).

And if i don't use using is there some other way to pass a razor block to the helper, which may or may not be actually rendered by the helper?

回答1:

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.



回答2:

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:

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.