ASP.NET MVC Razor将模型传递到布局


Answers:


66

如果您遇到此问题,似乎您对视图模型进行了建模有点错误。

就我个人而言,我永远不会键入布局页面。但是,如果要执行此操作,则应该有一个继承其他视图模型的基础视图模型,然后将布局键入基础视图模型,然后将页面分页到特定视图。


11
“就我个人而言,我永远不会键入版面页面。” 为什么?我的意思是,您如何处理出现在“所有页面”中的侧面动态内容?您是否从视图中跳过控制器?/也许您的意思是从布局中使用RenderAction?(我现在正在看)
eglasius

52
@eglasius,根据我们谈论的内容类型,我使用的解决方案会有所不同。但是一种常见的解决方案是使用RenderAction在布局页面中渲染需要自己数据的零件。我不喜欢输入布局页面的原因是,它将迫使您始终在所有特定视图模型中继承“基本”视图模型。以我的经验,这通常不是一个好主意,很多时候更改设计为时已晚(否则会花费很长时间)。
Mattias Jakobsson

2
如果我想通过聚合而不是继承包括基本模型,该怎么办?从设计角度来看,这是一种完全合法的方法。那我该如何处理布局呢?
Fyodor Soikin 2011年

4
我有2个解决方案:布局的通用模型,因此我可以将MyLayoutModel <MyViewModel>用于视图模型,仅将RenderPartial与MyViewModel一起使用。或使用RenderAction对静态缓存的部分和ajax调用对动态部分进行部分渲染。但是我更喜欢第一个解决方案,因为它对搜索引擎更友好,并且可以轻松地与Ajax更新结合。
Softlion 2012年

4
正是在完成遗留代码的地方进行工作。这是一场噩梦。不要输入您的布局...请!

79
  1. 使用您想要使用的任何类型,将一个属性(称为MainLayoutViewModel(或其他))添加到您的控制器(或基本控制器)中。
  2. 在您的控制器(或基本控制器)的构造函数中,实例化类型并将其设置为属性。
  3. 将其设置为ViewData字段(或ViewBag)
  4. 在“布局”页面中,将该属性转换为您的类型。

示例:控制器:

public class MyController : Controller
{
    public MainLayoutViewModel MainLayoutViewModel { get; set; }

    public MyController()
    {
        this.MainLayoutViewModel = new MainLayoutViewModel();//has property PageTitle
        this.MainLayoutViewModel.PageTitle = "my title";

        this.ViewData["MainLayoutViewModel"] = this.MainLayoutViewModel;
    }

}

布局页面顶部示例

@{
var viewModel = (MainLayoutViewModel)ViewBag.MainLayoutViewModel;
}

现在,您可以在布局页面中引用变量“ viewModel”,并且可以完全访问键入的对象。

我喜欢这种方法,因为是由控制器控制布局,而各个页面视图模型仍然与布局无关。

MVC Core的注意事项


第一次调用每个动作时,Mvc Core似乎吹走了ViewData / ViewBag的内容。这意味着在构造函数中分配ViewData不起作用。但是,有效的方法是使用IActionFilter和在中执行完全相同的工作OnActionExecuting。穿上MyActionFilter你的MyController

public class MyActionFilter: Attribute, IActionFilter
    {
        public void OnActionExecuted(ActionExecutedContext context)
        {
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var myController= context.Controller as MyController;

            if (myController!= null)
            {
                myController.Layout = new MainLayoutViewModel
                {

                };

                myController.ViewBag.MainLayoutViewModel= myController.Layout;
            }
        }
    }

1
我明白...但是动态/广播对于剃刀页面非常重要。您可以做的一件事是在MainLayoutViewModel中添加一个静态方法,该方法为您进行强制转换(例如MainLayoutViewModel.FromViewBag(this.ViewBag)),因此至少强制转换发生在一个地方,并且您可以在那里更好地处理异常。
BlackjacketMack

@BlackjacketMack好的方法,我使用上面的方法并通过对bcoz进行了一些修改来实现它,我有一个差异要求,这真的帮助了我。如果可以的话,我们可以使用TempData达到相同的效果吗,然后如何,如果没有,请plz告诉我为什么不能使用它。再次感谢。
Zaker

