Tampilan parsial ASP.NET MVC: memasukkan awalan nama

120

Misalkan saya memiliki ViewModel seperti

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

Dalam tampilan saya bisa membuat sebagian dengan

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

Secara parsial saya akan melakukannya

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

Namun, masalahnya adalah keduanya akan membuat name = "Name" sementara saya harus memiliki name = "Child.Name" agar pengikat model berfungsi dengan benar. Atau, name = "Child2.Name" saat saya merender properti kedua menggunakan tampilan parsial yang sama.

Bagaimana cara membuat tampilan parsial saya secara otomatis mengenali awalan yang diperlukan? Saya bisa melewatkannya sebagai parameter tetapi ini terlalu merepotkan. Ini bahkan lebih buruk ketika saya ingin misalnya merendernya secara rekursif. Apakah ada cara untuk merender tampilan parsial dengan prefiks, atau, bahkan lebih baik, dengan pengubahan otomatis dari ekspresi lambda yang memanggil sehingga

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

secara otomatis akan menambahkan "Anak" yang benar. awalan ke string nama / id yang dihasilkan?

Saya dapat menerima solusi apa pun, termasuk mesin dan pustaka tampilan pihak ke-3 - Saya benar-benar menggunakan Mesin Tampilan Spark (saya "memecahkan" masalah menggunakan makro-nya) dan MvcContrib, tetapi tidak menemukan solusi di sana. XForms, InputBuilder, MVC v2 - alat / wawasan apa pun yang menyediakan fungsionalitas ini akan sangat bagus.

Saat ini saya berpikir tentang pengkodean ini sendiri tetapi sepertinya buang-buang waktu, saya tidak percaya hal-hal sepele ini belum diterapkan.

Mungkin ada banyak solusi manual, dan semuanya diterima. Misalnya, saya bisa memaksa parsial saya untuk didasarkan pada IP PartialViewModel <T> {public string Prefix; Model T; }. Tetapi saya lebih suka beberapa solusi yang ada / disetujui.

UPDATE: ada pertanyaan serupa tanpa jawaban di sini .

ratu3
sumber

Jawaban:

110

Anda dapat memperluas kelas helper Html dengan ini:

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

    }

dan gunakan saja dalam tampilan Anda seperti ini:

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

dan Anda akan melihat semuanya baik-baik saja!

Mahmoud Moravej
sumber
17
Ini akan salah untuk rendering parsial bersarang. Anda perlu menambahkan awalan baru ke awalan lama dari helper.ViewData.TemplateInfo.HtmlFieldPrefixdalam bentuk{oldprefix}.{newprefix}
Ivan Zlatev
@Mahmoud Kode Anda berfungsi dengan baik, tetapi saya menemukan bahwa ViewData / ViewBag kosong ketika tiba saatnya untuk mengeksekusi kode di Partial. Saya menemukan bahwa helper, tipe HtmlHelper <TModel> memiliki properti baru ViewData, yang menyembunyikan model dasar. Dengan itu, saya ganti new ViewDataDictionary(helper.ViewData)dengan new ViewDataDictionary(((HtmlHelper)helper).ViewData). Apakah Anda melihat ada masalah dengan itu?
Pat Newell
@Ivanlate Terima kasih. Saya akan memperbaiki posting setelah mengujinya.
Mahmoud Moravej
2
@IvanZlatev benar. Sebelum Anda menetapkan nama, Anda harus melakukannyastring oldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix; if (oldPrefix != "") name = oldPrefix + "." + name;
kennethc
2
Perbaikan mudah untuk templat bersarang adalah dengan menggunakan HtmlFieldPrefix = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName (nama)
Aman Mahajan
95

Sejauh ini, saya sedang mencari hal yang sama dengan yang saya temukan di posting terbaru ini:

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

<% Html.RenderPartial("AnotherViewModelControl", Model.Child, new ViewDataDictionary
{
    TemplateInfo = new System.Web.Mvc.TemplateInfo { HtmlFieldPrefix = "Child1" }
})
%>
Jokin
sumber
3
Terima kasih untuk tautannya, sejauh ini opsi terbaik yang tercantum di sini
BZ
32
Sangat terlambat untuk pesta ini, tetapi jika Anda melakukan pendekatan ini, Anda harus menggunakan ViewDataDictionarykonstruktor yang mengambil arus ViewData, atau Anda akan kehilangan kesalahan status model, data validasi, dll.
bhamlin
9
membangun komentar bhamlin. Anda juga bisa meneruskan prefiks bersarang seperti (sry this is vb.net ex): Html.RenderP Partial ("AnotherViewModelControl", Model.PropX, New ViewDataDictionary (ViewData) With {.TemplateInfo = New TemplateInfo () With {.HtmlFieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix & ".PropX"}})
hubson bropa
1
Jawaban bagus, +1. Mungkin ada baiknya mengeditnya untuk mempertimbangkan komentar
@bhamlin
1
Perhatikan bahwa jika Anda perlu mengembalikan parsial ini dari pengontrol, Anda juga harus menyetel awalan ini di sana. stackoverflow.com/questions/6617768/…
The Muffin Man
12

