ASP.NET MVC-Teilansichten: Präfixe für Eingabenamen

120

Angenommen, ich habe ViewModel wie

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

In der Ansicht kann ich einen Teil mit rendern

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

Im Teil werde ich tun

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

Das Problem ist jedoch, dass beide name = "Name" rendern, während name = "Child.Name" erforderlich ist, damit der Modellordner ordnungsgemäß funktioniert. Oder name = "Child2.Name", wenn ich die zweite Eigenschaft mit derselben Teilansicht rendere.

Wie lasse ich meine Teilansicht das erforderliche Präfix automatisch erkennen? Ich kann es als Parameter übergeben, aber das ist zu unpraktisch. Dies ist noch schlimmer, wenn ich es beispielsweise rekursiv rendern möchte. Gibt es eine Möglichkeit, Teilansichten mit einem Präfix oder, noch besser, mit einer automatischen Neukonfiguration des aufrufenden Lambda-Ausdrucks zu rendern?

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

fügt automatisch das richtige "Kind" hinzu. Präfix zu den generierten Name / ID-Zeichenfolgen?

Ich kann jede Lösung akzeptieren, einschließlich View Engine und Bibliotheken von Drittanbietern. Ich verwende tatsächlich Spark View Engine (ich "löse" das Problem mithilfe seiner Makros) und MvcContrib, habe dort jedoch keine Lösung gefunden. XForms, InputBuilder, MVC v2 - jedes Tool / jede Erkenntnis, die diese Funktionalität bietet, ist großartig.

Momentan denke ich darüber nach, dies selbst zu codieren, aber es scheint Zeitverschwendung zu sein. Ich kann nicht glauben, dass dieses triviale Zeug noch nicht implementiert ist.

Möglicherweise gibt es viele manuelle Lösungen, und alle sind willkommen. Zum Beispiel kann ich erzwingen, dass meine Partials auf IPartialViewModel <T> {public string Prefix; T-Modell; }. Aber ich würde lieber eine bestehende / genehmigte Lösung bevorzugen.

UPDATE: es gibt eine ähnliche Frage ohne Antwort hier .

Königin3
quelle

Antworten:

110

Sie können die HTML-Hilfsklasse folgendermaßen erweitern:

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

    }

und verwenden Sie es einfach in Ihren Ansichten wie folgt:

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

und Sie werden sehen, dass alles in Ordnung ist!

Mahmoud Moravej
quelle
17
Dies ist beim verschachtelten teilweisen Rendern nicht korrekt. Sie müssen das neue Präfix an das alte Präfix von helper.ViewData.TemplateInfo.HtmlFieldPrefixin Form von{oldprefix}.{newprefix}
Ivan Zlatev
@Mahmoud Ihr Code funktioniert hervorragend, aber ich habe festgestellt, dass ViewData / ViewBag leer war, als es an der Zeit war, Code im Partial auszuführen. Ich fand heraus, dass der Helfer vom Typ HtmlHelper <TModel> eine neue Eigenschaft ViewData hatte, die die des Basismodells versteckte. Damit ersetzt ich new ViewDataDictionary(helper.ViewData)mit new ViewDataDictionary(((HtmlHelper)helper).ViewData). Sehen Sie ein Problem damit?
Pat Newell
@ IvanZlatev Danke. Ich werde den Beitrag nach dem Testen korrigieren.
Mahmoud Moravej
2
@IvanZlatev ist korrekt. Bevor Sie den Namen festlegen, sollten Sie tunstring oldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix; if (oldPrefix != "") name = oldPrefix + "." + name;
kennethc
2
Eine einfache Lösung für verschachtelte Vorlagen ist die Verwendung von HtmlFieldPrefix = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName (Name)
Aman Mahajan
95

Bisher habe ich nach dem gesucht, was ich in diesem letzten Beitrag gefunden habe:

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" }
})
%>
Jokin
quelle
3
Vielen Dank für den Link, dies ist bei weitem die beste Option hier aufgeführt
BZ
32
Super spät zu dieser Partei, aber wenn Sie diesen Ansatz machen, sollten Sie den ViewDataDictionaryKonstruktor verwenden, der den aktuellen verwendet ViewData, oder Sie verlieren Modellstatusfehler, Validierungsdaten usw.
Bhamlin
9
Aufbauend auf Bhamlins Kommentar. Sie können auch verschachtelte Präfixe weitergeben, z ViewData.TemplateInfo.HtmlFieldPrefix & ".PropX"}})
Hubson Bropa
1
Tolle Antwort, +1. Vielleicht lohnt es sich, es zu bearbeiten, um den Kommentar von @bhamlin zu berücksichtigen
ken2k
1
Beachten Sie, dass Sie dieses Präfix auch dort festlegen müssen, wenn Sie diesen Teil von einem Controller zurückgeben müssen. stackoverflow.com/questions/6617768/…
Der Muffin-Mann
12

