Как записать динамические данные в макет страницы в MVC 3 Razor?

У меня есть проект MVC 3 С# с движком Razor. Каковы способы и, я думаю, лучшие практики для записи динамических данных в _Layout.cshtml?

Например, может быть, я хотел бы отобразить имя пользователя в правом верхнем углу моего веб-сайта, и это имя исходит от сеанса, БД или чего-то еще в зависимости от того, какой пользователь вошел в систему.

ОБНОВЛЕНИЕ: я также ищу хорошую практику рендеринга определенных данных в элемент макета. Например, если мне нужно отобразить определенный файл CSS в зависимости от учетных данных вошедшего в систему пользователя.

(Для приведенного выше примера я подумал об использовании помощников URL.)


person Dimskiy    schedule 03.05.2011    source источник


Ответы (2)


Интернет-приложение по умолчанию, созданное Visual Studio, использует _LogOnPartial.cshtml именно для этого.

Значение имени пользователя задается в действии LogOn HomeController.

Код для _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 является частью поставщика членства aspnet.

Код для _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>

Код для действия входа в систему AccountController

[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);
        }

Код для класса ApplicationViewPage

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"]);
        }

    }

Приведенный выше код позволяет мне иметь ViewBag.LayoutModel, который содержит экземпляр моего класса LayoutModel на каждой странице.

Вот код для моего класса LayoutModel

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;
            }
        }
    }

Вот код представления

@{
    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       
}

Подробнее см. следующий вопрос. Убедитесь, что вы изменили свой файл web.config, как описано здесь: Как установить свойства ViewBag для всех представлений без использования базового класса для контроллеров?

person atbebtg    schedule 03.05.2011
comment
@atbebtg - То есть просто используйте частичные представления и визуализируйте их с помощью @Html.Partial() внутри макета? - person Dimskiy; 03.05.2011
comment
Да, просто используйте частичное представление и выполните @Html.Partial() - person atbebtg; 03.05.2011
comment
Что, если это часть данных, которая входит в элемент ‹head›? Как путь к файлу CSS или что-то в этом роде? - person Dimskiy; 03.05.2011
comment
Я считаю, что вы можете сделать то же самое, но я никогда не пробовал. Я использую @RenderSection(Script, false) для создания раздела для всего моего javascript, который я помещаю внизу страницы. Объявив имя раздела Script, для атрибута required которого задано значение false, я могу добавлять код в этот раздел всякий раз, когда мне это нужно. - person atbebtg; 03.05.2011
comment
Правильно, и это скрипты для конкретных страниц, которые либо связаны, либо нет, в зависимости от отображаемого представления. Что, если, скажем, я хочу сослаться на определенный файл CSS в зависимости от того, какой пользователь вошел в систему? Я бы не хотел иметь такую ​​логику в каждом представлении. Кроме того, я привык думать о представлениях как о частях элемента ‹body›, а не ‹head›. - person Dimskiy; 03.05.2011
comment
это может не отвечать на ваш вопрос, но может быть связано. У меня есть аналогичный случай, когда я хочу отображать другой файл макета в зависимости от URL-адреса сайта. Я собирался использовать разные файлы css для каждого домена, но решил, что разные макеты дают мне больше гибкости. Что я сделал, так это создал новый класс, который наследует WebViewPage‹T›. Я изменю свой ответ с помощью этого кода, так как он слишком длинный для размещения здесь - person atbebtg; 03.05.2011

В дополнение к ответу atbebtg, чтобы отобразить материал в голове, вы хотите использовать поддержку раздела Razor. Разделы — это именованные фрагменты шаблонного HTML, которые могут быть определены в представлениях и отображены в макете там, где он считает нужным. В макете вы вызываете @RenderSection("wellKnownSectionName"), а в представлении, использующем макет, объявляете @section wellKnownSectionName { <link rel="stylesheet" href="@UserStylesheetUrl" /><script type="text/javascript" src="@UserScriptUrl"> }. Обычно вы хотите описать назначение раздела в его названии, например, «documentHead».

Обновление: если вы визуализируете один и тот же HTML-шаблон в каждом представлении, вместо этого он попадет в макет. (Поскольку ваш макет включает теги HEAD и BODY, вы можете просто добавить соответствующий код в их тег HEAD.) Вам просто нужно убедиться, что вы передаете информацию, необходимую для макета, из контроллера через ViewBag/View.Model. /ВидДанные. Таким образом, ваш макет будет включать в себя следующее:

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

и ваш контроллер будет включать логику для заполнения ViewBag.UserName:

ViewBag.UserName = Session["UserName"];

(В идеале вы должны использовать строго типизированную модель представления, и я бы порекомендовал вам воздержаться от использования сеанса для чего-либо, поскольку его преимущества невелики по сравнению с альтернативами и обходится дорого для архитектуры... вместо этого я бы просто рекомендовал хранить некоторые зашифрованные cookie в браузере, который содержит имя пользователя или что-то еще, что вы можете использовать при каждой загрузке страницы для извлечения объекта пользователя из кеша/БД/сервиса.)

person G-Wiz    schedule 03.05.2011
comment
Спасибо! Но что, если это одни и те же данные для всех просмотров? Я не хочу иметь @section wellKnownSection..... в каждом представлении с одинаковой логикой для отображения одних и тех же данных. Я бы предпочел иметь эту логику в одном месте. - person Dimskiy; 03.05.2011
comment
Итак, я согласен с сеансом и не совсем согласен с файлом cookie. Но что это за контроллер, о котором вы говорите? Элемент ‹head› определен в Layout. Макеты не имеют контроллеров. Какой-то базовый/общий контроллер? (P.S. На самом деле я думал об использовании пользовательских помощников URL для моего примера с URL-адресами CSS. Не могу придумать никаких других данных, которые могли бы попасть в ‹head› на данный момент :).....) - person Dimskiy; 04.05.2011