2
@User-TempData使用Session,对我来说总是有点困惑。我的理解是,它是“一次读取”的,因此,一旦您阅读它,它就会将它从会话中删除(或者可能是在请求结束时)。您可能会将会话存储在Sql Server(或Dynamo Db)中,因此请考虑以下事实:您必须序列化MasterLayoutViewModel ...而不是您最可能想要的。因此,基本上,将其设置为ViewData可以将其存储在内存中,并且灵活一些,该字典非常合适。
BlackjacketMack 2015年

足够简单,我使用了您的解决方案,但是,我是MVC的新手,所以我只是想知道这是否是一种好习惯?还是至少不是一个坏人?
Karim AG

1
嗨Karim AG,我想两者兼而有之。我倾向于考虑将内容存储在ViewData中是一种不好的做法(很难跟踪,基于字典,不是真正的类型)...但是...在强类型的对象中键入所有布局属性是一种很好的做法。因此,我要妥协地说,好吧,让我们在其中存储一件事,但将其其余内容锁定到一个好的强类型ViewModel中。
BlackjacketMack

30

这是非常基本的东西,您需要做的就是创建一个基本视图模型并确保所有!我的意思是全部!您将永远使用该布局的视图将收到使用该基本模型的视图!

public class SomeViewModel : ViewModelBase
{
    public bool ImNotEmpty = true;
}

public class EmptyViewModel : ViewModelBase
{
}

public abstract class ViewModelBase
{
}

在_Layout.cshtml中:

@model Models.ViewModelBase
<!DOCTYPE html>
  <html>
  and so on...

在家庭控制器的Index(例如)方法中:

    public ActionResult Index()
    {
        var model = new SomeViewModel()
        {
        };
        return View(model);
    }

Index.cshtml:

@model Models.SomeViewModel

@{
  ViewBag.Title = "Title";
  Layout = "~/Views/Shared/_Layout.cshtml";
}

<div class="row">

我不同意将模型传递给_layout是错误的,可以传递一些用户信息,并且可以在控制器继承链中填充数据,因此仅需要一个实现。

显然,出于更高级的目的,您应该考虑使用注入创建自定义静态contaxt,并将该模型名称空间包含在_Layout.cshtml中。

但对于基本用户,这可以解决问题


我同意你的看法。谢谢。
塞巴斯蒂安·格雷罗

1
并只想提一下,它也可以用于接口而不是基类
VladL

27

常见的解决方案是制作一个基本视图模型,其中包含布局文件中使用的属性,然后从基本模型继承到各个页面上使用的模型。

这种方法的问题在于,您现在陷入了一个模型只能从另一个类继承的问题,并且可能您的解决方案是您无论如何都不能对想要使用的模型使用继承。

我的解决方案还从基本视图模型开始:

public class LayoutModel
{
    public LayoutModel(string title)
    {
        Title = title;
    }

    public string Title { get;}
}

然后,我使用的是LayoutModel的通用版本,它是从LayoutModel继承的,如下所示:

public class LayoutModel<T> : LayoutModel
{
    public LayoutModel(T pageModel, string title) : base(title)
    {
        PageModel = pageModel;
    }

    public T PageModel { get; }
}

通过此解决方案,我不再需要在布局模型和模型之间继承。

现在,我可以像这样在Layout.cshtml中使用LayoutModel:

@model LayoutModel
<!doctype html>
<html>
<head>
<title>@Model.Title</title>
</head>
<body>
@RenderBody()
</body>
</html>

在页面上,您可以使用如下通用类:

@model LayoutModel<Customer>
@{
    var customer = Model.PageModel;
}

<p>Customer name: @customer.Name</p>

从您的控制器中,您只需返回LayoutModel类型的模型:

