122

I was trying to create a Razor declarative helper in my App_Code folder for an MVC 3 RTM project.

The problem I ran into was that the MVC HtmlHelper extensions, like ActionLink, aren't available. This is because the compiled helpers derive from System.Web.WebPages.HelperPage, and though it exposes an Html property, its of type System.Web.WebPages.HtmlHelper rather than System.Web.Mvc.HtmlHelper.

An example of the kind of error I was getting is:

'System.Web.Mvc.HtmlHelper' does not contain a definition for 'ActionLink' and no extension method 'ActionLink' accepting a first argument of type 'System.Web.Mvc.HtmlHelper' could be found (are you missing a using directive or an assembly reference?)

My only solution has been to create my own HelperPage and override the Html property:

using System.Web.WebPages;

public class HelperPage : System.Web.WebPages.HelperPage 
{
    // Workaround - exposes the MVC HtmlHelper instead of the normal helper
    public static new HtmlHelper Html
    {
        get { return ((System.Web.Mvc.WebViewPage) WebPageContext.Current.Page).Html; }
    }
}

I then have to write the following at the top of every helper:

@inherits FunnelWeb.Web.App_Code.HelperPage
@using System.Web.Mvc
@using System.Web.Mvc.Html

@helper DoSomething()
{
    @Html.ActionLink("Index", "Home")
}

Is it meant to be this hard in MVC 3, or am I doing something wrong?

Alex Duggleby
  • 7,948
  • 5
  • 37
  • 44
Paul Stovell
  • 32,377
  • 16
  • 80
  • 108
  • 4
    If you need also the Url helper you culd add this line of code to HelperPage: public static UrlHelper Url { get { return new UrlHelper(Html.ViewContext.RequestContext); } } – Marco Staffoli Oct 24 '11 at 13:14

9 Answers9

42

Take a look at Marcind's answer to this question. What you're experiencing is a limitation of putting declarative views in the App_Code folder.

Putting your helpers in App_Code works but has certain limitations that impact certain MVC scenarios (for example: no access to standard MVC Html. helpers)

Community
  • 1
  • 1
Omar
  • 39,496
  • 45
  • 145
  • 213
  • 1
    Note to other readers: this answer is absolutely correct, but also check out Andrew Nurse's additional contribution below for a potential workaround. – Jordan Gray Mar 14 '13 at 10:42
38

I created an extension method for the WebPages helper so that I can get access to the page helper.

public static HtmlHelper GetPageHelper(this System.Web.WebPages.Html.HtmlHelper html)
{
 return ((System.Web.Mvc.WebViewPage) WebPageContext.Current.Page).Html;
}
Jake Hoffner
  • 1,037
  • 9
  • 17
  • 4
    Usage: `@Html.GetPageHelper().ActionLink("actioname")` – deerchao Nov 09 '11 at 09:04
  • This works for me, but I had to add `@using System.Web.Mvc` and `@using System.Web.Mvc.Html` into cshtml helpers file inside App_Code – Tomino May 19 '15 at 09:04
  • 1
    Why is there are separate HtmlHelper class? It should be the same whether it's in App_Code or Views. Epic half-implemented design fail. – Triynko Jul 26 '15 at 17:34
  • Got referred here by https://weblogs.asp.net/scottgu/asp-net-mvc-3-and-the-helper-syntax-within-razor which does a good job describing how to create "global" Razor helpers. So then, if you only need the `HtmlHelper` class for encoding purposes, I found an even quicker way to do this is via the static class `Microsoft.Security.Application.Encoder` as in: `Encoder.HtmlAttributeEncode(value)` – Matt Borja Jun 16 '16 at 21:36
11

Omar's got the right answer here, but I wanted to add something (do feel free to mark Omar's response as the answer).

We were aware of this in v1 and weren't able to get a great fix in the product, but David Ebbo (an architect on the ASP.Net team) posted a sample of a Visual Studio Code Generator that is basically a first exploration of the kind of ideas we're looking at to make this work properly: http://blogs.msdn.com/b/davidebb/archive/2010/10/27/turn-your-razor-helpers-into-reusable-libraries.aspx

Try that out and see what you think! Let David know if you have comments by posting on his blog.

