Defining Default Content For A Razor Layout Section

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

Layouts in Razor serve the same purpose as Master Pages do in Web Forms. They allow you to specify a layout for your site and carve out some placeholder sections for your views to implement.

For example, here’s a simple layout with a main body section and a footer section.


<!DOCTYPE html>
<html>
<head><title>Sample Layout</head>
<body>
    <div>@RenderBody()</div>
    <footer>@RenderSection("Footer")</footer>
</body>
</html>

In order to use this layout, your view might look like.


@{
    Layout = "MyLayout.cshtml";
}
<h1>Main Content!</h1>
@section Footer {
    This is the footer.
}

Notice we use the @section syntax to specify the contents for the defined Footer section.

But what if we have other views that don’t specify content for the Footer section? They’ll throw an exception stating that the “Footer” section wasn’t defined.

To make a section optional, we need to call an overload of RenderSection and specify false for the required parameter.


<!DOCTYPE html>
<html>
<head><title>Sample Layout</head>
<body>
    <div>@RenderBody()</div>
    <footer>@RenderSection("Footer", false)</footer>
</body>
</html>

But wouldn’t it be nicer if we could define some default content in the case that the section isn’t defined in the view?

Well here’s one way. It’s a bit ugly, but it works.


<footer>
  @if (IsSectionDefined("Footer")) {
      RenderSection("Footer");
  }
  else { 
      <span>This is the default yo!</span>   
  }
</footer>

That’s some ugly code. If only there were a way to write a version of RenderSection that could accept some Razor markup as a parameter to the method.

Templated Razor Delegates to the rescue! See, I told you these things would come in handy.

We can write an extension method on WebPageBase that encapsulates this bit of ugly boilerplate code. Here’s the implementation.


public static class Helpers {
  public static HelperResult RenderSection(this WebPageBase webPage, 
      string name, Func<dynamic, HelperResult> defaultContents) {
    if (webPage.IsSectionDefined(name)) {
      return webPage.RenderSection(name);
    }
    return defaultContents(null);
  }
}

What’s more interesting than this code is how we can use it now. My Layout now can do the following to define the Footer section:


<footer>
  @this.RenderSection("Footer", @<span>This is the default!</span>)
</footer>

That’s much cleaner! But we can do even better. Notice how there’s that ugly this keyword? That’s necessary because when you write an extension method on the current class, you have to call it using the this kewyord.

Remember when I wrote about how to change the base type of a Razor view? Here’s a case where that really comes in handy.

What we can do is write our own custom base page type (such as the CustomWebViewPage class I used in that blog post) and add the RenderSection method above as an instance method on that class. I’ll leave this as an exercise for the reader.

The end result will let you do the following:


<footer>
  @RenderSection("Footer", @<span>This is the default!</span>)
</footer>

Pretty slick!

You might be wondering why we didn’t just include this feature in Razor. My guess is that we wanted to but just ran out of time. Hopefully this will make it in the next version of Razor.

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

Comments

avatar

