ASP.NET MVC Razor pass model ไปยังเค้าโครง


98

สิ่งที่ฉันเห็นคือคุณสมบัติเค้าโครงสตริง แต่ฉันจะส่งแบบจำลองไปจัดวางอย่างชัดเจนได้อย่างไร


ฉันมีหลายหน้าในแบบจำลองที่แตกต่างกัน แต่เค้าโครงเดียวกัน
SiberianGuy

2
คำถาม stackoverflow นี้ดูเหมือนจะตอบสิ่งที่คุณกำลังถาม: stackoverflow.com/questions/13225315/…
Paul

ฉันไม่มีปัญหานี้ Modelมีให้ใน_Layout. ฉันใช้ MVC5
Toddmo

คำตอบ:


66

ดูเหมือนว่าคุณได้สร้างโมเดลมุมมองของคุณผิดไปเล็กน้อยหากคุณมีปัญหานี้

โดยส่วนตัวแล้วฉันจะไม่พิมพ์หน้าเค้าโครง แต่ถ้าคุณต้องการทำเช่นนั้นคุณควรมีโมเดลมุมมองพื้นฐานที่โมเดลมุมมองอื่น ๆ ของคุณสืบทอดมาจากนั้นพิมพ์เลย์เอาต์ของคุณไปยังโมเดลมุมมองพื้นฐานและคุณจะกำหนดเพจไปยังเฉพาะครั้งเดียว


11
"โดยส่วนตัวแล้วฉันจะไม่พิมพ์หน้าเค้าโครง" ทำไม? ฉันหมายถึงคุณจัดการเนื้อหาไดนามิกด้านข้างที่ปรากฏในทุกหน้าได้อย่างไร คุณข้ามตัวควบคุมจากมุมมองหรือไม่? / บางทีคุณอาจหมายถึงใช้ RenderAction จากเค้าโครง? (ฉันเพิ่งดูตอนนี้)
eglasius

53
@eglasius วิธีแก้ปัญหาที่ฉันใช้แตกต่างกันไปขึ้นอยู่กับประเภทของเนื้อหาที่เราพูดถึง แต่วิธีแก้ปัญหาทั่วไปคือการใช้ RenderAction เพื่อแสดงผลส่วนที่ต้องการข้อมูลของตัวเองในหน้าเค้าโครง เหตุผลที่ฉันไม่ชอบพิมพ์หน้าเค้าโครงก็คือมันจะบังคับให้คุณสืบทอดโมเดลมุมมอง "ฐาน" ในโมเดลมุมมองเฉพาะทั้งหมดของคุณ จากประสบการณ์ของฉันสิ่งนี้มักจะไม่ใช่ความคิดที่ดีนักและบ่อยครั้งที่คุณจะมีปัญหาเมื่อต้องเปลี่ยนการออกแบบ (หรืออาจใช้เวลานาน)
Mattias Jakobsson

2
จะเป็นอย่างไรหากฉันต้องการรวมโมเดลพื้นฐานโดยการรวมไม่ใช่โดยการสืบทอด วิธีที่ถูกต้องตามกฎหมายอย่างสมบูรณ์แบบจากมุมมองการออกแบบ ฉันจะจัดการเลย์เอาต์ได้อย่างไร?
Fyodor Soikin

4
ฉันมีโซลูชัน 2 แบบ: โมเดลทั่วไปสำหรับเลย์เอาต์ดังนั้นฉันจึงสามารถใช้ MyLayoutModel <MyViewModel> สำหรับโมเดลมุมมองโดยใช้ RenderPartial กับ MyViewModel ในเลย์เอาต์เท่านั้น หรือแสดงผลบางส่วนของเพจโดยใช้ RenderAction สำหรับส่วนที่แคชแบบคงที่และ ajax เรียกใช้ส่วนไดนามิก แต่ฉันชอบโซลูชันแรกเพราะมันเป็นมิตรกับเครื่องมือค้นหามากกว่าและรวมเข้ากับการอัปเดต ajax ได้อย่างง่ายดาย
Softlion

4
กำลังทำงานกับรหัสเดิมที่ได้ทำสิ่งนี้แล้ว มันคือฝันร้าย อย่าพิมพ์เค้าโครงของคุณ ... โปรด!

