Changing Base Type Of A Razor View

asp.net, asp.net mvc, razor 0 comments suggest edit

Within a Razor view, you have access to a base set of properties (such as Html, Url, Ajax, etc.) each of which provides methods you can use within the view.

For example, in the following view, we use the Html property to access the TextBox method.

<code.
@Html.TextBox("SomeProperty")
</code>

Html is a property of type HtmlHelper and there are a large number of useful extension methods that hang off this type, such as  TextBox.

But where did the Html property come from? It’s a property of System.Web.Mvc.WebViewPage, the default base type for all razor views. If that last phrase doesn’t make sense to you, let me explain.

Unlike many templating engines or interpreted view engines, Razor views are dynamically compiled at runtime into a class and then executed. The class that they’re compiled into derives from WebViewPage. For long time ASP.NET users, this shouldn’t come as a surprise because this is how ASP.NET pages work as well.

Customizing the Base Class

HTML 5 (or is it simply “HTML” now) is a big topic these days. It’d be nice to write a set of HTML 5 specific helpers extension methods, but you’d probably like to avoid adding even more extension methods to the HtmlHelper class because it’s already getting a little crowded in there.

html-extensions

Well perhaps what we need is a new property we can access from within Razor. Well how do we do that?

What we need to do is change the base type for all Razor views to something we control. Fortunately, that’s pretty easy. When you create a new ASP.NET MVC 3 project, you might have noticed that the Views directory contains a Web.config file.

Look inside that file and you’ll notice the following snippet of XML.

<system.web.webPages.razor>
    <host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, 
    System.Web.Mvc, Version=3.0.0.0, 
    Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
  <pages pageBaseType="System.Web.Mvc.WebViewPage">
    <namespaces>
      <add namespace="System.Web.Mvc" />
      <add namespace="System.Web.Mvc.Ajax" />
      <add namespace="System.Web.Mvc.Html" />
      <add namespace="System.Web.Routing" />
    </namespaces>
  </pages>
</system.web.webPages.razor>

The thing to notice is the <pages> element which has the pageBaseType attribute. The value of that attribute specifies the base page type for all Razor views in your application. But you can change that value by simply replacing that value with your custom class. While it’s not strictly required, it’s pretty easy to simply write a class that derives from WebViewPage.

Let’s look at a simple example of this.

public abstract class CustomWebViewPage : WebViewPage {
  public Html5Helper Html5 { get; set; }

  public override void InitHelpers() {
    base.InitHelpers();
    Html5 = new Html5Helper<object>(base.ViewContext, this);
  }
}

Note that our custom class derives from WebViewPage, but adds a new Html5 property of type Html5Helper. I’ll show the code for that helper here. In this case, it pretty much follows the pattern that HtmlHelper does. I’ve left out some properties for brevity, but at this point, you can add whatever you want to this class.

public class Html5Helper {
  public Html5Helper(ViewContext viewContext, 
    IViewDataContainer viewDataContainer)
    : this(viewContext, viewDataContainer, RouteTable.Routes) {
  }

  public Html5Helper(ViewContext viewContext,
     IViewDataContainer viewDataContainer, RouteCollection routeCollection) {
    ViewContext = viewContext;
    ViewData = new ViewDataDictionary(viewDataContainer.ViewData);
  }

  public ViewDataDictionary ViewData {
    get;
    private set;
  }

  public ViewContext ViewContext {
    get;
    private set;
  }
}

Let’s write a simple extension method that takes advantage of this new property first, so we can get the benefits of all this work.

public static class Html5Extensions {
    public static IHtmlString EmailInput(this Html5Helper html, string name,       string value) {
        var tagBuilder = new TagBuilder("input");
        tagBuilder.Attributes.Add("type", "email");
        tagBuilder.Attributes.Add("value", value);
        return new HtmlString(tagBuilder.ToString());
    }
}

Now, if we change the pageBaseType to CustomWebViewPage, we can recompile the application and start using the new property within our Razor views.

Html5Helpers

Nice! We can now start using our new helpers. Note that if you try this and don’t see your new property in Intellisense right away, try closing and re-opening Visual Studio.

What about Strongly Typed Views

What if I have a Razor view that specifies a strongly typed model like so:


@model Product
@{
    ViewBag.Title = "Home Page";
}

<p>@Model.Name</p>

The base class we wrote wasn’t a generic class so how’s this going to work? Not to worry. This is the part of Razor that’s pretty cool. We can simply write a generic version of our class and Razor will inject the model type into that class when it compiles the razor code.