Andrew Stanton-Nurse
  • 6,274
  • 1
  • 28
  • 20
  • 2
    Are there plans to fix this in the next version of Razor? I noticed this is still an issue in VS 11 Beta. – Omar Mar 04 '12 at 04:43
9

Similar to @Jakes answer:

public static class MvcIntrinsics {
    public static System.Web.Mvc.HtmlHelper Html {
        get { return ((System.Web.Mvc.WebViewPage)WebPageContext.Current.Page).Html; }
    }

    public static System.Web.Mvc.AjaxHelper Ajax {
        get { return ((System.Web.Mvc.WebViewPage)WebPageContext.Current.Page).Ajax; }
    }

    public static System.Web.Mvc.UrlHelper Url {
        get { return ((System.Web.Mvc.WebViewPage)WebPageContext.Current.Page).Url; }
    }

}

Usage:

@MvcIntrinsics.Html.Raw("test")

Source: Dino Esposito - Programming Microsoft ASP.NET MVC

Greg Gum
  • 33,478
  • 39
  • 162
  • 233
  • 1
    It's ridiculous that this is even necessary, and that these aren't just part of the base class for views in App_Code. – Triynko Jul 26 '15 at 17:36
8

An alternative solution:

Add this on top of your razor-helper file:

@functions {
    public static System.Web.Mvc.HtmlHelper<object> HHtml = ((System.Web.Mvc.WebViewPage)WebPageContext.Current.Page).Html;
}

then call it like this:

@HHtml.ActionLink("actionname")
Alex from Jitbit
  • 53,710
  • 19
  • 160
  • 149
  • This looked like it worked initially for me but when calling the helper again I got 'value does not fall within the expected range'. – d219 Aug 06 '18 at 22:26
3

For the benefit of searchers, I got the same error when creating MVC views as part of a class library (for component re-use). The solution, partially alluded to above, was to add the following using statements at the top of the .cshtml file:

@using System.Web.Mvc
@using System.Web.Mvc.Html

No further work necessary.

Bardicer
  • 1,405
  • 4
  • 26
  • 43
JsAndDotNet
  • 16,260
  • 18
  • 100
  • 123
  • My helpers in my .cshtml under App_Code were not recognized in Intellisense. Adding @using System.Web.Mvc.Html at the top of my .cshtml under App_code worked. –  Nov 23 '12 at 19:44
  • When I do this, I get `"Could not load type 'System.Web.WebPages.Instrumentation.InstrumentationService' from assembly 'System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'."` when hovering `@using System.Web.Mvc`. Any ideas? – JoeBrockhaus Sep 04 '13 at 15:51
  • That makes no difference for me – mems Jan 11 '19 at 17:42
3

My approach to this is to simply pass the page as a parameter to the helper method. So in your example it would be:

@helper DoSomething(WebViewPage page)
{
    @page.Html.ActionLink("Index", "Home")
}

Then in your Razor view where you need it call it like this:

@YourHelperFilename.DoSomething(this)

Doing this immediately gives you access to page properties like Html or Url that you usually have (and through that the HtmlHelper extensions).

As an additional benefit (if you require this), you also get access to instance properties such as the page's ViewData.

Dejan
  • 9,150
  • 8
  • 69
  • 117
0

Looks like the ASP.NET MVC has fixed this issue in VS 2013. See this post http://aspnet.uservoice.com/forums/41201-asp-net-mvc/suggestions/3670180-support-helper-extensionmethod-this-htmlhelper-ht

Omar
  • 39,496
  • 45
  • 145
  • 213
  • Nope, not at all. Intellisense is not picking up extension methods, and I have VS2013 update 5. I have `@using System.Web.Mvc.Html` at the top of the cshtml file in App_Code, but writing @Html.... reveals none of the extension methods such as `EditorFor`. It's rediculous that this isn't working after 2 major releases and blog posts claiming it was implemented. It's not. In fact, the extention methods can't work, because they target the System.Web.Mvc.HtmlHelper class, not the System.Web.WebPages.HtmlHelper class, which is exposed by the System.Web.WebPages.HelperPage class. – Triynko Jul 26 '15 at 17:50
0

I know that there are some intellisense issues with MVC 3. I think the helpers will still work if you have the namespace set in web.config.

MVC 3 RTM has just been realeased are you using this or a beta?

Lee Smith
  • 6,339
  • 6
  • 27
  • 34