มุมมองบางส่วน ASP.NET MVC: คำนำหน้าชื่ออินพุต


120

สมมติว่าฉันมี ViewModel เช่น

public class AnotherViewModel
{
   public string Name { get; set; }
}
public class MyViewModel
{
   public string Name { get; set; }
   public AnotherViewModel Child { get; set; }
   public AnotherViewModel Child2 { get; set; }
}

ในมุมมองฉันสามารถแสดงผลบางส่วนด้วย

<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>

ในบางส่วนฉันจะทำ

<%= Html.TextBox("Name", Model.Name) %>
or
<%= Html.TextBoxFor(x => x.Name) %>

อย่างไรก็ตามปัญหาคือทั้งสองจะแสดง name = "Name" ในขณะที่ฉันต้องมี name = "Child.Name" เพื่อให้ตัวยึดแบบจำลองทำงานได้อย่างถูกต้อง หรือ name = "Child2.Name" เมื่อฉันแสดงผลคุณสมบัติที่สองโดยใช้มุมมองบางส่วนเดียวกัน

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

<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>

จะเพิ่ม "เด็ก" ที่ถูกต้องโดยอัตโนมัติ คำนำหน้าของสตริงชื่อ / id ที่สร้างขึ้น?

ฉันสามารถยอมรับวิธีแก้ปัญหาใด ๆ รวมถึงเอนจิ้นและไลบรารีมุมมองบุคคลที่สาม - ฉันใช้ Spark View Engine (ฉัน "แก้ปัญหา" โดยใช้มาโคร) และ MvcContrib แต่ไม่พบวิธีแก้ไขที่นั่น XForms, InputBuilder, MVC v2 - เครื่องมือ / ข้อมูลเชิงลึกใด ๆ ที่ให้ฟังก์ชันนี้จะดีมาก

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

อาจมีวิธีแก้ปัญหาด้วยตนเองจำนวนมากและยินดีต้อนรับทุกคน ตัวอย่างเช่นฉันสามารถบังคับให้บางส่วนของฉันอิงจาก IPartialViewModel <T> {คำนำหน้าสตริงสาธารณะ; T รุ่น; } แต่ฉันต้องการโซลูชันที่มีอยู่ / ได้รับการอนุมัติมากกว่า

UPDATE: มีคำถามที่คล้ายกันกับคำตอบไม่มีที่นี่

คำตอบ:


110

คุณสามารถขยายคลาสตัวช่วย Html ได้โดย:

using System.Web.Mvc.Html


 public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, string partialViewName)
    {
        string name = ExpressionHelper.GetExpressionText(expression);
        object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
        var viewData = new ViewDataDictionary(helper.ViewData)
        {
            TemplateInfo = new System.Web.Mvc.TemplateInfo
            {
                HtmlFieldPrefix = name
            }
        };

        return helper.Partial(partialViewName, model, viewData);

    }

และใช้มันในมุมมองของคุณเช่นนี้:

<%= Html.PartialFor(model => model.Child, "_AnotherViewModelControl") %>

และคุณจะเห็นทุกอย่างเรียบร้อย!


17
สิ่งนี้จะไม่ถูกต้องสำหรับการแสดงผลบางส่วนที่ซ้อนกัน คุณต้องต่อท้ายคำนำหน้าใหม่กับคำนำหน้าเก่าจากhelper.ViewData.TemplateInfo.HtmlFieldPrefixในรูปแบบของ{oldprefix}.{newprefix}
Ivan Zlatev

@Mahmoud รหัสของคุณใช้งานได้ดี แต่ฉันพบว่า ViewData / ViewBag ว่างเปล่าเมื่อถึงเวลาเรียกใช้โค้ดในบางส่วน ฉันพบว่าตัวช่วยประเภท HtmlHelper <TModel> มีคุณสมบัติใหม่ ViewData ซึ่งซ่อนโมเดลพื้นฐาน กับที่ผมถูกแทนที่ด้วยnew ViewDataDictionary(helper.ViewData) new ViewDataDictionary(((HtmlHelper)helper).ViewData)คุณเห็นปัญหาหรือไม่?
Pat Newell

@IvanZlatev ขอบคุณ ฉันจะแก้ไขโพสต์หลังจากทดสอบแล้ว
Mahmoud Moravej

2
@IvanZlatev ถูกต้อง ก่อนตั้งชื่อคุณควรทำstring oldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix; if (oldPrefix != "") name = oldPrefix + "." + name;
kennethc