Jawaban saya, berdasarkan jawaban Mahmoud Moravej termasuk komentar 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);
    }

Edit: Jawaban Mohamoud salah untuk rendering parsial bersarang. Anda perlu menambahkan prefiks baru ke prefiks lama, hanya jika perlu. Ini tidak jelas dalam jawaban terbaru (:

asoifer1879
sumber
6
Akan bermanfaat bagi orang lain jika Anda menguraikan bagaimana hal di atas meningkatkan jawaban yang ada.
Leigh
2
Setelah kesalahan salin dan tempel berjari gemuk, yang ini bekerja dengan sempurna untuk ASP.NET MVC 5. +1.
Greg Burghardt
9

Menggunakan MVC2 Anda dapat mencapai ini.

Berikut adalah tampilan yang diketik dengan kuat:

<%@ 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>

Berikut adalah tampilan yang sangat diketik untuk kelas anak (yang harus disimpan dalam subfolder dari direktori tampilan yang disebut 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 />

Ini pengontrolnya:

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

Berikut adalah kelas khusus:

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

Dan sumber keluaran:

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

Sekarang ini sudah selesai. Tetapkan breakpoint dalam tindakan pengontrol Buat Posting untuk memverifikasi. Namun, jangan gunakan ini dengan daftar karena tidak akan berhasil. Lihat pertanyaan saya tentang menggunakan EditorTemplates dengan IEnumerable untuk lebih lanjut tentang itu.

Nick Larsen
sumber
Ya, sepertinya itu. Masalahnya adalah daftar tidak berfungsi (sementara model tampilan bersarang biasanya ada dalam daftar) dan itu v2 ... yang saya belum cukup siap untuk digunakan dalam produksi. Tapi tetap baik untuk mengetahui itu akan menjadi sesuatu yang saya butuhkan ... ketika itu datang (jadi +1).
ratu3
Saya juga menemukan ini beberapa hari yang lalu, matthidinger.com/archive/2009/08/15/… , Anda mungkin ingin melihat apakah Anda dapat mengetahui cara memperluasnya untuk editor (meskipun masih di MVC2). Saya menghabiskan beberapa menit untuk itu, tetapi terus mengalami masalah karena saya belum bisa menyesuaikan dengan ekspresi. Mungkin Anda bisa melakukan lebih baik dari saya.
Nick Larsen
1
Tautan yang berguna, terima kasih. Bukannya saya pikir itu mungkin untuk membuat EditorFor berfungsi di sini, karena itu tidak akan menghasilkan indeks [0] saya kira (saya yakin itu belum mendukung sama sekali). Salah satu solusinya adalah membuat output EditorFor () menjadi string dan secara manual mengubah output (menambahkan prefiks yang diperlukan). Namun, peretasan kotor. Hm! Saya dapat melakukan metode ekstensi untuk Html.Helper (). UsePrefix () yang hanya akan mengganti name = "x" dengan name = "prefix.x" ... di MVC v1. Masih sedikit pekerjaan tapi tidak sebanyak itu. Dan Html.DenganPrefix ("prefix"). RenderP Partial () yang bekerja berpasangan.
ratu3
1 untuk merekomendasikan Html.EditorFormetode untuk merender bentuk anak. Untuk itulah template editor seharusnya digunakan. Ini harus menjadi jawabannya.
Greg Burghardt
9

Ini adalah pertanyaan lama, tetapi bagi siapa pun yang tiba di sini mencari solusi, pertimbangkan untuk menggunakan EditorFor, seperti yang disarankan dalam komentar di https://stackoverflow.com/a/29809907/456456 . Untuk berpindah dari tampilan parsial ke template editor, ikuti langkah-langkah berikut.

  1. Verifikasi bahwa tampilan parsial Anda terikat ke ComplexType .

  2. Pindahkan tampilan parsial Anda ke subfolder EditorTemplates dari folder tampilan saat ini, atau ke folder Bersama . Sekarang, ini adalah template editor.

  3. Ganti @Html.Partial("_PartialViewName", Model.ComplexType)ke @Html.EditorFor(m => m.ComplexType, "_EditorTemplateName"). Template editor bersifat opsional jika itu satu-satunya template untuk tipe kompleks.

Elemen Input HTML secara otomatis akan dinamai ComplexType.Fieldname.

R. Schreurs
sumber
8

PartailFor untuk asp.net Core 2 jika seseorang membutuhkannya.

    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);
    }