21 responses

  1. Avatar for Marcin Dobosz
    Marcin Dobosz March 5th, 2011

    Looks like something I've seen before (Optional Razor Sections with Default Content) :) Also check out Razor, Nested Layouts and Redefined Sections for even more advanced scenarios.

  2. Avatar for haacked
    haacked March 5th, 2011

    @Marcin Ha! Great minds think alike. I should make sure to subscribe to your blog. :)

  3. Avatar for Andrew Wrigley
    Andrew Wrigley March 5th, 2011

    Looks like this should be part of MVC 4.

  4. Avatar for Ben
    Ben March 5th, 2011

    I was wondering today what a good way of rendering footers would be. Thanks for reading my mind and coming up with this post :)

  5. Avatar for ViewStartLover
    ViewStartLover March 5th, 2011

    Why wasn't this solved using the ViewStart files? They already contain common functionality. If ViewStart files could just define template sections, none if this black magic would be required.
    C.f. http://www.dotnetcurry.com/ShowArticle.aspx?ID=605

  6. Avatar for Lukas Kubis
    Lukas Kubis March 11th, 2011

    @ViewStartLower: I think that you can use declarative html helpers inside App_Code folder for all views. Look at my post blog.lukaskubis.net/...

  7. Avatar for Rush
    Rush March 17th, 2011

    The original way is way better. Why does everyone want to make their code "pretty" by getting it down to one line? It won't be pretty if you have a lot of default content to stuff in there.

  8. Avatar for Petr Divis
    Petr Divis April 22nd, 2011

    what another PHP framework (Nette) does with sections (here blocks) is inheritance:
    1. you declare default content in the layout file by typing:
    {block #title}I'm the default content!{/block}
    2. the view file optionally overwrites the section or even includes it:
    {block #title}Child content with parent content: {include #parent}{/block}
    --awesome, you should think about it for MVC4
    Nette has also pretty good ajax snippets, caching options and other stuff, but since I'm beginning to learn MVC3, I don't know how it works exactly.
    But Phill, you should check it out and please, add some modularity for enterprise webapps to be developed easily, because right now I have to deep dive into all the DI frameworks to get to know them and decide how to inject modules with controllers and views for my diploma thesis project (how it's done in Orchard), but there are no tutorials:(

  9. Avatar for Fabian
    Fabian August 9th, 2011

    You could add this signature to the help in the case you want to use Html.Partial() has the default content:
    public static HelperResult RenderSection(this WebPageBase webPage, string name, IHtmlString defaultContents)
    {
    if (webPage.IsSectionDefined(name))
    {
    return webPage.RenderSection(name);
    }
    var result = new HelperResult((x) => x.Write(defaultContents.ToString()));
    return webPage.RenderSection(name, (x) => result);
    }

  10. Avatar for Jack
    Jack September 3rd, 2011

    MVC3 / Razor is a total mess. The team behind it are out-of-control amateurs and should be sacked. Time for ANOTHER vote of no-confidence from the community. Microsoft are wasting our time and energy. Wise up people.

  11. Avatar for andrew boudreau
    andrew boudreau September 22nd, 2011

    It's important to note that if you do change the BasePageType and create a custom implementation of WebViewPage then you need to extend the generic and non-generic classes separately. I had a pretty tricky time figuring out why my some of my expressions weren't able to do type-inference on the typed view<viewmodel>. After adding RenderSection methods to both CustomWebViewPage and CustomWebViewPage<TModel> i was back in business, great article.

  12. Avatar for kaicui
    kaicui February 12th, 2012

    hi,i'm a chinese programmer and i have got a problem in my work.I have spend a lot of time but can't find the answer.(my english is pool so i will try to explain it clearly^_^).
    The problem is this:
    1、in MVC3.0,Razor viewEngine,i want to put every <Script> block to the bottom of page(usually before "</body>" tag).
    2、So i use a "RenderSection" in the layout page and put it in the bottom of page,other page( who use the page as their layout page) define their script Section,these section contains javascript the page needs.
    3、question come out:if my page has some paticalPage,in partial page,i can't define scriptblock because the layout page cannot render it
    PS:because i have to pass some server para to the script so i can't write script in one page,so i need a method to put any script whitch writing in any pages can be moved to the </body> Tag

  13. Avatar for Notre
    Notre February 22nd, 2012

    @haicui I couldn't quite understand your problem, so I'm not sure if this will help. But take a look at:
    weblogs.asp.net/...

  14. Avatar for kamran
    kamran June 17th, 2012

    Thanks ive been looking for this

  15. Avatar for can
    can August 27th, 2012

    ViewBag.sidebar = View("_sidebar") => avaible?
    and
    ViewBag.sidebar.MenuTitle = "MenuTitle";

  16. Avatar for Funka!
    Funka! August 6th, 2013

    Hi Phil,
    Big fan of yours! This lovely two and a half year old article seems to answer the frustration I'm having right now converting a webforms MVC 2 site into razor MVC 4. It was really easy to do this with good ol' ContentPlaceHolder in our MVC2 master pages, but seems very convoluted with Razor. You mentioned you hoped this would make it into Razor, so I'm wondering: did it ever make it in? Am I just not finding a better solution, or is this still the recommended approach?
    Thank you!

  17. Avatar for haacked
    haacked August 7th, 2013

    I think it made it in. Just give it a try!

  18. Avatar for Guest
    Guest February 16th, 2014

    Basically because with the "pretty" way its easier to share the solution in the form of a package that the rest of your team can use instead of writing out the same boilerplate code over and over again

  19. Avatar for Riley Wortman
    Riley Wortman December 14th, 2015

    Thank you for this great article, I know its old but turns out it still isn't in MVC 5 and this pointed me in a nice direction for what I was trying to do.
    Edit - I really like the other blog post about removing the this keyword. helped a lot.

  20. Avatar for Kenia
    Kenia March 4th, 2016

    Nice! Thanks for sharing
    /Kenia

  21. Avatar for Matthew Steven
    Matthew Steven June 23rd, 2016

    Is this still a missing feature from Razor?