将数据传递到所有页面通用的布局

时间:2012-11-05 02:44:26

标签: asp.net-mvc-4

我有一个有布局页面的网站。但是,此布局页面包含所有页面模型必须提供的数据,例如页面标题,页面名称以及我们实际执行某些操作的HTML帮助程序所在的位置。此外,每个页面都有自己的视图模型属性。

我该怎么做?键入布局似乎是一个坏主意,但我如何传递这些信息呢?

18 个答案:

答案 0 :(得分:132)

如果您需要将相同的属性传递给每个页面,那么创建所有视图模型使用的基本视图模型将是明智之举。然后,您的布局页面可以采用此基本模型。

如果此数据背后需要逻辑,则应将其放入所有控制器使用的基本控制器中。

你可以做很多事情,重要的方法是不要在多个地方重复相同的代码。

修改:从以下评论中更新

这是一个展示这个概念的简单例子。

创建一个所有视图模型都将继承的基本视图模型。

public abstract class ViewModelBase
{
    public string Name { get; set; }
}

public class HomeViewModel : ViewModelBase
{
}

您的布局页面可以将其作为模型。

@model ViewModelBase
<!DOCTYPE html>
<html>
    <head>
        <meta name="viewport" content="width=device-width" />
        <title>Test</title>
    </head>
    <body>
        <header>
            Hello @Model.Name
        </header>
        <div>
            @this.RenderBody()
        </div>
    </body>
</html>

最后在动作方法中设置数据。

public class HomeController
{
    public ActionResult Index()
    {
        return this.View(new HomeViewModel { Name = "Bacon" });
    }
}

答案 1 :(得分:59)

我在布局中使用了RenderAction html helper for razor。

@{
   Html.RenderAction("Action", "Controller");
 }

我需要它用于简单的字符串。所以我的动作返回字符串并在视图中轻松写下来。 但是如果你需要复杂的数据,你可以返回PartialViewResult和model。

 public PartialViewResult Action()
    {
        var model = someList;
        return PartialView("~/Views/Shared/_maPartialView.cshtml", model);
    }

您只需要将模型开始部分视图&#39; _maPartialView.cshtml&#39;你创建了

@model List<WhatEverYourObjeIs>

然后您可以使用html在该局部视图中使用模型中的数据。

答案 2 :(得分:35)

另一种选择是创建一个单独的LayoutModel类,其中包含布局中需要的所有属性,然后将此类的实例填充到ViewBag中。我使用Controller.OnActionExecuting方法来填充它。 然后,在布局开始时,您可以从ViewBag中拉回此对象并继续访问此强类型对象。

答案 3 :(得分:26)

据推测,这个的主要用例是为所有(或大多数)控制器操作获取基本模型。

鉴于此,我使用了其中几个答案的组合,主要是对科林培根的回答。

这仍然是控制器逻辑是正确的,因为我们正在填充视图模型以返回视图。因此,放置它的正确位置在控制器中。

我们希望在所有控制器上都能实现这一点,因为我们将它用于布局页面。我正在将它用于在布局页面中呈现的部分视图。

我们还希望获得强类型ViewModel

的附加好处

因此,我创建了一个BaseViewModel和BaseController。所有ViewModels控制器将分别从BaseViewModel和BaseController继承。

代码:

<强> BaseController

public class BaseController : Controller
{
    protected override void OnActionExecuted(ActionExecutedContext filterContext)
    {
        base.OnActionExecuted(filterContext);

        var model = filterContext.Controller.ViewData.Model as BaseViewModel;

        model.AwesomeModelProperty = "Awesome Property Value";
        model.FooterModel = this.getFooterModel();
    }

    protected FooterModel getFooterModel()
    {
        FooterModel model = new FooterModel();
        model.FooterModelProperty = "OMG Becky!!! Another Awesome Property!";
    }
}

请注意使用 OnActionExecuted 取自this SO post

<强>的HomeController

public class HomeController : BaseController
{
    public ActionResult Index(string id)
    {
        HomeIndexModel model = new HomeIndexModel();

        // populate HomeIndexModel ...

        return View(model);
    }
}

<强> BaseViewModel

public class BaseViewModel
{
    public string AwesomeModelProperty { get; set; }
    public FooterModel FooterModel { get; set; }
}

<强> HomeViewModel

public class HomeIndexModel : BaseViewModel
{

