Getting information into the Layout without using ViewBag.

You should avoid using the ViewBag as much as possible since you get run-time errors instead of compile time error if something fails. The problem is that it can be hard to avoid it if you are using information in your layout.

The solution that I’m using lets you do the following:

public class YourController : BaseController
{
    public ActionResult YourAction()
    {
        LayoutModel.MetaDescription = "Does something in the application";
        return View();
    }
}

And in your _Layout.cshtml:

<meta name="description" value="@LayoutModel.MetaDescription" />

See? Everything is typed.

The approach I’m using is to create a base class for views and inherit WebViewPage in it. First things first, add a model for your layout:

public class LayoutViewModel
{
    public string MetaKeywords { get; set; }
}

Then add the following to your BaseController:

public class BaseController : Controller
{
    private LayoutViewModel _layoutModel = new LayoutViewModel();

    public LayoutViewModel LayoutModel { get { return _layoutModel; } }

    protected override void OnResultExecuting(ResultExecutingContext ctx) 
    {
        var viewResult = ctx.Result as ViewResult;
        if (viewResult != null)
            viewResult.ViewBag.LayoutModel = LayoutModel;

        base.OnResultExecuting(ctx);
    }
}

Ok. I know. I’m cheating and using the ViewBag. But since it’s only in one place it’s less error prone than using the viewbag everywhere.

Now create the View base class. You need to create two versions to have support for typed views.

public abstract class ViewBaseWithLayoutModel : WebViewPage
{
    public LayoutViewModel LayoutModel { get { return (LayoutViewModel)ViewBag.LayoutModel; } }
}
public abstract class ViewBaseWithLayoutModel<T> : WebViewPage<T>
{
    public LayoutViewModel LayoutModel { get { return (LayoutViewModel)ViewBag.LayoutModel; } }
}

The last thing is to specify your new base view class in your ViewsWeb.config:

  <system.web.webPages.razor>
    <pages pageBaseType="Namespace.To.ViewBaseWithLayoutModel">

That’s it.


  • http://gehling.dk Carsten Gehling

    Jonas just a quick note: Starting from MVC3, the WebViewPage is declared abstract, and your subclass should therefore also be declared abstract – since it is Razor that should implement the “Execute” method.

    Otherwise great article – thanks for pointing it out on StackOverflow

    • http://www.gauffin.com jgauffin

      Thank you for pointing that out. The code have been fixed.

  • Wouter

    Wow, I have no idea why it works, but it works! Thanks for this very handy piece of code! The part I don’t understand is the ViewBaseClass that inherits from WebViewPage, it’s new to me. Anyway, awesome! ;-)

    • http://www.gauffin.org jgauffin

      All views in ASP.NET MVC has a base class. What the view engine does is simply converting the HTML view into code (by creating a new class dynamically which will inherit the view base class). So when you are using @Model in the view you are really calling a property in the view base class. So if we want to get another property to use in the view we’ll have to define it in some way. And that’s by creating a custom view base class.

  • BJToepper

    I’m new to this stuff, but I’m having problems getting the example to work. Here’s what I did:

    1) Created a new .cs file and called it “BaseModels.cs”, and then pasted in the LayoutViewModel definition.

    2) Created a new .cs file and called it “BaseController.cs”, and then pasted in the BaseController code.

    3) Got confused as to where to add the View base class, so just pasted it into the BaseController.cs file, above the other code.

    4) Got confused about the “Namespace.To.ViewBaseWithLayoutModel” in web.config, and so added a namespace “wrapper” around the ViewBaseWithBaseModel code, then used that.

    5) Tested the code, and got the error “Object reference not set to an instance of object.” The error is precisely at @LayoutModel.MetaDescription in _layout.cshtml.

    6) In the controller, added the line: ‘LayoutModel.MetaDescription = “Welcome”;’

    7) Got the same error and gave up.

    Any ideas?

    Thanks.

    • JonasGauffin

      are your own controller inheriting the base controller?

      • BJToepper

        Thanks for getting back to me. Yes, I believe the controller is inheriting BaseController. The relevant controller code is below. One curious error: VS says “The name ‘LayoutModel’ does not exist in the current context,” in _layout.cshtml. I must be missing something small.

        //In HomeController.cs
        public class HomeController : BaseController
        {
        public ActionResult Index()
        {
        LayoutModel.MetaDescription = “Welcome”;
        return View();
        }
        }

        //In BaseController.cs

        public class BaseController : Controller
        {
        private LayoutViewModel _layoutModel = new LayoutViewModel();
        public LayoutViewModel LayoutModel { get { return _layoutModel; } }
        protected override void OnResultExecuting(ResultExecutingContext filterContext)
        {
        var viewResult = filterContext.Result as ViewResult;
        if (viewResult == null) {
        viewResult.ViewBag.LayoutModel = LayoutModel;
        }
        base.OnResultExecuting(filterContext);
        }

        //In BaseModels.cs

        namespace MyNamespace.Models
        {
        public class LayoutViewModel
        {
        public string MetaDescription { get; set; }
        }
        }

  • http://six.dev-heaven.net Sickboy

    Hi Gauffin,

    Great stuff! Is this still what you are using today or have you come up with a different approach?

    I was considering strongly typed layouts (Base ViewModel) for a while, but I cannot say I am a huge fan of having to use a common base class for my ViewModels, and having to fill it up in each of the command/query handlers.

    For a while I was abusing the dynamic App object, inserting a custom object, filling it from _ViewStart, but that is only available in Views, and not strongly typed.

    Thanks for reading

    • http://blog.gauffin.org/ jgauffin

      I usually only use user related information in layout. Then I just use my custom IPrincipal/IIdentity with a ThreadStatic property.