如何管理共享布局中显示的模型?
How to manage models that are being displayed in a shared layout?
我正在开发一个新的 MVC/Razor 网站,我们已经到了需要放入真实菜单结构的地步。目前,我们的菜单被硬编码到全局共享布局中,我们需要从代码中填充菜单,以便我们可以确定在登录后为用户显示哪些菜单。
我们希望菜单几乎出现在每个页面上,登录和其他一些特殊情况除外。所以我们将继续将它们之一呈现在共享布局中。
创建一个呈现菜单项的@Html.DisplayFor 模板看起来很简单。问题是,如何获取数据呢?
共享布局与大量视图一起使用,处理许多不同的模型,从许多控制器加载。
向每个模型添加一个 List 成员,然后将其填充到每个控制器中,这看起来既乏味又麻烦,而且容易出错。
或者我们可以跳过将集合添加到每个模型,而是让每个控制器将其粘贴到 ViewBag 中。这似乎也不是那么好。
为了避免为每个控制器重复此操作,我能够想到的唯一可能性是定义一个公共基础 class,派生自 Controller,所有控制器反过来,使用共享布局可以将 MenuItem 集合填充到 ViewBag 中。
但我想知道我是否遗漏了什么。有没有一些首选的方法来处理这种情况?
Shared/base 视图模型是您提到的一种方式,但在我看来它不是很好 "single responsibility"。如您所述,必须从基础模型继承并在每个页面上添加菜单项是乏味的。
如果我是你,我会使用:
@Html.Action("action", "controller")
https://msdn.microsoft.com/en-us/library/ee703457.aspx
这将调用一个操作方法,然后您可以绑定一个特定的菜单模型以及 return 一个将在调用它的视图中呈现的特定局部视图
@Html.Action("TopMenu", "Controller")
[ChildActionOnly]
public PartialViewResult TopMenu()
{
return PartialView(new MenuModel());
}
创建一个TopMenu.cshtml
您甚至可以将值传递到控制器操作以修改输出 model/view。
您可以从 layout/shared 视图调用 @Html.Action。
编辑
添加了评论中突出显示的 [ChildActionOnly],因为这会阻止访问操作,除非它是从父操作调用的,并且 应该 在这里使用
如果我正确理解你的问题...
我更喜欢 MasterViewModel
每个页面的每个 ViewModel 都从中继承。 MasterViewModel
包含每个页面共有的内容,例如用户名、导航规则等。共享布局然后使用 @model MasterViewModel
,它由 ActionFilterAttribute 填充。
public class MasterViewModel
{
public IEnumerable<string> NavItems {get; set;}
}
public class HomeIndexViewModel : MasterViewModel
{
// stuff for the home view
}
然后我有一个MasterViewModelAttribute
:
public class MasterViewModelAttribute : ActionFilterAttribute
{
[Inject]
public IDatabase Database { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
ViewResultBase viewResult = filterContext.Result as ViewResultBase;
// not a view result
if (viewResult == null)
{
return;
}
MasterViewModel model = viewResult.ViewData.Model as MasterViewModel;
// not a MasterViewModel view model
if (model == null)
{
return;
}
model.NavItems = // set your nav items
}
}
然后,每个控制器都派生自一个实现属性的基本控制器:
[MasterViewModel]
public class BaseController : Controller
{
}
并且对于每个控制器:
public class HomeController : BaseController
{
public ActionResult Index()
{
return View(new HomeIndexViewModel());
}
}
最后,布局:
@model MasterViewModel
// omitted
<div class="nav">
@foreach (var item in Model.NavItems)
{
<a></a>
}
</div>
我喜欢该模式的一点是,您仍然可以通过使用 ViewModel(而不是 ViewBag)进行强大的类型检查,并且您不必在需要时处理所有额外的导航内容例如,单元测试一个动作。
与使用局部视图和@Html.Action() 相比,这还有一个优势,即您不会为每次页面加载都向服务器发出第二次请求。
我正在开发一个新的 MVC/Razor 网站,我们已经到了需要放入真实菜单结构的地步。目前,我们的菜单被硬编码到全局共享布局中,我们需要从代码中填充菜单,以便我们可以确定在登录后为用户显示哪些菜单。
我们希望菜单几乎出现在每个页面上,登录和其他一些特殊情况除外。所以我们将继续将它们之一呈现在共享布局中。
创建一个呈现菜单项的@Html.DisplayFor 模板看起来很简单。问题是,如何获取数据呢?
共享布局与大量视图一起使用,处理许多不同的模型,从许多控制器加载。
向每个模型添加一个 List 成员,然后将其填充到每个控制器中,这看起来既乏味又麻烦,而且容易出错。
或者我们可以跳过将集合添加到每个模型,而是让每个控制器将其粘贴到 ViewBag 中。这似乎也不是那么好。
为了避免为每个控制器重复此操作,我能够想到的唯一可能性是定义一个公共基础 class,派生自 Controller,所有控制器反过来,使用共享布局可以将 MenuItem 集合填充到 ViewBag 中。
但我想知道我是否遗漏了什么。有没有一些首选的方法来处理这种情况?
Shared/base 视图模型是您提到的一种方式,但在我看来它不是很好 "single responsibility"。如您所述,必须从基础模型继承并在每个页面上添加菜单项是乏味的。
如果我是你,我会使用:
@Html.Action("action", "controller")
https://msdn.microsoft.com/en-us/library/ee703457.aspx
这将调用一个操作方法,然后您可以绑定一个特定的菜单模型以及 return 一个将在调用它的视图中呈现的特定局部视图
@Html.Action("TopMenu", "Controller")
[ChildActionOnly]
public PartialViewResult TopMenu()
{
return PartialView(new MenuModel());
}
创建一个TopMenu.cshtml
您甚至可以将值传递到控制器操作以修改输出 model/view。
您可以从 layout/shared 视图调用 @Html.Action。
编辑
添加了评论中突出显示的 [ChildActionOnly],因为这会阻止访问操作,除非它是从父操作调用的,并且 应该 在这里使用
如果我正确理解你的问题...
我更喜欢 MasterViewModel
每个页面的每个 ViewModel 都从中继承。 MasterViewModel
包含每个页面共有的内容,例如用户名、导航规则等。共享布局然后使用 @model MasterViewModel
,它由 ActionFilterAttribute 填充。
public class MasterViewModel
{
public IEnumerable<string> NavItems {get; set;}
}
public class HomeIndexViewModel : MasterViewModel
{
// stuff for the home view
}
然后我有一个MasterViewModelAttribute
:
public class MasterViewModelAttribute : ActionFilterAttribute
{
[Inject]
public IDatabase Database { get; set; }
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
ViewResultBase viewResult = filterContext.Result as ViewResultBase;
// not a view result
if (viewResult == null)
{
return;
}
MasterViewModel model = viewResult.ViewData.Model as MasterViewModel;
// not a MasterViewModel view model
if (model == null)
{
return;
}
model.NavItems = // set your nav items
}
}
然后,每个控制器都派生自一个实现属性的基本控制器:
[MasterViewModel]
public class BaseController : Controller
{
}
并且对于每个控制器:
public class HomeController : BaseController
{
public ActionResult Index()
{
return View(new HomeIndexViewModel());
}
}
最后,布局:
@model MasterViewModel
// omitted
<div class="nav">
@foreach (var item in Model.NavItems)
{
<a></a>
}
</div>
我喜欢该模式的一点是,您仍然可以通过使用 ViewModel(而不是 ViewBag)进行强大的类型检查,并且您不必在需要时处理所有额外的导航内容例如,单元测试一个动作。
与使用局部视图和@Html.Action() 相比,这还有一个优势,即您不会为每次页面加载都向服务器发出第二次请求。