In this case, we’ll need a generic version of both our CustomWebViewPage and our Html5Helper classes. I’ll follow a similar pattern implemented by HtmlHelper<T> and WebViewPage<T>.

public abstract class CustomWebViewPage<TModel> : CustomWebViewPage {
  public new Html5Helper<TModel> Html5 { get; set; }

  public override void InitHelpers() {
    base.InitHelpers();
    Html5 = new Html5Helper<TModel>(base.ViewContext, this);
  }
}

public class Html5Helper<TModel> : Html5Helper {
  public Html5Helper(ViewContext viewContext, IViewDataContainer container)
    : this(viewContext, container, RouteTable.Routes) {
  }

  public Html5Helper(ViewContext viewContext, IViewDataContainer container, 
      RouteCollection routeCollection) : base(viewContext, container,
      routeCollection) {
    ViewData = new ViewDataDictionary<TModel>(container.ViewData);
  }

  public new ViewDataDictionary<TModel> ViewData {
    get;
    private set;
  }
}

Now you can write extension methods of Html5Helper<TModel> which will have access to the model type much like HtmlHelper<TModel> does.

As usual, if there’s a change you want to make, there’s probably an extensibility point in ASP.NET MVC that’ll let you make it. The tricky part of course, in some cases, is finding the correct point.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