    public string FirstName { get; set; }

    // other awesome properties
}

<强> FooterModel

public class FooterModel
{
    public string FooterModelProperty { get; set; }
}

<强> Layout.cshtml

@model WebSite.Models.BaseViewModel
<!DOCTYPE html>
<html>
<head>
    < ... meta tags and styles and whatnot ... >
</head>
<body>
    <header>
        @{ Html.RenderPartial("_Nav", Model.FooterModel.FooterModelProperty);}
    </header>

    <main>
        <div class="container">
            @RenderBody()
        </div>

        @{ Html.RenderPartial("_AnotherPartial", Model); }
        @{ Html.RenderPartial("_Contact"); }
    </main>

    <footer>
        @{ Html.RenderPartial("_Footer", Model.FooterModel); }
    </footer>

    < ... render scripts ... >

    @RenderSection("scripts", required: false)
</body>
</html>

<强> _Nav.cshtml

@model string
<nav>
    <ul>
        <li>
            <a href="@Model" target="_blank">Mind Blown!</a>
        </li>
    </ul>
</nav>

希望这有帮助。

答案 4 :(得分:7)

您不必乱用操作或更改模型,只需使用基本控制器并从布局viewcontext中转换现有控制器。

使用所需的公共数据(标题/页面/位置等)和操作初始化...

创建一个基本控制器
public abstract class _BaseController:Controller {
    public Int32 MyCommonValue { get; private set; }

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}

确保每个控制器都使用基本控制器......

public class UserController:_BaseController {...

_Layout.cshml页面的视图上下文中投射现有的基本控制器...

@{
    var myController = (_BaseController)ViewContext.Controller;
}

现在,您可以从布局页面引用基本控制器中的值。

@myController.MyCommonValue

答案 5 :(得分:4)

如果你想在布局中传递整个模型:

@model ViewAsModelBase
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta charset="utf-8"/>
    <link href="/img/phytech_icon.ico" rel="shortcut icon" type="image/x-icon" />
    <title>@ViewBag.Title</title>
    @RenderSection("styles", required: false)    
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
    @RenderSection("scripts", required: false)
    @RenderSection("head", required: false)
</head>
<body>
    @Html.Action("_Header","Controller", new {model = Model})
    <section id="content">
        @RenderBody()
    </section>      
    @RenderSection("footer", required: false)
</body>
</html>

并在控制器中添加:

public ActionResult _Header(ViewAsModelBase model)

答案 6 :(得分:3)

其他答案几乎涵盖了我们如何将模型传递到布局页面的所有内容。但我找到了一种方法,您可以使用该方法动态地将变量传递到布局页面,而无需在布局中使用任何模型或局部视图。让我们说你有这个模型 -

public class SubLocationsViewModel
{
    public string city { get; set; }
    public string state { get; set; }
}

你想要动态地获得城市和州。例如

在index.cshtml中

可以将这两个变量放在ViewBag中

@model  MyProject.Models.ViewModel.SubLocationsViewModel
@{
    ViewBag.City = Model.city;
    ViewBag.State = Model.state;
}

然后在layout.cshtml中,您可以访问那些viewbag变量

<div class="text-wrap">
    <div class="heading">@ViewBag.City @ViewBag.State</div>
</div>

答案 7 :(得分:1)

我认为这些答案中的任何一个都不适合大型企业级应用程序。我不是过度使用ViewBag的粉丝,但在这种情况下,为了灵活性,我会例外。这就是我要做的......

您应该在所有控制器上都有一个基本控制器。在您的基本控制器中添加您的布局数据OnActionExecuting(如果您想推迟,则OnActionExecuted)...

public class BaseController : Controller
{
    protected override void OnActionExecuting(ActionExecutingContext     
        filterContext)
    {
        ViewBag.LayoutViewModel = MyLayoutViewModel;
    }
}

public class HomeController : BaseController
{
    public ActionResult Index()
    {
        return View(homeModel);
    }
}

然后在你的_Layout.cshtml中从ViewBag中提取你的ViewModel ......

@{
  LayoutViewModel model = (LayoutViewModel)ViewBag.LayoutViewModel;
}

<h1>@model.Title</h1>

或者...

<h1>@ViewBag.LayoutViewModel.Title</h1>

这样做不会干扰页面控制器或视图模型的编码。

答案 8 :(得分:1)

您还可以使用RenderSection,这有助于您将Model数据注入_Layout视图。

您可以注入View Model数据,JsonScriptCSSHTML

在此示例中,我将Json视图中的Index注入Layout视图。

<强> Index.chtml

@section commonLayoutData{

    <script>

        var products = @Html.Raw(Json.Encode(Model.ToList()));

    </script>

    }

<强> _Layout.cshtml

@RenderSection("commonLayoutData", false)

这样就无需创建单独的Base View Model

希望帮助某人。

答案 9 :(得分:1)

我所做的非常简单,而且有效

在任何控制器中声明静态属性,或者如果需要,可以使用静态值创建数据类:

public static username = "Admin";
public static UserType = "Administrator";

这些值可以由控制器根据操作进行更新。 以后您可以在_Layout

中使用它们

在_layout.cshtml

@project_name.Controllers.HomeController.username
@project_name.Controllers.HomeController.UserType

答案 10 :(得分:0)

还有另一种方法可以处理此问题。从体系结构的角度来看,这也许不是最干净的方法,但是它避免了其他答案带来的很多痛苦。只需在Razor布局中注入服务,然后调用获取必要数据的方法即可:

@inject IService myService

然后在布局视图中:

@if (await myService.GetBoolValue()) {
   // Good to go...
}

再次,就体系结构而言,这不是很整洁(显然,不应将服务直接注入视图中),但可以完成工作。

答案 11 :(得分:0)

为什么没有人建议在ViewData上使用扩展方法?

选项1

在我看来,到目前为止,该问题的介入最少,最简单。没有硬编码的字符串。没有强加的限制。没有魔术编码。没有复杂的代码。

public static class ViewDataExtensions
{
    private const string TitleData = "Title";
    public static void SetTitle<T>(this ViewDataDictionary<T> viewData, string value) => viewData[TitleData] = value;
    public static string GetTitle<T>(this ViewDataDictionary<T> viewData) => (string)viewData[TitleData] ?? "";
}

在页面中设置数据

ViewData.SetTitle("abc");

选项#2

另一个选项,使字段声明更加容易。

public static class ViewDataExtensions
{
    public static ViewDataField<string, V> Title<V>(this ViewDataDictionary<V> viewData) => new ViewDataField<string, V>(viewData, "Title", "");
}

public class ViewDataField<T,V>
{
    private readonly ViewDataDictionary<V> _viewData;
    private readonly string _field;
    private readonly T _defaultValue;