81
  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. ใส่บนMyActionFilterMyController

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 หรือไม่ถ้าใช่แล้วอย่างไรและไม่ได้โปรดบอกฉันว่าทำไมถึงใช้ไม่ได้ ขอบคุณอีกครั้ง.
Zaker

2
@ ผู้ใช้ - TempData ใช้ Session และรู้สึกแย่กับฉันเสมอ ความเข้าใจของฉันคือ 'อ่านครั้งเดียว' ดังนั้นทันทีที่คุณอ่านมันจะลบมันออกจากเซสชัน (หรืออาจจะทันทีที่คำขอสิ้นสุดลง) เป็นไปได้ว่าคุณจัดเก็บเซสชันไว้ใน Sql Server (หรือ Dynamo Db) ดังนั้นให้พิจารณาความจริงที่ว่าคุณต้องต่ออนุกรม MasterLayoutViewModel ... ไม่ใช่สิ่งที่คุณต้องการ โดยพื้นฐานแล้วการตั้งค่าเป็น ViewData จะเก็บไว้ในหน่วยความจำในพจนานุกรมที่ยืดหยุ่นเล็กน้อยซึ่งเหมาะกับการเรียกเก็บเงิน
BlackjacketMack

ง่ายพอฉันใช้วิธีแก้ปัญหาของคุณ แต่ฉันยังใหม่กับ 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 เป็นข้อผิดพลาดข้อมูลผู้ใช้บางส่วนสามารถส่งผ่านได้และข้อมูลสามารถถูกเติมในห่วงโซ่การสืบทอดตัวควบคุมดังนั้นจึงจำเป็นต้องใช้เพียงครั้งเดียว

เห็นได้ชัดว่าเพื่อวัตถุประสงค์ขั้นสูงคุณควรพิจารณาสร้างคอนแทกซ์แบบคงที่ที่กำหนดเองโดยใช้การแทรกและรวมเนมสเปซของโมเดลนั้นไว้ใน _Layout.cshtml

แต่สำหรับผู้ใช้ขั้นพื้นฐานสิ่งนี้จะทำตามเคล็ดลับ


ฉันเห็นด้วยกับคุณ. ขอบคุณ.
Sebastián Guerrero

1
ขึ้นและแค่อยากจะบอกว่ามันยังใช้งานได้กับอินเทอร์เฟซแทนคลาสพื้นฐาน
VladL

29

วิธีแก้ปัญหาทั่วไปคือการสร้างโมเดลมุมมองฐานซึ่งมีคุณสมบัติที่ใช้ในไฟล์โครงร่างแล้วสืบทอดจากโมเดลพื้นฐานไปยังโมเดลที่ใช้บนเพจตามลำดับ

ปัญหาของแนวทางนี้คือตอนนี้คุณได้ขังตัวเองไว้ในปัญหาของโมเดลที่สามารถสืบทอดจากคลาสอื่นได้เท่านั้นและบางทีวิธีแก้ปัญหาของคุณอาจทำให้คุณไม่สามารถใช้การสืบทอดกับโมเดลที่คุณตั้งใจไว้ได้

โซลูชันของฉันเริ่มต้นด้วยโมเดลมุมมองฐาน:

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; }
}

ด้วยโซลูชันนี้ฉันได้ยกเลิกความจำเป็นในการสืบทอดระหว่างโมเดลโครงร่างและโมเดล

ตอนนี้ฉันสามารถใช้ LayoutModel ใน Layout.cshtml ได้ดังนี้:

@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
โบนัสสำหรับชี้ให้เห็นปัญหาการถ่ายทอดทางพันธุกรรมและวิธีจัดการกับมัน! นี่คือคำตอบที่ดีกว่าสำหรับความสามารถในการปรับขนาด
Brett Spencer

1
ทางออกที่ดีที่สุดในความคิดของฉัน จากมุมมองทางสถาปัตยกรรมสามารถปรับขนาดและบำรุงรักษาได้ นี่เป็นวิธีที่ถูกต้องในการทำเช่นนี้ ฉันไม่เคยชอบ ViewBag หรือ ViewData ..... ทั้งคู่ดูเหมือนแฮ็คสำหรับฉัน
Jonathan Alfaro

11

