awj
awj

Reputation: 7949

MVC: Reading the contents inside a custom HTML helper

I'm trying to build an HTML helper which conditionally modifies its content. The reason is so that I can move the contents to another part of the page (in the context of master page and child views) or remove them completely given some circumstances.

As an example, I might have some JavaScript that I either wish to move elsewhere (bear in mind that section objects only work if the view directly references the page which hosts that named section) or perhaps if I wish to qualify some JavaScript calls with a module namespace.

I have no problem in building the IDisposable which forms the main part of the HTML helper, and I know how to write contents into the page's output, but I can't figure out how to read in the contents.

For example, if I have this on my Razor view:

@using (Html.MyHtmlHelper())
{
    <div>hello world</div>
}

And then have

private static IDisposable MyHtmlHelper(this HtmlHelper htmlHelper)
{
    // ...
}

What do I need in that static helper method to retrieve the <div>hello world</div> contents?

Upvotes: 0

Views: 1991

Answers (2)

xDaevax
xDaevax

Reputation: 2022

This seems like a strange approach that may have an alternative solution that is more effective. One observation: everything inside of your using is static (hard coded) in the view. This means that there must be other external variables that affect what gets rendered since what is in the view is not dynamic. <div>My div</div> will never change, so what type of logic could be performed on it that wouldn't always result in the same thing?

I'll cover one reason why (there may be more), then propose an alternate solution.

Why?

Consider this example:

@using(Html.MyHtmlHelper()) {
    <div>Normal Text, no problem in theory</div>
    <span>@Model.MyCustomContent</span>
}

In this case, @Model is a dynamic value that isn't known until runtime and serves only as a place holder for some server side value to be replaced by actual content once the whole MVC pipeline is invoked (which will happen after you invoke your helper). What should the helper do in this case? It has no way to identify what @Model (or what MyCustomContent) is. Additionally, conditional logic and/or looping inside of the using would also pose a challenge in this case.

Perhaps more problematic (for similar reasons), would be something like this:

@using(Html.MyHtmlHelper()) {
    <div>Normal Text, no problem in theory</div>
    <span>@Model.MyCustomContent</span>
    @using(Html.MyHtmlHelper()) {
        <div>More nested custom stuff</div>
    }
}

In this case, what we want is for the inner using to execute first, render a string output, then for the outer using to execute, using the output rendered by the inner using, but this is not how the code will execute.

An Alternative:

Instead, consider having the contents you want to conditionally render be in a separate partial view:

//MyPartialView.cshtml

<div>Hello World</div>
@Model.CustomContent
<div>Dynamic Switcheroo</div>

Then you could say in your main view:

@Html.MyHtmlHelper("MyPartialView", Model)

In your extension method:

private static HtmlString MyHtmlHelper(this HtmlHelper htmlHelper, string viewName, object model)
{
    string output = htmlHelper.Partial(viewName, model).ToHtmlString();
    //TODO: Conditional string parsing based on output
    string formattedOutput = output.SomeOperation();
    return new HtmlString(formattedOutput);
}

A second alternative:

If you wanted, you could use properties of a model or context to determine what to render where, and when.

If you had a view model:

public class ConditionalRenderingViewModel {

    public bool ShouldRenderJavascript {get; set;}

}

Then, in your view:

@Html.MyHtmlHelper(Model)

Then, in your Html helper

public static HtmlString MyHtmlHelper(ConditionalRenderingViewModel model) {
    if(model.ShouldRenderJavascript) {
        return new HtmlString("<script type='text/javascript' src='customjs.js'></script>");
    } else {
        return new HtmlString("");
    }
}

This gives you the ability to control the output without parsing text or relying on hard-coded view markup.

You can see some additional strategies here: http://www.codemag.com/Article/1312081

Upvotes: 0

Anton
Anton

Reputation: 9961

You can call this code on initialization of helper:

var strContents = new HtmlTextWriter(htmlHelper.ViewContext.Writer).InnerWriter.ToString();

to get current string that is in your context.

And then compare result of same code on disposing this control to get content that is inside your helper.

Upvotes: 2

Related Questions