How can I write dynamic data to page layout in MVC 3 Razor?

45,601

Solution 1

The default internet application created by visual studio use _LogOnPartial.cshtml to do exactly this.

The user Name value is set in the LogOn action of the HomeController

Code for _LogOnPartial.cshtml

@if(Request.IsAuthenticated) {
    <text>Welcome <strong>@User.Identity.Name</strong>!
    [ @Html.ActionLink("Log Off", "LogOff", "Account") ]</text>
}
else {
    @:[ @Html.ActionLink("Log On", "LogOn", "Account") ]
}

User.Identity is part of the aspnet Membership provider.

Code for the _Layout.cshtml

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>@ViewBag.Title</title>
    <link href="@Url.Content("~/Content/Site.css")" rel="stylesheet" type="text/css" />
    <script src="@Url.Content("~/Scripts/jquery-1.5.1.min.js")" type="text/javascript"></script>
    <script src="@Url.Content("~/Scripts/modernizr-1.7.min.js")" type="text/javascript"></script>
</head>
<body>
    <div class="page">
        <header>
            <div id="title">
                <h1>Test</h1>
            </div>
            <div id="logindisplay">
                @Html.Partial("_LogOnPartial")
            </div>
            <nav>
                <ul id="menu">
                </ul>
            </nav>
        </header>
        <section id="main">
            @RenderBody()
        </section>
        <footer>
        </footer>
    </div>
</body>
</html>

Code for the AccountController Logon Action

[HttpPost]
        public ActionResult LogOn(LogOnModel model, string returnUrl)
        {
            if (ModelState.IsValid)
            {
                if (Membership.ValidateUser(model.UserName, model.Password))
                {
                    FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe);
                    if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/")
                        && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\"))
                    {
                        return Redirect(returnUrl);
                    }
                    else
                    {
                        return RedirectToAction("Index", "Home");
                    }
                }
                else
                {
                    ModelState.AddModelError("", "The user name or password provided is incorrect.");
                }
            }

            // If we got this far, something failed, redisplay form
            return View(model);
        }

Code for ApplicationViewPage class

public abstract class ApplicationViewPage<T> : WebViewPage<T>
    {
        protected override void InitializePage()
        {
            SetViewBagDefaultProperties();
            base.InitializePage();
        }

        private void SetViewBagDefaultProperties()
        {
            ViewBag.LayoutModel = new LayoutModel(Request.ServerVariables["SERVER_NAME"]);
        }

    }

The above code allow me to have a ViewBag.LayoutModel that hold an instance of my LayoutModel class in every page.

Here is a code for my LayoutModel class

public class LayoutModel
    {
        public string LayoutFile { get; set; }
        public string IpsTop { get; set; }
        public string IpsBottom { get; set; }
        public string ProfileTop { get; set; }
        public string ProfileBottom { get; set; }

        public LayoutModel(string hostname)
        {
            switch (hostname.ToLower())
            {
                default:

                    LayoutFile = "~/Views/Shared/_BnlLayout.cshtml";
                    IpsBottom = "~/Template/_BnlIpsBottom.cshtml";
                    IpsTop = "~/Template/_BnlTop.cshtml";
                    ProfileTop = "~/Template/_BnlProfileTop.cshtml";
                    break;

                case "something.com":
                    LayoutFile = "~/Views/Shared/_Layout.cshtml";
                    IpsBottom = "~/Template/_somethingBottom.cshtml";
                    IpsTop = "~/Template/_somethingTop.cshtml";
                    ProfileTop = "~/Template/_somethingProfileTop.cshtml";
                    break;
            }
        }
    }

Here is the code to the View

@{
    ViewBag.Title = "PageTitle";
    Layout = @ViewBag.LayoutModel.LayoutFile; 
}
@using (Html.BeginForm())
{
    <span class="error">@ViewBag.ErrorMessage</span>
    <input type="hidden" name="Referrer" id="Referrer" value="@ViewBag.Referrer" />
    html stuff here       
}

