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

Rus*_*ino 115 asp.net-mvc-4

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

我怎样才能做到这一点?键入布局似乎是一个坏主意但我如何通过这些信息呢?

Col*_*con 136

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

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

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

编辑:从下面的评论更新

这是一个演示概念的简单示例.

创建所有视图模型将继承的基本视图模型.

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

public class HomeViewModel : ViewModelBase
{
}
Run Code Online (Sandbox Code Playgroud)

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

@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>
Run Code Online (Sandbox Code Playgroud)

最后在action方法中设置数据.

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

  • 但这不是要求每个控制器和每个动作都包含{Name ="Bacon"}代码吗?如果我想向ViewModelBase添加另一个属性,我必须转到每个控制器和每个操作并添加代码来填充该属性?您提到"如果需要逻辑[...],则应将其放入基本控制器[...]".如何在每个控制器和每个动作中消除这些重复的代码? (15认同)
  • 但数据用于布局.如何将数据传递给布局? (9认同)
  • 这种方法的问题是有时并不是每个视图都有ViewModel,所以在这种情况下这不起作用:O / (7认同)
  • @Lee如果它是所有页面的通用数据,那么你可以在其中放置一个基本控制器.然后,您的控制器将继承此基本控制器.例如`public class HomeController:BaseController`.这样,公共代码只需要编写一次,并且可以应用于所有控制器. (5认同)
  • 完善!我看到了我的错误.我忘了将模型传递给视图..这是一个蹩脚的错误.谢谢! (2认同)

Bur*_*urk 64

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

@{
   Html.RenderAction("Action", "Controller");
 }
Run Code Online (Sandbox Code Playgroud)

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

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

您只需要将您创建的部分视图"_maPartialView.cshtml"的模型开头

@model List<WhatEverYourObjeIs>
Run Code Online (Sandbox Code Playgroud)

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

  • 这是迄今为止最好的答案! (15认同)

Den*_*kem 35

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

  • 我不明白这给你带来了什么.如果你有一个包含布局所需的所有属性的类,为什么还要将它添加到ViewBag而只需要再次将其强制转换?在布局视图中使用模型,您仍然可以在`OnActionExecuting`中填充模型.使用ViewBag也意味着你在控制器中放松了类型安全,从来都不是一件好事. (6认同)
  • @ColinBacon此选项的另一个优点是您的操作不需要始终具有视图模型.此外,我认为开发人员需要知道他们必须始终从基础继承他们的视图模型是一个缺点. (5认同)
  • 这给了我的是能够为布局添加模型,而不必在已经存在的项目中重构所有模型以继承所有控制器的所有方法中的单个"超级"模型.如果您从头开始,则可以选择从公共根中派生所有模型. (3认同)
  • 绝对是最好的解决方案,我没有看到任何缺点. (2认同)

dri*_*zie 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!";
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意从此SO帖子中使用的OnActionExecuted

HomeController的

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

        // populate HomeIndexModel ...

        return View(model);
    }
}
Run Code Online (Sandbox Code Playgroud)

BaseViewModel

public class BaseViewModel
{
    public string AwesomeModelProperty { get; set; }
    public FooterModel FooterModel { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

HomeViewModel

public class HomeIndexModel : BaseViewModel
{

    public string FirstName { get; set; }

    // other awesome properties
}
Run Code Online (Sandbox Code Playgroud)

FooterModel

public class FooterModel
{
    public string FooterModelProperty { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

_Nav.cshtml

@model string
<nav>
    <ul>
        <li>
            <a href="@Model" target="_blank">Mind Blown!</a>
        </li>
    </ul>
</nav>
Run Code Online (Sandbox Code Playgroud)

希望这会有所帮助.

  • 我使用了这种方法,但是更喜欢从接口而不是基类继承。所以我做了:var model = filterContext.Controller.ViewData.Model as IBaseViewModel if(model!= null){model.AwesomeModelProperty =“ Awesome Property Value”; } (2认同)
  • 很好的答案,我比其他所有人都喜欢这个。 (2认同)

Car*_*lin 8

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

使用所需的公共数据(标题/页面/位置等)和操作初始化创建基本控制器...

public abstract class _BaseController:Controller {
    public Int32 MyCommonValue { get; private set; }

    protected override void OnActionExecuting(ActionExecutingContext filterContext) {

        MyCommonValue = 12345;

        base.OnActionExecuting(filterContext);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public class UserController:_BaseController {...
Run Code Online (Sandbox Code Playgroud)

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

@{
    var myController = (_BaseController)ViewContext.Controller;
}
Run Code Online (Sandbox Code Playgroud)

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

@myController.MyCommonValue
Run Code Online (Sandbox Code Playgroud)

  • .net core 中与此等效的是什么?由于 ViewContext.Controller 不存在并且继承链发生了一些变化 (2认同)

And*_*rew 7

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

@inject IService myService
Run Code Online (Sandbox Code Playgroud)

然后在布局视图中:

@if (await myService.GetBoolValue()) {
   // Good to go...
}
Run Code Online (Sandbox Code Playgroud)

同样,在架构方面不干净(显然服务不应该直接注入到视图中),但它完成了工作。

  • 再想一想,也许你是对的。事实上,这种方法避免了如此多的痛苦,这表明它可能是最干净的方法。我一直在开发一个非常大的 ASP.NET Core 应用程序,我将这种模式用于导航面包屑逻辑、大多数页面上的标头数据等。我通过这样做避免了很多痛苦。 (3认同)

Yak*_*nor 5

如果你想传递整个模型,请在布局中像这样:

@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>
Run Code Online (Sandbox Code Playgroud)

并将其添加到控制器中:

public ActionResult _Header(ViewAsModelBase model)
Run Code Online (Sandbox Code Playgroud)


小智 5

我认为这些答案对于大型企业级应用程序来说都不够灵活。我不喜欢过度使用 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

<h1>@model.Title</h1>
Run Code Online (Sandbox Code Playgroud)

或者...

<h1>@ViewBag.LayoutViewModel.Title</h1>
Run Code Online (Sandbox Code Playgroud)

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