    public ViewDataField(ViewDataDictionary<V> viewData, string field, T defaultValue)
    {
        _viewData = viewData;
        _field = field;
        _defaultValue = defaultValue;
    }

    public T Value {
        get => (T)(_viewData[_field] ?? _defaultValue);
        set => _viewData[_field] = value;
    }
}

在页面中设置数据。声明比第一个选项更容易,但是用法语法稍长一些。

ViewData.Title().Value = "abc";

选项3

然后可以将其与返回包含所有与布局相关的字段及其默认值的单个对象相结合。

public static class ViewDataExtensions
{
    private const string LayoutField = "Layout";
    public static LayoutData Layout<T>(this ViewDataDictionary<T> viewData) => 
        (LayoutData)(viewData[LayoutField] ?? (viewData[LayoutField] = new LayoutData()));
}

public class LayoutData
{
    public string Title { get; set; } = "";
}

在页面中设置数据

var layout = ViewData.Layout();
layout.Title = "abc";

第三个选项有几个好处,我认为在大多数情况下是最好的选择:

  • 最简单的字段声明和默认值。

  • 设置多个字段时最简单的用法语法。

  • 允许在ViewData中设置各种数据(例如,布局,标题,导航)。

  • 在LayoutData类中允许其他代码和逻辑。

P.S。不要忘记在_ViewImports.cshtml

中添加ViewDataExtensions的命名空间。

答案 12 :(得分:0)

您可以在App_Code文件夹中创建一个剃刀文件,然后从您的视图页面访问它。

Project> Repository / IdentityRepository.cs

namespace Infrastructure.Repository
{
    public class IdentityRepository : IIdentityRepository
    {
        private readonly ISystemSettings _systemSettings;
        private readonly ISessionDataManager _sessionDataManager;

        public IdentityRepository(
            ISystemSettings systemSettings
            )
        {
            _systemSettings = systemSettings;
        }