Refer to the following question for more detail. Make sure you modify your web.config as described there: How to set ViewBag properties for all Views without using a base class for Controllers?

Solution 2

In addition to atbebtg's answer, to render stuff into the head, you want to leverage Razor's section support. Sections are named fragments of templated HTML that can be defined in views and rendered in the layout, where the layout sees fit. In the layout, you call @RenderSection("wellKnownSectionName") and in the view that uses the layout, you declare @section wellKnownSectionName { <link rel="stylesheet" href="@UserStylesheetUrl" /><script type="text/javascript" src="@UserScriptUrl"> }. Usually you want to describe the intention of the section in its name, such as "documentHead".

Update: If you're rendering the same templated HTML on every view, it would instead go into the layout. (Since your layout includes the HEAD and the BODY tags, you can just add the appropriate code to the HEAD tag their.) You just need to make sure you pass the information necessary for the layout from the controller via the ViewBag/View.Model/ViewData. So your layout would include this:

<head>
    <link rel="stylesheet" href="/css/@ViewBag.UserName/.css"/>
</head>

and your controller would include the logic to populate ViewBag.UserName:

ViewBag.UserName = Session["UserName"];

(Ideally you would use a strongly-typed view model and I would recommend you abstain from using Session for anything, since its benefits are small compared to alternatives and comes at a big cost to architecture... instead I would simply recommend storing some encrypted cookie on the browser that contains a username or something, which you can use on each page load to retrieve the user object from cache/db/service.)

Share:
45,601
Dimskiy
Author by

Dimskiy

Updated on April 01, 2020

Comments

  • Dimskiy
    Dimskiy about 4 years

    I have MVC 3 C# project with Razor engine. What are the ways and, I guess, the best practices to write dynamic data to the _Layout.cshtml?

    For example, maybe I'd like to display user's name in the upper right corner of my website, and that name is coming from Session, DB, or whatever based on what user is logged in.

    UPDATE: I'm also looking for a good practice on rendering certain data into the element of the Layout. For example, if I need to render a specific CSS file depending on the logged-in user's credentials.

    (For the example above, I thought of using Url Helpers.)

  • Dimskiy
    Dimskiy about 13 years
    @atbebtg - So basically just use partial views and render them with @Html.Partial() inside of the Layout?
  • atbebtg
    atbebtg about 13 years
    Yup, just use partial view and do @Html.Partial()
  • Dimskiy
    Dimskiy about 13 years
    What if it's a piece of data that's going into <head> element? Like a path for CSS file or something?
  • atbebtg
    atbebtg about 13 years
    I believe you can do the samething however I never tried it. I use @RenderSection("Script", false) to create a section for all my javascript that i put in the bottom of the page. By declaring a section name "Script" that has its required attribute set to false, I can add more code to that section whenever i need it.
  • Dimskiy
    Dimskiy about 13 years
    Right, and those are page specific scripts that are either linked to or not depending on the view that is being rendered. What if, let's say, I want to link to a specific CSS file depending on what user is logged in? I wouldn't want to have that logic in every view. Also, I'm used to thinking of views as parts of <body> element, not <head>.
  • atbebtg
    atbebtg about 13 years
    this might not answer your question but might be related. I have similar case where I want to display different layout file depending on the url of the site. I was going to use diffferent css file for each domain but i decided that different layout offer me more flexibility. What I did is i created a new class that inherit the WebViewPage<T> I will modify my answer with this code since its too long to be put in here
  • Dimskiy
    Dimskiy about 13 years
    Thanks! But what if it's the same data for all views? I don't want to have @section wellKnownSection..... on every view with the same logic to render the same data. I'd prefer having that logic in 1 place.
  • Dimskiy
    Dimskiy about 13 years
    Ok, so I do agree about session and don't completely agree about cookie. But what is that controller that you're talking about? The <head> element is defined in Layout. Layouts don't have controllers. Some sort of a base/shared controller? (P.S. I actually thought of using custom URL Helpers for my example with CSS URLs. Can't think of any other data that would go into <head> at the moment :).....)