43 responses

  1. Avatar for Martin
    Martin February 21st, 2011

    Why are the methods of Html5Helper implemented as extension methods instead of "normal" methods? Is there any advantage?

  2. Avatar for Jesse
    Jesse February 21st, 2011

    I ran into this exact problem doing, literally, the exact same thing a few weeks ago. Now I know the solution, but my cool idea for some separate HTML5 helpers doesn't seem so cool. So...thanks? :)

  3. Avatar for Macu
    Macu February 21st, 2011

    There is a problem with this special web.config file hidden in view directory. If you try to plug mvc directly into source codes some of the razor intelisence will break. (set PublicKeyToken of MvcWebRazorHostFactory to null and plug your own Razor dlls ) . I other words try not to define your extensibility point in mvc soruce codes or you will end up without intelisence support.
    Best

  4. Avatar for haacked
    haacked February 21st, 2011

    @Martin they could have been normal methods. It's just an example of how I can add more methods later without changing the Html5Helper class.

  5. Avatar for xgluxv
    xgluxv February 21st, 2011

    Can we change base type of a Razor view with code ,not change web.config ?

  6. Avatar for jbogard
    jbogard February 21st, 2011

    Cool stuff. How is the generic type chosen? Does it need to have the same name as the non-generic version, or is there additional web.config changes needed?

  7. Avatar for Koistya `Navin
    Koistya `Navin February 21st, 2011

    Good hint. Going to use it :)

  8. Avatar for haacked
    haacked February 21st, 2011

    @xgluxv No, I don't believe so.
    @Jimmy I believe it goes by name because I didn't need to make any additional web.config changes. But I'll follow up to find out for sure.

    I confirmed, it is indeed by name. In C#, you actually always need the generic type because even without a model, the view is typed as dynamic by default.

  9. Avatar for Nathan
    Nathan February 21st, 2011

    Out of curiosity, can something similar be done with WebMatrix and System.Web.WebPages.WebPage?

  10. Avatar for Allan Ferreira
    Allan Ferreira February 22nd, 2011

    Great article! Any insights about how the Execute method works? I mean, when in the pipeline does the class gets created (inheriting from the class defined in the web.config) and when does the method Execute itself created/called?

  11. Avatar for Tim Scott
    Tim Scott February 22nd, 2011

    There certainly is an advantage to extending the class instead of a property of it. For the big payoff, go one step further and extend only interfaces.
    Case in point, MvcContrib.FluentHtml. Some years ago, we created a whole bunch of helpers. The helpers extend the existing MVC framework interface. We added our own interface as well and extended it.
    And the payoff? All of FluentHtml extensions are available in Razor by adding one very small class. It took like 30 minutes.

  12. Avatar for Kourosh Saleh
    Kourosh Saleh February 23rd, 2011

    Hi,
    Thank you for your real valuable blog.
    I am newbie on MVC and trying to learn.
    when I try your solution I got following error.
    The type or namespace name 'CustomWebVeiwPage' does not exist in the namespace 'MvcApplication1' (are you missing an assembly reference?)
    thank you again.

  13. Avatar for ReFocus
    ReFocus February 24th, 2011

    Is it possible to create your own implementation of the MvcWebRazorHostFactory and inject code into the views?

  14. Avatar for Thanigainathan
    Thanigainathan February 27th, 2011

    Hi,
    One thing that bothers me in this article is that Razor views are dynamically compiled.Will this be a performance impact ?
    Thanks,
    Thani

  15. Avatar for Jonas Eriksson
    Jonas Eriksson March 2nd, 2011

    Is it possible to add a separate web.config in a subfolder with just that section and a base class for the cshtml's in that folder?

  16. Avatar for Jonas
    Jonas March 2nd, 2011

    Do'h, of course it does. *delete* *won't post again when I'm too tired*

  17. Avatar for haacked
    haacked March 4th, 2011

    @Kouroush Looks like you mispelled it. It should be CustomWebViewPage not CustomWebVeiwPage.
    @Nathan, I'm not sure.
    @Allan, the class is created and compiled very early in the pipeline once. The execute method is called much later.
    @Refocus Yes, you can. Takes a bit of work though.
    @Thanigainathan It's only a perf hit on the first request. After that, it's very fast because it's all compiled.

  18. Avatar for Andrew Nurse
    Andrew Nurse March 7th, 2011

    @Thanigainathan - Just to follow up on Phil's answer: Razor uses exactly the same compilation model as ASPX, so the performance after the first request is very similar to ASPX. Razor does take a little more time to do that initial compilation due to it being a more complex language to parse, but that's only on the first request.

  19. Avatar for Berlin dong
    Berlin dong March 7th, 2011

    CustomWebViewPage<TModel> should derive from WebViewPage<TModel>, not WebViewPage.

  20. Avatar for Mare
    Mare March 31st, 2011

    Macu pointed out to the problems with intellisense when changing the web.config file in Views folder and I can confirm it. It makes everything to break (no other helpers are recognized/red underlined) and our custom helpers are not available though they do work when you run the site. It's just in the VS that they get broken.
    Could you please investigate on that?

  21. Avatar for Mark
    Mark April 8th, 2011

    This is the coolest tip I've run into in several weeks! Thanks for sharing it.
    BTW, I don't know if anyone else noticed, but unless you define your custom class in the same namespace as the MVC default intellisense breaks down.

  22. Avatar for DrMsal
    DrMsal April 23rd, 2011

    Nice Idea but its not working...
    Extension method is not showing up. its not even accessible. here is code I am using..
    public abstract class CustomWebViewPage : WebViewPage
    {
    public Html5Helper Html5 { get; set; }
    public override void InitHelpers()
    {
    base.InitHelpers();
    Html5 = new Html5Helper(base.ViewContext, this);
    }
    }
    }
    ++++++++++++++++++++
    public class Html5Helper
    {
    public Html5Helper(ViewContext viewContext,
    IViewDataContainer viewDataContainer)
    : this(viewContext, viewDataContainer, RouteTable.Routes)
    {
    }
    public Html5Helper(ViewContext viewContext,
    IViewDataContainer viewDataContainer, RouteCollection routeCollection)
    {
    ViewContext = viewContext;
    ViewData = new ViewDataDictionary(viewDataContainer.ViewData);
    }
    public ViewDataDictionary ViewData
    {
    get;
    private set;
    }
    public ViewContext ViewContext
    {
    get;
    private set;
    }
    }
    +++++++++++++++++++++++ And Extension Method +++++++++++
    public static class Html5Extensions
    {
    public static IHtmlString GetMyName(this Html5Helper html)
    {
    var tagBuilder = new TagBuilder("span");
    tagBuilder.SetInnerText("Hello World");
    return new HtmlString(tagBuilder.ToString());
    }
    }

  23. Avatar for DrMsal
    DrMsal April 24th, 2011

    Got it finally.
    when you set the base type in webconfig file it set itself as strongly type by default. So I put @inherits directive on view itself make non strongly type. that make it work.
    Is it correct way?? or is there any other better way of doing the same ???

  24. Avatar for Sam Moore
    Sam Moore July 5th, 2011

    Hey Phil,
    I've been playing around with this recently and I ran into a bit of an issue. What do I do when I want to use this functionality across multiple ASP.NET "Areas"? Do I have to make this change in each web.config in each area? Or can I just make it in the root section and delete the other web.configs?

  25. Avatar for John
    John July 30th, 2011

    Super neat way to go and would like to use it. Any chance that you could post a basic working set of code?
    Pretty sure that I've followed everything written here and am getting a compilation error, not on build but when browsing a view:
    CS0308: The non-generic type 'xxx.xxxx.CustomWebViewPage' cannot be used with type arguments.
    Source Error:
    public class _Page_Views_TestCheckout_Index_cshtml : xxx.xxxx.CustomWebViewPage<dynamic> {

  26. Avatar for Andy Cohen
    Andy Cohen December 2nd, 2011

    Phil,
    At the beginning of this great article, you said:
    "Html is a property of type HtmlHelper and there are a large number of useful extension methods that hang off this type, such as TextBox."
    Don't you mean that it is a property of type WebViewPage?

  27. Avatar for Andy Cohen
    Andy Cohen December 2nd, 2011

    Whoops... I see what you meant, it is of type HtmlHelper<Tmodel> but it is a property of the class WebViewPage<TModel>.

  28. Avatar for haacked
    haacked December 2nd, 2011

    @Andy exactly! :)

  29. Avatar for Ben
    Ben December 30th, 2011

    I'm trying this technique with the RazorEngine, and I'm getting an error every time. Basically it will say "Unable to compile template. The name 'Html5Helper' does not exist in the current context".
    I'm a bit stumped, ATM. Any advice would be helpful.

  30. Avatar for Ben
    Ben December 30th, 2011

    I found the issue, actually. It appears the RazorEngine does not yet support these sorts of properties (Html, Url, etc).
    stackoverflow.com/.../razorengine-issues-with-html
    I hope this helps someone else.

  31. Avatar for Matthew Marksbury
    Matthew Marksbury January 24th, 2012

    This article helped me a lot, thanks. There is one problem with the code sample, however, and that is the base class on CustomWebViewPage<TModel>. It is shown as CustomWebViewPage, which compiles, but throws a runtime error when you load a model bound page. The only solution to this, that I could find was having multiple CustomWebView pages that derive from WebViewPage and WebViewPage<T>.

  32. Avatar for Evan Larsen
    Evan Larsen February 10th, 2012

    Thanks a lot Phil! I was looking everywhere on how to do this. I ended up using ILSpy on the RazorViewEngine to figure out that its initializing the helpers in the WebViewPage. After finding the correct terminology and using Bing. I found this page. Exactly what I was looking for. Thanks again.

  33. Avatar for Emil
    Emil May 28th, 2012

    @Matthew Marksbury thanks a lot, that is indeed a flaw of the sample posted by the author. When I added my custom webviewpage ALL the strongly typed views broke down. Spent some time searching for a solution (am new to MVC) not knowing the answer was right here! Thanks a million!!!

  34. Avatar for Alexander Sidorenko
    Alexander Sidorenko February 25th, 2013

    Good post!
    Thank you, Phil! 

  35. Avatar for Anthony Chambers
    Anthony Chambers March 28th, 2013

    @ffc6eca3b241373c291d3d51e0cc0a96:disqus thanks! Your comment helped me too. @haaked thanks also for the article, most helptul.

  36. Avatar for HiTopp
    HiTopp June 19th, 2013

    I am trying to implement my own custom base view page in MVC 3, but am having trouble. I followed the instructions, but when I attempt to access a custom property from the view page in a view I receive an error via IntelliSense:

    'Html is ambiguous, imported from the namespaces or types 'System.Web.Mvc, System.Web.WebPages'

    I understand the error, but I don't quite know how to fix it. My current application was built using Visual Basic (not sure if that is a factor). However, I can implement the exact same setup just fine in a C# MVC application. No errors.

    Any ideas?

  37. Avatar for ass head
    ass head August 29th, 2013

    as usual another worthless nigger piece od crapola article post by a uni incher sob

  38. Avatar for Seyi
    Seyi April 3rd, 2015

    Is this the same pattern to follow for mvc 5, I noticed the WebViewPage and WebViewPage<tmodel> have some dissimilarities

  39. Avatar for Igor Monteiro
    Igor Monteiro November 10th, 2015

    best explanation so far

  40. Avatar for mithun chopda
    mithun chopda March 24th, 2017

    Code needs correction.

    The CustomWebViewPage<tmodel> should derive from System.Web.Mvc.WebViewPage<tmodel>

    and not

    public abstract class CustomWebViewPage<tmodel> : CustomWebViewPage {
    public new Html5Helper<tmodel> Html5 { get; set; }

    public override void InitHelpers() {
    base.InitHelpers();
    Html5 = new Html5Helper<tmodel>(base.ViewContext, this);
    }
    }

  41. Avatar for Ekjon Nabik
    Ekjon Nabik October 13th, 2017

    Nice article

  42. Avatar for Adanay Martín
    Adanay Martín October 14th, 2017

    Thanks so much! I was looking for exactly this!

  43. Avatar for jay rathod
    jay rathod October 30th, 2017

    How to develop same behavior for controller's "url.action" ?