        public string GetCurrentUserName()
        {
            return HttpContext.Current.User.Identity.Name;
        }
    }
}

Project> App_Code / IdentityRepositoryViewFunctions.cshtml:

@using System.Web.Mvc
@using Infrastructure.Repository
@functions
{
    public static IIdentityRepository IdentityRepositoryInstance
    {
        get { return DependencyResolver.Current.GetService<IIdentityRepository>(); }
    }

    public static string GetCurrentUserName
    {
        get
        {
            var identityRepo = IdentityRepositoryInstance;
            if (identityRepo != null)
            {
                return identityRepo.GetCurrentUserName();
            }
            return null;
        }
    }
}

Project> Views / Shared / _Layout.cshtml(或任何其他.cshtml文件)

<div>
    @IdentityRepositoryViewFunctions.GetCurrentUserName
</div>

答案 13 :(得分:0)

在 .NET Core 中,您可以使用视图组件来执行此操作。

https://docs.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-5.0

从上面的链接,添加一个继承自 ViewComponent 的类

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ViewComponentSample.Models;

namespace ViewComponentSample.ViewComponents
{
    public class PriorityListViewComponent : ViewComponent
    {
        private readonly ToDoContext db;

        public PriorityListViewComponent(ToDoContext context)
        {
            db = context;
        }

        public async Task<IViewComponentResult> InvokeAsync(
        int maxPriority, bool isDone)
        {
            var items = await GetItemsAsync(maxPriority, isDone);
            return View(items);
        }
        private Task<List<TodoItem>> GetItemsAsync(int maxPriority, bool isDone)
        {
            return db.ToDo.Where(x => x.IsDone == isDone &&
                                 x.Priority <= maxPriority).ToListAsync();
        }
    }
}

然后在您看来(在我的情况下为 _layout

@await Component.InvokeAsync("PriorityList", new { maxPriority = 4, isDone = true })

如果您需要视图,请在 ~/Views/Shared/Components/<Component Name>/Default.cshtml 处创建一个文件夹。您需要创建文件夹 Components 然后在其中创建一个包含您的组件名称的文件夹。在上面的例子中,PriorityList.

答案 14 :(得分:0)

使用静态字符串(如页面标题、页面名称和位置等)的最佳方法是通过 ViewData 定义。只需在 ViewStart.cshtml

中定义所需的 ViewData
@{
    Layout = "_Layout";
    ViewData["Title"] = "Title";
    ViewData["Address"] = "1425 Lane, Skardu,<br> Pakistan";
}

并在需要时调用

<div class="rn-info-content">
     <h2 class="rn-contact-title">Address</h2>
         <address>
             @Html.Raw(ViewData["Address"].ToString())
         </address>
</div>

答案 15 :(得分:-1)

而不是经历这个 你可以随时使用另一种快速的方法

在共享目录中创建一个新的局部视图,并在布局中调用部分视图

@Html.Partial("MyPartialView")

在您的局部视图中,您可以调用数据库并执行您想要执行的操作

@{
    IEnumerable<HOXAT.Models.CourseCategory> categories = new HOXAT.Models.HOXATEntities().CourseCategories;
}

<div>
//do what ever here
</div>

假设您已添加实体框架数据库

答案 16 :(得分:-1)

令人难以置信的是,在这里没有人说过。通过基本控制器传递视图模型是一团糟。我们正在使用用户声明将信息传递到布局页面(例如,用于在导航栏上显示用户数据)。 还有一个优势。数据是通过cookie存储的,因此不需要通过局部检索每个请求中的数据。 只需做一些谷歌搜索“ asp网络身份声明”即可。

答案 17 :(得分:-5)

您可以这样使用:

 @{ 
    ApplicationDbContext db = new ApplicationDbContext();
    IEnumerable<YourModel> bd_recent = db.YourModel.Where(m => m.Pin == true).OrderByDescending(m=>m.ID).Select(m => m);
}
<div class="col-md-12">
    <div class="panel panel-default">
        <div class="panel-body">
            <div class="baner1">
                <h3 class="bb-hred">Recent Posts</h3>
                @foreach(var item in bd_recent)
                {
                    <a href="/BaiDangs/BaiDangChiTiet/@item.ID">@item.Name</a>
                }
            </div>
        </div>
    </div>
</div>