我有一个有布局页面的网站。然而,这个布局页面有数据,所有页面模型都必须提供这样的页面标题、页面名称和我们实际所在的位置,用于执行某些操作的 HTML 助手。此外,每个页面都有自己的视图模型属性。
我怎样才能做到这一点?输入布局似乎是个坏主意,但我如何传递这些信息?
我有一个有布局页面的网站。然而,这个布局页面有数据,所有页面模型都必须提供这样的页面标题、页面名称和我们实际所在的位置,用于执行某些操作的 HTML 助手。此外,每个页面都有自己的视图模型属性。
我怎样才能做到这一点?输入布局似乎是个坏主意,但我如何传递这些信息?
如果您需要将相同的属性传递给每个页面,那么创建一个可供所有视图模型使用的基本视图模型将是明智的。然后,您的布局页面可以采用此基本模型。
如果此数据背后需要逻辑,则应将其放入所有控制器使用的基本控制器中。
您可以做很多事情,重要的方法是不要在多个地方重复相同的代码。
编辑:从下面的评论更新
这是一个简单的例子来演示这个概念。
创建一个所有视图模型都将继承的基础视图模型。
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>
最后在action方法中设置数据。
public class HomeController
{
public ActionResult Index()
{
return this.View(new HomeViewModel { Name = "Bacon" });
}
}
我在布局中使用 RenderAction html helper 进行剃刀。
@{
Html.RenderAction("Action", "Controller");
}
我需要它来制作简单的字符串。所以我的操作返回字符串并在视图中轻松写下来。但是如果您需要复杂的数据,您可以返回 PartialViewResult 和模型。
public PartialViewResult Action()
{
var model = someList;
return PartialView("~/Views/Shared/_maPartialView.cshtml", model);
}
您只需将模型放在您创建的局部视图“_maPartialView.cshtml”的开头
@model List<WhatEverYourObjeIs>
然后,您可以在带有 html 的局部视图中使用模型中的数据。
另一种选择是创建一个单独的 LayoutModel 类,其中包含您在布局中需要的所有属性,然后将此类的一个实例填充到 ViewBag 中。我使用 Controller.OnActionExecuting 方法来填充它。然后,在布局开始时,您可以从 ViewBag 中拉回这个对象,并继续访问这个强类型对象。
据推测,主要用例是为所有(或大部分)控制器操作获取视图的基本模型。
鉴于此,我使用了其中几个答案的组合,主要支持 Colin Bacon 的答案。
这仍然是控制器逻辑是正确的,因为我们正在填充视图模型以返回视图。因此,放置它的正确位置是在控制器中。
我们希望这发生在所有控制器上,因为我们将它用于布局页面。我将它用于在布局页面中呈现的部分视图。
我们还想要强类型 ViewModel 的额外好处
因此,我创建了 BaseViewModel 和 BaseController。所有 ViewModels 控制器将分别继承自 BaseViewModel 和 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!";
}
}
请注意从这个 SO 帖子中获取的OnActionExecuted的使用
家庭控制器
public class HomeController : BaseController
{
public ActionResult Index(string id)
{
HomeIndexModel model = new HomeIndexModel();
// populate HomeIndexModel ...
return View(model);
}
}
基础视图模型
public class BaseViewModel
{
public string AwesomeModelProperty { get; set; }
public FooterModel FooterModel { get; set; }
}
主页查看模型
public class HomeIndexModel : BaseViewModel
{
public string FirstName { get; set; }
// other awesome properties
}
页脚模型
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>
希望这会有所帮助。
还有另一种处理方式。从架构的角度来看,这可能不是最干净的方式,但它避免了与其他答案相关的很多痛苦。只需在 Razor 布局中注入服务,然后调用获取必要数据的方法:
@inject IService myService
然后在布局视图中:
@if (await myService.GetBoolValue()) {
// Good to go...
}
同样,在架构方面并不干净(显然服务不应该直接注入到视图中),但它可以完成工作。
您不必弄乱操作或更改模型,只需使用基本控制器并从布局视图上下文中转换现有控制器。
使用所需的公共数据(标题/页面/位置等)和操作初始化创建一个基本控制器...
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 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)
我认为这些答案中的任何一个对于大型企业级应用程序都不够灵活。我不喜欢过度使用 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>
这样做不会干扰页面控制器或视图模型的编码。
创建一个代表布局视图模型的基础视图是一种糟糕的方法。想象一下,您想要一个模型来表示布局中定义的导航。你会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)
{
}
}
其他答案几乎涵盖了有关如何将模型传递到布局页面的所有内容。但是我找到了一种方法,您可以使用它动态地将变量传递到布局页面,而无需在布局中使用任何模型或局部视图。让我们说你有这个模型 -
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>
您还可以使用RenderSection,它可以帮助您将Model
数据注入_Layout
视图。
您可以注入View Model
数据Json
、、、、Script
等CSS
HTML
Json
在这个例子中,我从我的视图注入Index
到Layout
视图。
索引.chtml
@section commonLayoutData{
<script>
var products = @Html.Raw(Json.Encode(Model.ToList()));
</script>
}
_Layout.cshtml
@RenderSection("commonLayoutData", false)
这消除了创建单独的 Base 的需要View Model
。
希望能帮助某人。
为什么没有人建议 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 类中的其他代码和逻辑。
PS不要忘记在_ViewImports.cshtml中添加ViewDataExtensions的命名空间
您可以在 App_Code 文件夹中创建一个 razor 文件,然后从您的视图页面访问它。
项目>存储库/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;
}
}
}
项目>视图/共享/_Layout.cshtml(或任何其他 .cshtml 文件)
<div>
@IdentityRepositoryViewFunctions.GetCurrentUserName
</div>
在 .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
。
使用静态字符串(如页面标题、页面名称和位置等)的最佳方式是通过 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>
而不是经历这个,你总是可以使用另一种也很快的方法
在共享目录中创建一个新的局部视图,并将布局中的局部视图称为
@Html.Partial("MyPartialView")
在您的局部视图中,您可以调用您的数据库并执行您想做的任何事情
@{
IEnumerable<HOXAT.Models.CourseCategory> categories = new HOXAT.Models.HOXATEntities().CourseCategories;
}
<div>
//do what ever here
</div>
假设您已经添加了实体框架数据库
令人难以置信的是,这里没有人这么说。通过基本控制器传递视图模型是一团糟。我们正在使用用户声明将信息传递到布局页面(例如,用于在导航栏上显示用户数据)。还有一个优势。数据通过 cookie 存储,因此无需通过部分检索每个请求中的数据。只需做一些谷歌搜索“asp 网络身份声明”。
我所做的很简单,而且很有效
在任何控制器中声明静态属性,或者如果您愿意,可以使用静态值创建一个数据类:
public static username = "Admin";
public static UserType = "Administrator";
这些值可以由控制器根据操作进行更新。稍后您可以在 _Layout 中使用它们
在 _layout.cshtml
@project_name.Controllers.HomeController.username
@project_name.Controllers.HomeController.UserType
你可以这样使用:
@{
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>