2
การแก้ไขที่ง่ายสำหรับเทมเพลตที่ซ้อนกันคือการใช้ HtmlFieldPrefix = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName (ชื่อ)
Aman Mahajan

95

จนถึงตอนนี้ฉันกำลังค้นหาสิ่งเดียวกันที่ฉันพบโพสต์ล่าสุดนี้:

http://davybrion.com/blog/2011/01/prefixing-input-elements-of-partial-views-with-asp-net-mvc/

<% Html.RenderPartial("AnotherViewModelControl", Model.Child, new ViewDataDictionary
{
    TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Child1" }
})
%>

3
ขอบคุณสำหรับลิงค์นี่เป็นตัวเลือกที่ดีที่สุดที่ระบุไว้ที่นี่
BZ

32
สายสุด ๆ สำหรับงานปาร์ตี้นี้ แต่ถ้าคุณทำตามแนวทางนี้คุณควรใช้ตัวViewDataDictionaryสร้างที่รับกระแสไม่ViewDataเช่นนั้นคุณจะสูญเสียข้อผิดพลาดสถานะโมเดลข้อมูลการตรวจสอบความถูกต้อง ฯลฯ
bhamlin

9
สร้างตามความคิดเห็นของ bhamlin คุณยังสามารถส่งผ่านคำนำหน้าแบบซ้อนเช่น (sry this is vb.net ex): Html.RenderPartial ("AnotherViewModelControl", Model.PropX, New ViewDataDictionary (ViewData) ด้วย {.TemplateInfo = New TemplateInfo () ด้วย {.HtmlFieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix & ".PropX"}})
hubson bropa

1
คำตอบที่ดี +1 บางทีมันอาจจะคุ้มค่าที่จะแก้ไขเพื่อนำความคิดเห็นของ @bhamlin มาพิจารณา
ken2k

1
โปรดทราบว่าหากคุณต้องการส่งคืนบางส่วนจากคอนโทรลเลอร์คุณจะต้องตั้งค่าคำนำหน้านี้ด้วย stackoverflow.com/questions/6617768/…
The Muffin Man

12

คำตอบของฉันอ้างอิงจากคำตอบของ Mahmoud Moravej รวมถึงความคิดเห็นของ Ivan Zlatev

    public static MvcHtmlString PartialFor<TModel, TProperty>(this HtmlHelper<TModel> helper, System.Linq.Expressions.Expression<Func<TModel, TProperty>> expression, string partialViewName)
    {
            string name = ExpressionHelper.GetExpressionText(expression);
            object model = ModelMetadata.FromLambdaExpression(expression, helper.ViewData).Model;
            StringBuilder htmlFieldPrefix = new StringBuilder();
            if (helper.ViewData.TemplateInfo.HtmlFieldPrefix != "")
            {
                htmlFieldPrefix.Append(helper.ViewData.TemplateInfo.HtmlFieldPrefix);
                htmlFieldPrefix.Append(name == "" ? "" : "." + name);
            }
            else
                htmlFieldPrefix.Append(name);

            var viewData = new ViewDataDictionary(helper.ViewData)
            {
                TemplateInfo = new System.Web.Mvc.TemplateInfo
                {
                    HtmlFieldPrefix = htmlFieldPrefix.ToString()
                }
            };

        return helper.Partial(partialViewName, model, viewData);
    }