Rahma Samaroon
sumber
3

Saya menemukan masalah ini juga dan setelah banyak rasa sakit saya merasa lebih mudah untuk mendesain ulang antarmuka saya sehingga saya tidak perlu memposting kembali objek model bersarang. Ini memaksa saya untuk mengubah alur kerja antarmuka saya: tentu saya sekarang meminta pengguna untuk melakukan dalam dua langkah apa yang saya impikan untuk dilakukan di satu langkah, tetapi kegunaan dan pemeliharaan kode dari pendekatan baru ini lebih bernilai bagi saya sekarang.

Semoga ini bisa membantu beberapa.

Matt Kocaj
sumber
1

Anda bisa menambahkan helper untuk RenderP Partial yang mengambil awalan dan memunculkannya di ViewData.

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

Kemudian helper lebih lanjut yang menggabungkan nilai ViewData

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

dan dalam tampilan ...

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

di parsial ...

<%= Html.TextBox(Html.GetName("Name"), Model.Name) %>
Anthony Johnston
sumber
Ya, itulah yang saya jelaskan dalam komentar ke stackoverflow.com/questions/1488890/… . Solusi yang lebih baik lagi adalah RenderP Partial yang juga menggunakan lambda, yang mudah diterapkan. Namun, terima kasih untuk kodenya, saya rasa itu adalah pendekatan yang paling tidak menyakitkan dan abadi.
ratu3
1
mengapa tidak menggunakan helper.ViewData.TemplateInfo.HtmlFieldPrefix = prefix? Saya menggunakannya di pembantu saya dan tampaknya berfungsi dengan baik.
ajbeaven
0

Seperti Anda, saya menambahkan properti Prefix (string) ke ViewModels saya yang saya tambahkan sebelum nama input model terikat. (YAGNI mencegah hal di bawah)

Solusi yang lebih elegan mungkin model tampilan dasar yang memiliki properti ini dan beberapa HtmlHelpers yang memeriksa apakah model tampilan berasal dari basis ini dan jika demikian tambahkan awalan ke nama input.

Semoga membantu,

Dan

Daniel Elliott
sumber
1
Hmm, sayang sekali. Saya dapat membayangkan banyak solusi elegan, dengan HtmlHelpers khusus memeriksa properti, atribut, render khusus, dll. Tetapi misalnya jika saya menggunakan MvcContrib FluentHtml, apakah saya menulis ulang semuanya untuk mendukung peretasan saya? Aneh bahwa tidak ada yang membicarakannya, seolah-olah semua orang hanya menggunakan ViewModels datar satu tingkat ...
queen3
Memang, setelah menggunakan kerangka kerja untuk waktu yang lama dan berjuang untuk kode tampilan bersih yang bagus, saya pikir ViewModel berjenjang tidak bisa dihindari. Basket ViewModel dalam Order ViewModel misalnya.
Daniel Elliott
0

Bagaimana sebelum Anda memanggil RenderParsial yang Anda lakukan

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

Kemudian di bagian Anda, Anda punya

<%= Html.TextBox(ViewData["Prefix"] + "Name", Model.Name) %>
Anthony Johnston
sumber
1
Ini pada dasarnya sama dengan meneruskannya secara manual (tipe ini aman untuk mendapatkan semua model tampilan dari IViewModel dengan "IViewModel SetPrefix (string)" sebagai gantinya), dan sangat jelek, kecuali saya menulis ulang semua pembantu Html dan RenderP Partial sehingga mereka secara otomatis mengelola ini. Masalahnya bukanlah bagaimana melakukannya, saya sudah menyelesaikannya; Masalahnya, apakah bisa dilakukan secara otomatis.
ratu3
karena tidak ada tempat umum Anda dapat meletakkan kode yang akan mempengaruhi semua pembantu html Anda tidak akan dapat melakukan ini secara otomatis. lihat jawaban lain ...
Anthony Johnston