ทำไมคุณไม่เพิ่มมุมมองบางส่วนใหม่ด้วยคอนโทรลเลอร์เฉพาะของฉันเองโดยส่งโมเดลที่ต้องการไปยังมุมมองบางส่วนและในที่สุดก็แสดงมุมมองบางส่วนที่กล่าวถึงบน Layout.cshtml ของคุณโดยใช้ RenderPartial หรือ RenderAction

ฉันใช้วิธีนี้เพื่อแสดงข้อมูลของผู้ใช้ที่ล็อกอินเช่นชื่อรูปโปรไฟล์และอื่น ๆ


3
ช่วยอธิบายให้ละเอียดหน่อยได้ไหม ฉันขอขอบคุณลิงก์ไปยังบล็อกโพสต์บางส่วนที่ใช้เทคนิคนี้
J86

สิ่งนี้สามารถทำงานได้ แต่ทำไมต้องใช้ประสิทธิภาพ? คุณต้องรอการประมวลผลทั้งหมดที่ทำโดยคอนโทรลเลอร์ส่งคืนมุมมองเพียงเพื่อให้เบราว์เซอร์ของผู้ใช้ทำการร้องขออีกครั้งเพื่อรับข้อมูลที่จำเป็น และจะเกิดอะไรขึ้นถ้า Layout ของคุณขึ้นอยู่กับข้อมูลที่จะแสดงผลอย่างเหมาะสม IMHO นี่ไม่ใช่คำตอบสำหรับคำถามนี้
Brett Spencer

4

คำถามเก่า แต่พูดถึงวิธีแก้ปัญหาสำหรับนักพัฒนา MVC5 คุณสามารถใช้Modelคุณสมบัติเดียวกับในมุมมอง

Modelคุณสมบัติทั้งในมุมมองและรูปแบบเป็น assosiated ด้วยเหมือนกันViewDataDictionaryวัตถุดังนั้นคุณจึงไม่ต้องทำงานพิเศษใด ๆ ที่จะผ่านรูปแบบของคุณไปยังหน้ารูปแบบและคุณไม่ได้มีการประกาศ@model MyModelNameในรูปแบบ

แต่แจ้งให้ทราบว่าเมื่อคุณใช้@Model.XXXในรูปแบบเมนูบริบท IntelliSense จะไม่ปรากฏเพราะที่นี่เป็นวัตถุแบบไดนามิกเช่นเดียวกับModelViewBag


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 ที่มีนิยามคลาสสำหรับออบเจ็กต์ของคุณ ตัวอย่างเช่น:

@ ใช้ YourApplication.YourClasses;

3) เมื่อคุณอ้างอิง yourObject ใน _Layout ส่งมัน คุณสามารถใช้นักแสดงได้เนื่องจากสิ่งที่คุณทำใน (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
ไม่แน่ใจว่าทำไมคุณถึงถูกโหวตไม่ลง การใช้อินเทอร์เฟซคล้ายกับสิ่งที่คุณทำที่นี่ทำงานในบริบทของฉัน
costa

-6

ตัวอย่างเช่น

@model IList<Model.User>

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

อ่านเพิ่มเติมเกี่ยวกับคำสั่ง@modelใหม่


แต่ถ้าฉันต้องการส่งผ่านองค์ประกอบแรกของคอลเลกชันไปยังโมเดลเค้าโครง?
SiberianGuy

คุณต้องดึงองค์ประกอบแรกในคอนโทรลเลอร์ของคุณและตั้งค่าโมเดลเป็น @model Model ผู้ใช้
Martin Fabik

แต่ฉันต้องการให้หน้าของฉันได้รับ IList และ Layout - เฉพาะองค์ประกอบแรก
SiberianGuy

ถ้าฉันเข้าใจคุณถูกต้องคุณต้องการให้โมเดลเป็น IList <SomeThing> และในมุมมองรับองค์ประกอบแรกของคอลเลกชันหรือไม่ ถ้าเป็นเช่นนั้นให้ใช้ @ Model.First ()
Martin Fabik

6
ผู้โพสต์ถามเกี่ยวกับวิธีส่งโมเดลไปยังหน้า _Layout.cshtml .. ไม่ใช่มุมมองหลักที่ใช้เค้าโครง
Pure.Krome
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.