Meine Antwort basiert auf der Antwort von Mahmoud Moravej, einschließlich des Kommentars von 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);
    }

Bearbeiten: Die Antwort von Mohamoud ist für verschachteltes partielles Rendern falsch. Sie müssen das neue Präfix nur dann an das alte Präfix anhängen, wenn dies erforderlich ist. Dies war in den letzten Antworten nicht klar (:

asoifer1879
quelle
6
Es wäre für andere hilfreich, wenn Sie näher erläutern, wie das oben Gesagte die vorhandene Antwort verbessert.
Leigh
2
Nach einem Fehler beim Kopieren und Einfügen mit fetten Fingern funktionierte dieser Fehler perfekt für ASP.NET MVC 5. +1.
Greg Burghardt
9

Mit MVC2 können Sie dies erreichen.

Hier ist die stark typisierte Ansicht:

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

Hier ist die stark typisierte Ansicht für die untergeordnete Klasse (die in einem Unterordner des Ansichtsverzeichnisses namens EditorTemplates gespeichert werden muss):

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

Hier ist der Controller:

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

Hier sind die benutzerdefinierten Klassen:

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

Und die Ausgabequelle:

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

Nun ist dies abgeschlossen. Legen Sie in der Aktion "Post-Controller erstellen" einen Haltepunkt fest, um dies zu überprüfen. Verwenden Sie dies jedoch nicht mit Listen, da dies nicht funktioniert. Weitere Informationen hierzu finden Sie in meiner Frage zur Verwendung von EditorTemplates mit IEnumerable.

Nick Larsen
quelle
Ja, es sieht so aus. Die Probleme sind, dass Listen nicht funktionieren (während verschachtelte Ansichtsmodelle normalerweise in Listen enthalten sind) und dass es sich um Version 2 handelt, die ich in der Produktion noch nicht verwenden kann. Aber immer noch gut zu wissen, dass es etwas sein wird, das ich brauche ... wenn es darum geht (also +1).
Königin3
Ich bin neulich auch darauf gestoßen , matthidinger.com/archive/2009/08/15/… . Vielleicht möchten Sie sehen, ob Sie herausfinden können, wie Sie es für Redakteure erweitern können (obwohl immer noch in MVC2). Ich habe ein paar Minuten damit verbracht, bin aber immer wieder auf Probleme gestoßen, weil ich mit Ausdrücken noch nicht auf dem neuesten Stand bin. Vielleicht kannst du es besser machen als ich.
Nick Larsen
1
Ein nützlicher Link, danke. Nicht, dass ich denke, dass es möglich ist, EditorFor hier zum Laufen zu bringen, da es vermutlich keine [0] -Indizes generiert (ich wette, es wird noch gar nicht unterstützt). Eine Lösung wäre, die Ausgabe von EditorFor () in einen String zu rendern und die Ausgabe manuell zu optimieren (erforderliche Präfixe anhängen). Ein schmutziger Hack. Hm! Ich kann eine Erweiterungsmethode für Html.Helper (). UsePrefix () ausführen, die nur name = "x" durch name = "prefix.x" ersetzt ... in MVC v1. Immer noch ein bisschen Arbeit, aber nicht so viel. Und Html.WithPrefix ("Präfix"). RenderPartial (), das paarweise funktioniert.
Königin3
+1 für die Empfehlung der Html.EditorForMethode zum Rendern des untergeordneten Formulars. Genau dafür sollen Editor-Vorlagen verwendet werden. Dies sollte die Antwort sein.
Greg Burghardt
9

Dies ist eine alte Frage, aber für alle, die hier ankommen und nach einer Lösung suchen, sollten Sie die Verwendung in Betracht ziehen EditorFor, wie in einem Kommentar unter https://stackoverflow.com/a/29809907/456456 vorgeschlagen . Führen Sie die folgenden Schritte aus, um von einer Teilansicht zu einer Editorvorlage zu wechseln.

  1. Stellen Sie sicher, dass Ihre Teilansicht an ComplexType gebunden ist .

  2. Verschieben Sie Ihre Teilansicht in einen Unterordner EditorTemplates des aktuellen Ansichtsordners oder in den Ordner Shared . Jetzt ist es eine Editorvorlage.

  3. Wechseln Sie @Html.Partial("_PartialViewName", Model.ComplexType)zu @Html.EditorFor(m => m.ComplexType, "_EditorTemplateName"). Die Editorvorlage ist optional, wenn sie die einzige Vorlage für den komplexen Typ ist.

HTML-Eingabeelemente werden automatisch benannt ComplexType.Fieldname.

R. Schreurs
quelle
8

PartailFor für asp.net Core 2, falls jemand es braucht.

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

Ich bin auch auf dieses Problem gestoßen, und nach viel Mühe fand ich, dass es einfacher war, meine Schnittstellen so zu gestalten, dass ich keine verschachtelten Modellobjekte zurückschicken musste. Dies zwang mich, meine Workflows für die Benutzeroberfläche zu ändern: Natürlich muss der Benutzer jetzt in zwei Schritten das tun, wovon ich geträumt habe, aber die Benutzerfreundlichkeit und Wartbarkeit des Codes des neuen Ansatzes ist für mich jetzt von größerem Wert.

Hoffe das hilft einigen.

Matt Kocaj
quelle
1

Sie können einen Helfer für das RenderPartial hinzufügen, der das Präfix übernimmt und es in die ViewData einfügt.

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

Dann ein weiterer Helfer, der den ViewData-Wert verkettet

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

und so in der Ansicht ...

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

in der partiellen ...

<%= Html.TextBox(Html.GetName("Name"), Model.Name) %>
Anthony Johnston
quelle
Ja, das habe ich in einem Kommentar zu stackoverflow.com/questions/1488890/… beschrieben . Eine noch bessere Lösung wäre RenderPartial, das auch Lambda verwendet und einfach zu implementieren ist. Trotzdem, danke für den Code, ich denke, das ist der schmerzloseste und zeitloseste Ansatz.
Königin3
1
Warum nicht helper.ViewData.TemplateInfo.HtmlFieldPrefix = Präfix verwenden? Ich benutze das in meinem Helfer und es scheint gut zu funktionieren.
Ajbeaven
0

Wie Sie füge ich meinen ViewModels die Präfix-Eigenschaft (eine Zeichenfolge) hinzu, die ich vor meinen modellgebundenen Eingabenamen anhänge. (YAGNI verhindert das Folgende)

Eine elegantere Lösung könnte ein Basisansichtsmodell mit dieser Eigenschaft und einige HtmlHelper sein, die prüfen, ob das Ansichtsmodell von dieser Basis abgeleitet ist, und in diesem Fall das Präfix an den Eingabenamen anhängen.

Hoffentlich hilft das,

Dan

Daniel Elliott
quelle
1
Hmm, schade. Ich kann mir viele elegante Lösungen vorstellen, bei denen benutzerdefinierte HTML-Helfer Eigenschaften, Attribute, benutzerdefiniertes Rendern usw. überprüfen. Wenn ich beispielsweise MvcContrib FluentHtml verwende, schreibe ich alle neu, um meine Hacks zu unterstützen? Es ist seltsam, dass niemand darüber spricht, als ob jeder nur flache einstufige ViewModels verwendet ...
Queen3
In der Tat denke ich, dass das abgestufte ViewModel unvermeidlich ist, nachdem ich das Framework für längere Zeit verwendet und nach einem netten, sauberen Ansichtscode gesucht habe. Das Basket ViewModel im Order ViewModel zum Beispiel.
Daniel Elliott
0

Wie wäre es kurz bevor Sie RenderPartial aufrufen?

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

Dann haben Sie in Ihrem Teil

<%= Html.TextBox(ViewData["Prefix"] + "Name", Model.Name) %>
Anthony Johnston
quelle
1
Dies entspricht im Wesentlichen der manuellen Übergabe (es ist typsicher, alle Ansichtsmodelle von IViewModel mit "IViewModel SetPrefix (Zeichenfolge)" abzuleiten) und ist sehr hässlich, es sei denn, ich schreibe alle HTML-Helfer und RenderPartial neu, damit sie automatisch verwaltet werden Dies. Das Problem ist nicht, wie es geht, ich habe dies bereits gelöst; Das Problem ist, kann es automatisch durchgeführt werden.
Königin3
Da es keinen gemeinsamen Ort gibt, können Sie Code einfügen, der alle HTML-Helfer betrifft. Dies wäre nicht automatisch möglich. siehe andere Antwort ...
Anthony Johnston