public ActionResult Page()
{
    return View(new LayoutModel<Customer>(new Customer() { Name = "Test" }, "Title");
}

1
指出多重继承问题以及如何处理的奖励!对于可伸缩性,这是一个更好的答案。
布雷特·斯潘塞

1
我认为最好的解决方案。从体系结构的角度来看,它是可伸缩和可维护的。这是正确的方法。我从不喜欢ViewBag或ViewData .....对我来说,它们似乎都很笨拙。
乔纳森·阿尔法罗

10

为什么不使用我自己的特定控制器添加新的Partial View,将所需的模型传递给Partial View,最后使用RenderPartial或RenderAction在Layout.cshtml上渲染提及的Partial View?

我使用这种方法来显示已登录用户的信息,例如姓名,个人资料图片等。


2
您能详细说明一下吗?我希望通过链接链接到涉及此技术的某些博客文章
J86,2017年

这行得通,但是为什么会对性能造成影响?您必须等待控制器完成的所有处理,返回视图,才使用户的浏览器发出ANOTHER请求来获取所需的数据。还有,如果您的版式取决于要正确渲染的数据。恕我直言,这不是这个问题的答案。
布雷特·斯宾塞

3

这个老问题,但仅提及针对MVC5开发人员的解决方案,您可以使用与Model视图相同的属性。

Model视图和布局中的属性都与同一个ViewDataDictionary对象相关联,因此您无需做任何额外的工作即可将模型传递到布局页面,也不必@model MyModelName在布局中声明。

但是请注意,当您@Model.XXX在布局中使用intelliSense上下文菜单时,它不会出现,因为Model此处是一个动态对象,就像ViewBag


2

也许从技术上讲,这不是正确的处理方法,但是对我来说,最简单,最合理的解决方案是只创建一个类并在布局中实例化它。这是一次性的例外方式,否则它是正确的方式。如果这样做比在布局中做得更多,那么您需要认真地重新考虑您的工作,并可能在继续进行项目之前阅读更多的教程。

public class MyLayoutModel {
    public User CurrentUser {
        get {
            .. get the current user ..
        }
    }
}

然后在视图中

@{
    // Or get if from your DI container
    var myLayoutModel = new MyLayoutModel();
}

在.net core中,您甚至可以跳过它并使用依赖项注入。

@inject My.Namespace.IMyLayoutModel myLayoutModel

这是那些阴暗的领域之一。但是,鉴于我在这里看到的极其复杂的替代方案,我认为以实用性为名,这绝对不是一个好的例外。特别是如果您要确保它简单并确保任何繁琐的逻辑(我认为确实不应该有任何逻辑,但要求有所不同)位于它所属的另一个类/层中。为了基本上只使用一个视图,它肯定比污染所有控制器或模型更好。


2

还有另一种存档方法。

  1. 只需为所有控制器实现BaseController类

  2. BaseController类中,创建一个返回Model类的方法,例如。

public MenuPageModel GetTopMenu() 
{    

var m = new MenuPageModel();    
// populate your model here    
return m; 

}
  1. Layout页面中,您可以调用该方法GetTopMenu()
@using GJob.Controllers

<header class="header-wrapper border-bottom border-secondary">
  <div class="sticky-header" id="appTopMenu">
    @{
       var menuPageModel = ((BaseController)this.ViewContext.Controller).GetTopMenu();
     }
     @Html.Partial("_TopMainMenu", menuPageModel)
  </div>
</header>

0

假设您的模型是对象(或单个对象)的集合。对于模型中的每个对象,请执行以下操作。

1)将要显示的对象放在ViewBag中。例如:

  ViewBag.YourObject = yourObject;

2)在_Layout.cshtml的顶部添加一个using语句,其中包含对象的类定义。例如:

@using YourApplication.YourClasses;

3)当您在_Layout中引用yourObject时,将其强制转换。由于您在(2)中所做的操作,您可以应用强制转换。


-2
public interface IContainsMyModel
{
    ViewModel Model { get; }
}

public class ViewModel : IContainsMyModel
{
    public string MyProperty { set; get; }
    public ViewModel Model { get { return this; } }
}

public class Composition : IContainsMyModel
{
    public ViewModel ViewModel { get; set; }
}

在布局中使用IContainsMyModel。

解决了。接口规则。


1
不知道为什么你不赞成投票。使用界面(类似于您在此处所做的操作)在我的上下文中有效。
哥斯达黎加'18

-6

例如

@model IList<Model.User>

@{
    Layout="~/Views/Shared/SiteLayout.cshtml";
}

阅读有关新的@model指令的更多信息


但是,如果我想将集合的第一个元素传递给Layout模型呢?
SiberianGuy 2010年

您必须获取控制器中的第一个元素,并将模型设置为@model Model.User
Martin Fabik 2010年

但我希望页面获得IList和Layout-仅是第一个元素
SiberianGuy 2010年

如果我没看错,您希望模型成为IList <SomeThing>,并在视图中获取集合的第一个元素?如果是这样,请使用@ Model.First()
Martin Fabik 2010年

6
海报询问的是如何将模型传递到_Layout.cshtml页面..而不是使用布局的主视图。
Pure.Krome
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.