แก้ไข: คำตอบของ Mohamoud ไม่ถูกต้องสำหรับการแสดงผลบางส่วนที่ซ้อนกัน คุณต้องต่อท้ายคำนำหน้าใหม่กับคำนำหน้าเก่าเฉพาะในกรณีที่จำเป็น สิ่งนี้ไม่ชัดเจนในคำตอบล่าสุด (:


6
จะเป็นประโยชน์กับผู้อื่นหากคุณอธิบายอย่างละเอียดว่าข้างต้นปรับปรุงคำตอบที่มีอยู่อย่างไร
Leigh

2
หลังจากเกิดข้อผิดพลาดในการคัดลอกและวางแบบใช้นิ้วชี้ข้อผิดพลาดนี้ทำงานได้อย่างสมบูรณ์แบบสำหรับ ASP.NET MVC 5 +1
Greg Burghardt

9

การใช้ MVC2 คุณสามารถทำได้

นี่คือมุมมองที่พิมพ์ผิด:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<MvcLearner.Models.Person>" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Create
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

    <h2>Create</h2>

    <% using (Html.BeginForm()) { %>
        <%= Html.LabelFor(person => person.Name) %><br />
        <%= Html.EditorFor(person => person.Name) %><br />
        <%= Html.LabelFor(person => person.Age) %><br />
        <%= Html.EditorFor(person => person.Age) %><br />
        <% foreach (String FavoriteFoods in Model.FavoriteFoods) { %>
            <%= Html.LabelFor(food => FavoriteFoods) %><br />
            <%= Html.EditorFor(food => FavoriteFoods)%><br />
        <% } %>
        <%= Html.EditorFor(person => person.Birthday, "TwoPart") %>
        <input type="submit" value="Submit" />
    <% } %>

</asp:Content>

นี่คือมุมมองที่พิมพ์อย่างรุนแรงสำหรับคลาสลูก (ซึ่งต้องเก็บไว้ในโฟลเดอร์ย่อยของไดเร็กทอรีมุมมองที่เรียกว่า EditorTemplates):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MvcLearner.Models.TwoPart>" %>

<%= Html.LabelFor(birthday => birthday.Day) %><br />
<%= Html.EditorFor(birthday => birthday.Day) %><br />

<%= Html.LabelFor(birthday => birthday.Month) %><br />
<%= Html.EditorFor(birthday => birthday.Month) %><br />

นี่คือตัวควบคุม:

public class PersonController : Controller
{
    //
    // GET: /Person/
    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Index()
    {
        return View();
    }

    [AcceptVerbs(HttpVerbs.Get)]
    public ActionResult Create()
    {
        Person person = new Person();
        person.FavoriteFoods.Add("Sushi");
        return View(person);
    }

    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Create(Person person)
    {
        return View(person);
    }
}

นี่คือคลาสที่กำหนดเอง:

public class Person
{
    public String Name { get; set; }
    public Int32 Age { get; set; }
    public List<String> FavoriteFoods { get; set; }
    public TwoPart Birthday { get; set; }

    public Person()
    {
        this.FavoriteFoods = new List<String>();
        this.Birthday = new TwoPart();
    }
}

public class TwoPart
{
    public Int32 Day { get; set; }
    public Int32 Month { get; set; }
}

และแหล่งที่มาของผลลัพธ์:

<form action="/Person/Create" method="post"><label for="Name">Name</label><br /> 
    <input class="text-box single-line" id="Name" name="Name" type="text" value="" /><br /> 
    <label for="Age">Age</label><br /> 
    <input class="text-box single-line" id="Age" name="Age" type="text" value="0" /><br /> 
    <label for="FavoriteFoods">FavoriteFoods</label><br /> 
    <input class="text-box single-line" id="FavoriteFoods" name="FavoriteFoods" type="text" value="Sushi" /><br /> 
    <label for="Birthday_Day">Day</label><br /> 
    <input class="text-box single-line" id="Birthday_Day" name="Birthday.Day" type="text" value="0" /><br /> 

    <label for="Birthday_Month">Month</label><br /> 
    <input class="text-box single-line" id="Birthday_Month" name="Birthday.Month" type="text" value="0" /><br /> 
    <input type="submit" value="Submit" /> 
</form>

ตอนนี้เสร็จสมบูรณ์แล้ว ตั้งค่าเบรกพอยต์ในการดำเนินการสร้างตัวควบคุมโพสต์เพื่อตรวจสอบ อย่าใช้สิ่งนี้กับรายการอย่างไรก็ตามมันจะไม่ได้ผล ดูคำถามของฉันเกี่ยวกับการใช้ EditorTemplates กับ IEnumerable สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนั้น


ใช่ดูเหมือนมัน ปัญหาคือรายการไม่ทำงาน (ในขณะที่แบบจำลองมุมมองที่ซ้อนกันมักจะอยู่ในรายการ) และเป็น v2 ... ซึ่งฉันยังไม่พร้อมที่จะใช้ในการผลิต แต่ก็ยังดีที่รู้ว่ามันจะเป็นสิ่งที่ฉันต้องการ ... เมื่อมันมาถึง (+1)
queen3

เมื่อวันก่อนฉันก็เจอเช่นกันmatthidinger.com/archive/2009/08/15/…คุณอาจต้องการดูว่าคุณสามารถหาวิธีขยายสำหรับบรรณาธิการได้หรือไม่ (แม้ว่าจะยังอยู่ใน MVC2) ฉันใช้เวลาสองสามนาทีกับมัน แต่ก็ยังคงพบปัญหาเพราะฉันยังไม่ถึงกับสำนวน บางทีคุณอาจทำได้ดีกว่าฉัน
Nick Larsen

1
ลิงค์ที่มีประโยชน์ขอบคุณ ไม่ใช่ว่าฉันคิดว่าเป็นไปได้ที่จะทำให้ EditorFor ทำงานที่นี่เพราะมันจะไม่สร้างดัชนี [0] ที่ฉันคิดไว้ (ฉันพนันได้เลยว่ามันยังไม่รองรับเลย) ทางออกหนึ่งคือการแสดงผล EditorFor () เป็นสตริงและปรับแต่งผลลัพธ์ด้วยตนเอง (ผนวกคำนำหน้าที่จำเป็น) แม้ว่าแฮ็คสกปรก หืมมม! ฉันอาจใช้วิธีการขยายสำหรับ Html.Helper () usePrefix () ซึ่งจะแทนที่ name = "x" ด้วย name = "prefix.x" ... ใน MVC v1 ยังทำงานได้บ้าง แต่ไม่มาก และ Html WithPrefix ("คำนำหน้า") RenderPartial () ที่ทำงานเป็นคู่
ควีน 3

+1 สำหรับแนะนำHtml.EditorForวิธีการเรนเดอร์ฟอร์มลูก นี่คือสิ่งที่ควรใช้เทมเพลตตัวแก้ไข นี่น่าจะเป็นคำตอบ
Greg Burghardt

9

นี่เป็นคำถามที่เก่า แต่สำหรับทุกคนที่เดินทางมาถึงที่นี่มองหาวิธีการแก้ปัญหาให้พิจารณาใช้EditorForตามข้อเสนอแนะในความคิดเห็นในhttps://stackoverflow.com/a/29809907/456456 หากต้องการย้ายจากมุมมองบางส่วนไปยังเทมเพลตตัวแก้ไขให้ทำตามขั้นตอนเหล่านี้

  1. ตรวจสอบว่ามีมุมมองบางส่วนของคุณถูกผูกไว้กับcomplexType

  2. เลื่อนมุมมองบางส่วนของคุณไปยังโฟลเดอร์ย่อยEditorTemplatesของโฟลเดอร์มุมมองปัจจุบันหรือไปยังโฟลเดอร์ที่ใช้ร่วมกัน ตอนนี้เป็นเทมเพลตตัวแก้ไข

  3. เปลี่ยน@Html.Partial("_PartialViewName", Model.ComplexType)เป็น@Html.EditorFor(m => m.ComplexType, "_EditorTemplateName"). เทมเพลตตัวแก้ไขเป็นทางเลือกหากเป็นเทมเพลตเดียวสำหรับประเภทที่ซับซ้อน

html ComplexType.Fieldnameองค์ประกอบการป้อนข้อมูลจะได้รับการตั้งชื่อโดยอัตโนมัติ


8

PartailForสำหรับ asp.net Core 2 เผื่อมีคนต้องการ

    public static ModelExplorer GetModelExplorer<TModel, TResult>(this IHtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TResult>> expression)
    {
        if (expression == null)
            throw new ArgumentNullException(nameof(expression));
        return ExpressionMetadataProvider.FromLambdaExpression(expression, htmlHelper.ViewData, htmlHelper.MetadataProvider);
    }

    public static IHtmlContent PartialFor<TModel, TResult>(this IHtmlHelper<TModel> helper, Expression<Func<TModel, TResult>> expression, string partialViewName, string prefix = "")
    {
        var modelExplorer = helper.GetModelExplorer(expression);
        var viewData = new ViewDataDictionary(helper.ViewData);
        viewData.TemplateInfo.HtmlFieldPrefix += prefix;
        return helper.Partial(partialViewName, modelExplorer.Model, viewData);
    }

3

ฉันเจอปัญหานี้เช่นกันและหลังจากเจ็บปวดมากฉันพบว่ามันง่ายกว่าที่จะออกแบบอินเทอร์เฟซของฉันใหม่โดยที่ฉันไม่จำเป็นต้องโพสต์วัตถุโมเดลที่ซ้อนกันกลับ สิ่งนี้บังคับให้ฉันต้องเปลี่ยนเวิร์กโฟลว์อินเทอร์เฟซของฉัน: แน่ใจว่าตอนนี้ฉันต้องการให้ผู้ใช้ทำในสองขั้นตอนสิ่งที่ฉันใฝ่ฝันที่จะทำในขั้นตอนเดียว แต่ความสามารถในการใช้งานและการบำรุงรักษาโค้ดของแนวทางใหม่นั้นมีค่าสำหรับฉันในตอนนี้

หวังว่านี่จะช่วยได้บ้าง


1

คุณสามารถเพิ่มตัวช่วยสำหรับ RenderPartial ซึ่งใช้คำนำหน้าและปรากฏใน ViewData

    public static void RenderPartial(this HtmlHelper helper,string partialViewName, object model, string prefix)
    {
        helper.ViewData["__prefix"] = prefix;
        helper.RenderPartial(partialViewName, model);
    }

จากนั้นผู้ช่วยเพิ่มเติมซึ่งเชื่อมต่อค่า ViewData

    public static void GetName(this HtmlHelper helper, string name)
    {
        return string.Concat(helper.ViewData["__prefix"], name);
    }

และในมุมมอง ...

<% Html.RenderPartial("AnotherViewModelControl", Model.Child, "Child.") %>

ในบางส่วน ...

<%= Html.TextBox(Html.GetName("Name"), Model.Name) %>

ใช่ว่าเป็นสิ่งที่ผมอธิบายไว้ในความคิดเห็นที่stackoverflow.com/questions/1488890/... ทางออกที่ดีกว่านั้นคือ RenderPartial ซึ่งใช้ lambda ด้วยซึ่งง่ายต่อการใช้งาน ยังคงขอบคุณสำหรับรหัสฉันเดาว่านั่นเป็นวิธีที่ไม่เจ็บปวดและไร้กาลเวลาที่สุด
queen3

1
ทำไมไม่ใช้ helper.ViewData.TemplateInfo.HtmlFieldPrefix = prefix? ฉันใช้สิ่งนั้นในตัวช่วยของฉันและดูเหมือนว่าจะทำงานได้ดี
ajbeaven

0

เช่นเดียวกับคุณฉันเพิ่มคุณสมบัติคำนำหน้า (สตริง) ให้กับ ViewModels ของฉันซึ่งฉันต่อท้ายชื่ออินพุตที่ผูกไว้กับโมเดลของฉัน (YAGNI ป้องกันด้านล่าง)

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

หวังว่าจะช่วย

แดน


1
อืมแย่เหมือนกัน ฉันสามารถจินตนาการถึงโซลูชันที่สวยงามมากมายโดยมี HtmlHelpers ที่กำหนดเองในการตรวจสอบคุณสมบัติแอตทริบิวต์การเรนเดอร์ที่กำหนดเอง ฯลฯ ... แต่ตัวอย่างเช่นถ้าฉันใช้ MvcContrib FluentHtml ฉันจะเขียนใหม่ทั้งหมดเพื่อรองรับการแฮ็กของฉันหรือไม่ เป็นเรื่องแปลกที่ไม่มีใครพูดถึงเรื่องนี้ราวกับว่าทุกคนใช้ ViewModels ระดับเดียวแบบแบน ...
queen3

หลังจากใช้เฟรมเวิร์กเป็นระยะเวลานานและพยายามหาโค้ดมุมมองที่สะอาดตาฉันคิดว่า ViewModel แบบแบ่งชั้นเป็นสิ่งที่หลีกเลี่ยงไม่ได้ ตะกร้า ViewModel ภายใน Order ViewModel ตัวอย่างเช่น
Daniel Elliott

0

ก่อนที่คุณจะเรียก RenderPartial ที่คุณทำ

<% ViewData["Prefix"] = "Child."; %>
<% Html.RenderPartial("AnotherViewModelControl", Model.Child) %>

จากนั้นในบางส่วนของคุณคุณมี

<%= Html.TextBox(ViewData["Prefix"] + "Name", Model.Name) %>

1
โดยพื้นฐานแล้วจะเหมือนกับการส่งผ่านด้วยตนเอง (เป็นประเภทที่ปลอดภัยในการรับโมเดลมุมมองทั้งหมดจาก IViewModel ด้วย "IViewModel SetPrefix (สตริง)" แทน) และน่าเกลียดมากเว้นแต่ฉันจะเขียนตัวช่วย Html และ RenderPartial ทั้งหมดใหม่เพื่อให้จัดการโดยอัตโนมัติ นี้. ปัญหาไม่ได้อยู่ที่วิธีการแก้ไขปัญหานี้แล้ว ปัญหาคือสามารถทำได้โดยอัตโนมัติ
queen3

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