44

Using MVC 3 RTM I'm getting a strange NullReferenceException:

@helper TestHelperMethod() {
    var extra = "class=\"foo\"";
    <div @Html.Raw(extra)></div>
}

It turns out that Html (of type HtmlHelper) is null.

I've never seen this before in a regular view. I'm starting to experiment with declarative helper methods in Razor (so far they seem a little limited) and I'm quite stumped by what I'm seeing here.

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742

6 Answers6

42

Using Drew Noakes suggestion, I have come to a workaround that does the trick for now and that can be easily removed once the issue is solved in a newer version of MVC (that is if more stuff isn't changed that would break it:))

The goal is to be able to use an HtmlHelper inside a declarative helper method that lives in a file in App_Code without having a NullReferenceException. To solve this I included in all the files in App_Code the following:

@using System.Web.Mvc;

@functions
{
    private static new HtmlHelper<object> Html
    {
        get { return ((WebViewPage)CurrentPage).Html; }
    }

    private static UrlHelper Url
    {
        get { return ((WebViewPage)CurrentPage).Url; }
    }
}

This does seem to do the trick as I can now write the following helper (in the same file):

@helper PrintAsRaw(string htmlString)
{
     @Html.Raw(htmlString)
}

Obviously the helper method is just an example. This solution has the downside that the @functions declarations has to be introduced in all helper declaration files in App_Code, but does avoid complicating the call to the helper, as you can simply write in a view:

@MyAppCodeFile.PrintAsRaw("<p>My paragraph</p>")

Hope this helps...

Community
  • 1
  • 1
kkara
  • 811
  • 1
  • 10
  • 11
  • 1
    Great, I look forward to giving this a try. Are there any downsides you've found to this approach? – Drew Noakes Aug 11 '11 at 12:58
  • No, not yet at least... I've also applied the same idea for the UrlHelper and I can now call the UrlHelper methods in the delegate helper methods. Let me know if you encounter anything strange. – kkara Aug 11 '11 at 14:23
  • It seems I found a problem with this approach, at least when using the UrlHelper. Obviously @marcind knows better when he says that the HtmlHelper is not the same in that case, which I suppose is true for the UrlHelper as well. Anyway, using the UrlHelper gave me a NullReferenceException in the method System.Web.Mvc.UrlRewriterHelper.WasThisRequestRewritten(HttpContext). It seems that the ServerVariables collection that is obtained through the HttpContext of this UrlHelper is not the same as the usual ServerVariables collection (that does not throw that exception)... – kkara Aug 16 '11 at 14:51
  • Another advise: Include in the app_code file a using to the System.Web.Mvc.Html namespace, in order to call all the rest of the extension methods. Still no luck with the Url generation problem though :( – kkara Aug 17 '11 at 20:13
  • 1
    This worked for me - allowed me to call a compiled helper via a declarative helper when I added this function declaration to the top of the declarative helper. – Glenn Barnett Nov 02 '11 at 14:57
  • 1
    Very nice! Saved me some grief. – Trevor de Koekkoek Jul 28 '12 at 21:59
35

That's a known limitation of those helpers. One possibility is to pass it as parameter:

@helper TestHelperMethod(HtmlHelper html) {
    var extra = "class=\"foo\"";
    <div@html.Raw(extra)></div>
}

Another possibility is to write the helper as an extension method:

public static class HtmlExtensions
{
    public static MvcHtmlString TestHelperMethod(this HtmlHelper)
    {
        var div = new TagBuilder("div");
        div.AddCssClass("foo");
        return MvcHtmlString.Create(div.ToString());
    }
}

and then:

@Html.TestHelperMethod()
Community
  • 1
  • 1
Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
  • Thanks Darin. Actually I was trying to convert from a helper method in code to a declarative helper. They are so very promising, but this limitation and the inability to use type arguments is a real shame. From the code in my answer I wonder whether a simple change to the `Html` property getter might provide this support. `WebViewPage` has an `HtmlHelper` property which could be read and returned. – Drew Noakes Mar 13 '11 at 16:37
  • 参数 1: 无法从“System.Web.Mvc.HtmlHelper>”转换为“System.Web.WebPages.Html.HtmlHelper” :( – deerchao May 19 '11 at 03:06
  • how to call this "TestHelperMethod(HtmlHelper html)" from View?? – Praveen Prasad Jul 06 '11 at 09:53
  • 2
    @PraveenPrasad, I see no one ever answered your question. This is a little late, but, to call his first solution (declarative helper) you would pass the local instance of the "System.Web.Mvc.HtmlHelper" object from your calling code as: TestHelperMethod(Html). Be sure to fully qualify the namespace as "HtmlHelper" exists in 2 namespaces as mentioned above and seems to default to the wrong one (at least it did in my code). – Richard B Jan 13 '12 at 15:16
  • Hi! I have created an idea in ASP.NET user voice related to this: [Support @helper ExtensionMethod(this HtmlHelper html) for views in APP_CODE](http://aspnet.uservoice.com/forums/41201-asp-net-mvc/suggestions/3670180-support-helper-extensionmethod-this-htmlhelper-ht)... help is appreciated in making it come to the front-lines... Thanks! – Miguel Angelo Feb 20 '13 at 02:05
  • @Darin then with the first solution, how can we pass it to helper class – Sara N Mar 08 '17 at 04:11
26

I know it's not the point but if it is just Html.Raw(value) you were hoping to use when finding this question on Google (as I was) according to the source code of System.Web.Mvc.dll all Html.Raw does is:

public IHtmlString Raw(string calue)
{
    return new HtmlString(value);
}

So I've just used @(new HtmlString(value)) in my helper which works nicely.

BritishDeveloper
  • 13,219
  • 9
  • 52
  • 62
19

Just replace

 @Html.Raw(extra)

with

@(new HtmlString(extra))
Otto Kanellis
  • 3,629
  • 1
  • 23
  • 24
5

I think I know what's causing the issue...

The definition of the Html property getter is:

public static HtmlHelper Html {
    get { 
        WebPage currentWebPage = CurrentPage as WebPage;
        if (currentWebPage == null) {
            return null;
        } 
        return currentWebPage.Html;
    } 
} 

Setting a breakpoint in my helper method shows that CurrentPage is not in fact an instance of WebPage, hence the null value.

Here is the type hierarchy of CurrentPage (my class names doctored slightly):

ASP._Page_Views_mycontroller_View_cshtml
  My.Site.MyWebViewPage`1
    System.Web.Mvc.WebViewPage`1
      System.Web.Mvc.WebViewPage
        System.Web.WebPages.WebPageBase
          System.Web.WebPages.WebPageRenderingBase
            System.Web.WebPages.WebPageExecutingBase
              System.Object

Note that the base class of my view has been specified in Web.config:

<system.web.webPages.razor>
  <pages pageBaseType="My.Site.MyWebViewPage">
    ...

Which is defined both in generic and non-generic form:

public abstract class MyWebViewPage : WebViewPage { ... }
public abstract class MyWebViewPage<TModel> : WebViewPage<TModel> { ... }

So, if this problem does not occur for others, perhaps they're not using a custom pageBaseType.

Note too that I've placed the @helper declaration in App_Code\Helpers.cshtml in the hope of making it globally accessible.

Am I doing something wrong, or is this a bug?

EDIT Thanks Darin for pointing out this as a known issue. Still, why isn't the Html property redefined as:

public static HtmlHelper Html {
    get { 
        WebPage currentWebPage = CurrentPage as WebPage;
        if (currentWebPage != null) {
            return currentWebPage.Html;
        } 
        WebViewPage currentWebViewPage = CurrentPage as WebViewPage;
        if (currentWebViewPage != null) {
            return currentWebViewPage.Html;
        } 
        return null;
    } 
} 
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • it's not as straightforward. First of all, System.Web.WebPages.dll does not have a dependency on System.Web.Mvc.dll, so you can't have a reference to `WebViewPage`. Secondly, this `HtmlHelper` is actually a different type than the `HtmlHelper` in MVC, so this won't work. If it was this easy we certainly would have done it. – marcind Mar 13 '11 at 18:09
  • @marcind, fair enough. I didn't expect I'd have dug as deeply as the team who built the API itself :) I really like the idea of these declarative helpers, so whatever work can be done to support project-wide access and type parameters would be fantastic. – Drew Noakes Mar 15 '11 at 08:02
  • 2
    we certainly realize that the current story with helpers is insufficient and we plan on addressing that in the next version. – marcind Mar 15 '11 at 15:50
  • @marcind - glad to hear this will be addressed! In the meantime, does this mean that there is no way to have unencoded HTML in a declarative helper? (short of passing in the HtmlHelper as a parameter which is kludgy)? What would you suggest here? – Chris Rogers May 09 '11 at 02:25
  • 1
    @ChrisRogers: I've found that just using `new HtmlString` in place of `Html.Raw` works. – mpen Dec 04 '11 at 23:25
  • Well it's now 2015 and I'm using MVC5 and the Html helper is still null in declarative helpers. I'm thinking I missed something somewhere, but it looks like it wasn't ever addressed. – BardMorgan Jul 29 '15 at 19:14
1

I had the same issue and this line of code did the trick. It´s not a solution for using HtmlHelper, it's just a way of writing RAW html in a declarative razor helper.

@{CurrentPage.WriteLiteral("html code");}
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
santi
  • 487
  • 2
  • 4
  • 10