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

我有一个网站,有一个布局页面。但是,这个布局页面的数据,所有的页面模型必须提供这样的页面标题,页面名称和位置,我们实际上是一个 HTML 助手,我做了一些动作。此外,每个页面都有自己的视图模型属性。

我怎么能这么做?这似乎是一个坏主意类型的布局,但我如何传递这些信息?

183803 次浏览

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

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

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

编辑: 根据下面的评论更新

这里有一个简单的例子来演示这个概念。

创建所有视图模型都将从中继承的基视图模型。

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

如果你想通过一个完整的模型去这样的布局:

@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)

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

我在布局中使用了 RenderActionhtml 助手来处理剃须刀。

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

我需要它为简单的字符串。因此,我的行动返回字符串,并写下它很容易看到。 但是如果您需要复杂的数据,您可以返回 PartialViewResult 和 model。

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

您只需要将您的模型从您创建的部分视图“ _ maPartialView.cshtml”开始

@model List<WhatEverYourObjeIs>

然后,您可以在带有 html 的部分视图中使用模型中的数据。

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

鉴于此,我综合使用了几个答案,主要依据科林 · 培根的答案。

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

我们希望这种情况发生在所有控制器上,因为我们使用这个布局页面。我将它用于在布局页面中呈现的部分视图。

我们还希望获得强类型 ViewModel 的额外好处

因此,我创建了一个 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!";
}
}

请注意从 这个职位中取出的 行动执行的用法

家庭控制器

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

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>

希望这能帮上忙。

而不是经历这些 你总是可以使用另一种方法,这也是快速的

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

@Html.Partial("MyPartialView")

在您的部分视图中,您可以调用 db 并执行任何您想要执行的操作

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


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

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

你可以这样使用:

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

我认为这些答案对于大型企业级应用程序来说都不够灵活。我不喜欢过度使用 ViewBag,但在这种情况下,为了灵活性,我会破例一次。我会这么做。

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

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>

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

其他的答案几乎涵盖了关于如何将模型传递到布局页面的所有内容。但是我发现了一种方法,可以使用这种方法将变量动态地传递到布局页面,而无需在布局中使用任何模型或部分视图。假设你有这个模型

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>

创建表示布局视图模型的基础视图是一种糟糕的方法。假设您希望拥有一个模型,该模型表示在布局中定义的导航。你会做 CustomersViewModel : LayoutNavigationViewModel吗?为什么?为什么要通过解决方案中的每个视图模型传递导航模型数据?

布局视图模型应该是专用的,它本身不应该强制其他视图模型依赖于它。

相反,您可以在 _Layout.cshtml文件中执行以下操作:

@{ var model = DependencyResolver.Current.GetService<MyNamespace.LayoutViewModel>(); }

最重要的是,我们不需要 new LayoutViewModel(),我们将得到所有的依赖关系,LayoutViewModel有,为我们解决。

例如:。

public class LayoutViewModel
{
private readonly DataContext dataContext;
private readonly ApplicationUserManager userManager;


public LayoutViewModel(DataContext dataContext, ApplicationUserManager userManager)
{
}
}

您不必改变操作或模型,只需使用基本控制器并从布局视图上下文强制转换现有控制器即可。

使用所需的公共数据(title/page/location 等)和操作初始化创建一个基本控制器..。

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

更新

您还可以创建一个允许您使用 this的页面扩展。

//Allows typed "this.Controller()." in cshtml files
public static class MyPageExtensions {
public static _BaseController Controller(this WebViewPage page) => Controller<_BaseController>(page);
public static T Controller<T>(this WebViewPage page) where T : _BaseController => (T)page.ViewContext.Controller;
}

然后,您只需要记住在需要控制器时使用 this.Controller()即可。

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

或从 _BaseController继承的特定控制器..。

@{
var myController = this.Controller<MyControllerType>();
}

您也可以利用 渲染部门,它有助于您将您的 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的需要。

希望能帮助别人。

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

还有别的办法。从架构的角度来看,这可能不是最干净的方法,但它避免了其他答案所带来的许多痛苦。只需在 Razor 布局中注入一个服务,然后调用获取必要数据的方法:

@inject IService myService

然后在布局视图中:

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

同样,在体系结构方面也不干净(显然服务不应该直接注入到视图中) ,但是它可以完成任务。

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

选择一

在我看来,这似乎是迄今为止最不具侵扰性和最简单的解决问题的方法。没有硬编码的字符串。没有强加的限制。没有魔法代码。没有复杂的代码。

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

选项二

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

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

选择三

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

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 类中使用其他代码和逻辑。

附注: 不要忘记在 _ ViewImport. cshtml 中添加 ViewDataExtended 的名称空间

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

工程项目 > 储存库/ 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;
}
}
}

项目 > 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>

我所做的很简单,也很有效

在任何控制器中声明 Static 属性,或者如果您想要这样,您可以使用静态值创建一个数据类:

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

控制器可以根据操作更新这些值。 稍后您可以在您的 _ 布局中使用它们

In _ layout.cshtml

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

在.NETCore 中,您可以使用视图组件来完成此操作。

Https://learn.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

使用静态字符串(如页面标题、页面名称和位置等)的最佳方法是通过 ViewData 进行定义。只需在 视图开始.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>