如何管理共享布局中显示的模型?

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() 相比,这还有一个优势,即您不会为每次页面加载都向服务器发出第二次请求。