ASP.NET MVC Razorはモデルをレイアウトに渡します


97

文字列のLayoutプロパティが表示されます。しかし、モデルをレイアウトに明示的に渡すにはどうすればよいですか?


異なるモデルで同じレイアウトのページがいくつかあります
SiberianGuy

2
:このstackoverflowの質問は、あなたが求めているものを答えるように思わstackoverflow.com/questions/13225315/...
ポール・

この問題は発生していません。Modelで利用可能です_Layout。MVC5を使用しています。
toddmo 2016

回答:


66

この問題がある場合、ビューモデルを少し間違ってモデリングしたようです。

個人的には、レイアウトページを入力することはありません。ただし、それを行う場合は、他のビューモデルが継承する基本ビューモデルを用意し、基本ビューモデルにレイアウトを入力して、特定のページにページを移動する必要があります。


11
「個人的には、レイアウトページを入力することはありません。」どうして?つまり、すべてのページに表示されるサイドダイナミックコンテンツをどのように処理しますか?ビューからコントローラーをスキップしますか?/おそらく、レイアウトからRenderActionを使用するつもりですか?(私は今それを見ているだけです)
eglasius 2011

52
@eglasius、私が使用するソリューションは、話しているコンテンツの種類によって異なります。しかし、一般的な解決策は、RenderActionを使用して、レイアウトページで独自のデータを必要とするパーツをレンダリングすることです。レイアウトページを入力したくないのは、特定のすべてのビューモデルで常に「ベース」ビューモデルを継承する必要があるためです。私の経験では、これは通常、あまり良い考えではなく、多くの場合、デザインを変更するのが遅い(または時間がかかる)ときに問題が発生します。
マティアス・ヤコブソン

2
継承ではなく集計によって基本モデルを含める場合はどうなりますか?デザインの観点から完全に正当な方法です。次に、レイアウトをどのように処理しますか?
フョードルソイキン

4
私は2つの解決策があります。レイアウトの汎用モデルです。ビューモデルにMyLayoutModel <MyViewModel>を使用でき、レイアウトでのみMyViewModelを使用してRenderPartialを使用します。または、静的にキャッシュされたパーツには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コアに関する注意


Mvcコアは、最初に各アクションを呼び出すと、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
わかりました...しかし、ダイナミクス/キャストはかみそりのページのかなり中心です。あなたができることの1つは、キャストを行うMainLayoutViewModelに静的メソッドを追加することです(MainLayoutViewModel.FromViewBag(this.ViewBag)など)。少なくとも1つの場所でキャストが行われ、そこで例外をより適切に処理できます。
BlackjacketMack 14

@BlackjacketMack良いアプローチと私は上記を使用してそれを達成し、いくつかの変更を加えましたbcoz私はdiff要件があり、これは本当に感謝しました。TempDataを使用して同じことを達成できますか?再度、感謝します。
Zaker、2015年

2
@User-TempDataはSessionを使用しており、常に私には少しぎこちない感じがします。私が理解しているのは、それが「一度だけ」であるため、それを読むとすぐに、セッションから削除される(または、リクエストが終了するとすぐに)ことです。セッションをSQL Server(またはDynamo Db)に保存することは可能です。そのため、MasterLayoutViewModelをシリアル化する必要があるということを考慮してください。したがって、基本的には、ViewDataに設定すると、メモリに格納され、目的に適した少し柔軟な辞書に格納されます。
BlackjacketMack

非常に単純ですが、私はあなたのソリューションを使用しましたが、MVCは初めてなので、これは良い方法と考えられているのでしょうか。または少なくとも悪いものではありませんか?
Karim AG

1
こんにちはカリムAG、私はそれは少し両方だと思います。私はViewDataにデータを保存することを悪い習慣として考える傾向があります(追跡するのは難しい、辞書ベースで、実際には型付けされていません)... だから私は妥協します。OK、1つをそこに格納しましょう。残りはしっかりと強く型付けされた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...

ホームコントローラーの(たとえば)インデックスメソッドで:

    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に渡すことはエラーであることに同意しません。一部のユーザー情報を渡すことができ、データはコントローラーの継承チェーンに入力できるため、1つの実装のみが必要です。

明らかに、より高度な目的のために、インジェクションを使用してカスタム静的contaxtを作成することを検討し、そのモデルの名前空間を_Layout.cshtmlに含める必要があります。

しかし、基本的なユーザーの場合、これはトリックを行います


仰るとおりです。どうも。
セバスティアン・ゲレロ

1
アップし、基本クラスの代わりにインターフェイスでも機能することを述べたい
VladL

27

一般的な解決策は、レイアウトファイルで使用されるプロパティを含むベースビューモデルを作成し、ベースモデルから各ページで使用されるモデルに継承することです。

このアプローチの問題は、モデルの問題に固執し、他の1つのクラスからのみ継承できることです。おそらく、意図したモデルで継承を使用できないようなソリューションである可能性があります。

私のソリューションもベースビューモデルから始まります。

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>

そして、ページでは次のような一般的なLayoutModelを使用できます。

@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

必要なモデルを部分ビューに渡し、独自の特定のコントローラーを備えた新しい部分ビューを追加して、最後に、RenderPartialまたはRenderActionを使用して、Layout.cshtmlで上記の部分ビューをレンダリングしないでください。

この方法を使用して、ログインしたユーザーの名前、プロフィール写真などの情報を表示します。


2
これについて詳しく教えてもらえますか?私はこのテクニックを通過し、いくつかのブログ記事へのリンクをいただければと思います
J86

これは機能しますが、なぜパフォーマンスに影響を与えますか?必要なデータを取得するためにユーザーのブラウザーに別の要求を行わせるためにのみ、コントローラーによって実行されるすべての処理を待機し、ビューを返す必要があります。また、レイアウトがデータに依存して適切にレンダリングされるとしたらどうでしょう。私見これはこの質問に対する答えではありません。
ブレットスペンサー、

3

古い質問ですが、MVC5開発者向けのソリューションについてはModel、viewと同じプロパティを使用できます。

Modelビューとレイアウトの両方のプロパティは同じViewDataDictionaryオブジェクトに関連付けられているため、モデルをレイアウトページに渡すために追加の作業を行う必要はなく@model MyModelName、レイアウトで宣言する必要もありません。

ただし@Model.XXX、レイアウトで使用する場合、Modelここはと同様の動的オブジェクトであるため、intelliSenseコンテキストメニューは表示されませんViewBag


2

多分それは技術的にそれを処理する適切な方法ではありませんが、私にとって最も単純で最も合理的な解決策は、クラスを作成してレイアウトにインスタンス化することです。それは、それ以外の場合は正しい方法で行うための1回限りの例外です。これがレイアウトよりも多く行われている場合は、自分の作業を真剣に考え直して、プロジェクトをさらに進める前にいくつかのチュートリアルを読む必要があります。

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

次に、ビューで

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

.netコアでは、それをスキップして依存性注入を使用することもできます。

@inject My.Namespace.IMyLayoutModel myLayoutModel

それは一種の怪しげなそれらの領域の1つです。しかし、私がここで見ている非常に複雑な代替案を考えると、実用性の名の下に作ることは大丈夫な例外以上のものだと思います。特に、それをシンプルに保ち、重いロジック(本当にあるべきではないと主張しますが、要件は異なります)が、それが属する別のクラス/レイヤーにあることを確認する場合。基本的にビューを1つだけにするために、すべてのコントローラーまたはモデルを汚染するよりも確かに優れています。


2

アーカイブするには別の方法があります。

  1. すべてのコントローラーにBaseControllerクラスを実装するだけです。

  2. ではBaseControllerクラスインスタンスのようなモデルクラスを返すメソッドを作成します。

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
なぜあなたが反対票を投じたのかわからない。ここで行ったのと同様のインターフェースを使用すると、私のコンテキストで機能しました。
コスタ

-6

例えば

@model IList<Model.User>

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

新しい@modelディレクティブの詳細を読む


しかし、コレクションの最初の要素をレイアウトモデルに渡したい場合はどうなりますか?
SiberianGuy

コントローラーの最初の要素をフェッチし、モデルを@model Model.Userに設定する必要があります
Martin Fabik

しかし、私は自分のページにIListとレイアウトを取得させたい-最初の要素のみ
SiberianGuy

私があなたを正しく理解している場合、モデルをIList <SomeThing>にして、ビューでコレクションの最初の要素を取得しますか?使用もしそうなら、@ Model.First()
マーティンFabik

6
ポスターは、レイアウトを使用するメインビューではなく、_Layout.cshtmlページにモデルを渡す方法について尋ねていました。
Pure.Krome、2011年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.