Visualizzazioni parziali ASP.NET MVC: prefissi dei nomi di input


120

Supponiamo che io abbia ViewModel come

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

Nella vista posso eseguire il rendering di un parziale con

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

Nel parziale lo farò

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

Tuttavia, il problema è che entrambi renderanno name = "Name" mentre devo avere name = "Child.Name" affinché il raccoglitore di modelli funzioni correttamente. Oppure, name = "Child2.Name" quando eseguo il rendering della seconda proprietà utilizzando la stessa vista parziale.

Come faccio a fare in modo che la mia visualizzazione parziale riconosca automaticamente il prefisso richiesto? Posso passarlo come parametro ma questo è troppo scomodo. Questo è anche peggio quando voglio, ad esempio, renderlo ricorsivamente. C'è un modo per rendere le viste parziali con un prefisso o, ancora meglio, con la riconizione automatica dell'espressione lambda chiamante in modo che

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

aggiungerà automaticamente "Child" corretto. prefisso alle stringhe nome / ID generate?

Posso accettare qualsiasi soluzione, inclusi i motori di visualizzazione e le librerie di terze parti: in realtà uso Spark View Engine ("risolvo" il problema utilizzando le sue macro) e MvcContrib, ma non ho trovato una soluzione lì. XForms, InputBuilder, MVC v2: qualsiasi strumento / insight che fornisce questa funzionalità sarà eccezionale.

Attualmente penso di codificarlo da solo, ma sembra una perdita di tempo, non posso credere che questa roba banale non sia già implementata.

Possono esistere molte soluzioni manuali e tutte sono benvenute. Ad esempio, posso forzare i miei partials a essere basati su IPartialViewModel <T> {public string Prefix; Modello T; }. Ma preferisco una soluzione esistente / approvata.

AGGIORNAMENTO: c'è una domanda simile senza risposta qui .

Risposte:


110

Puoi estendere la classe helper Html in questo modo:

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

    }

e usalo semplicemente nelle tue visualizzazioni in questo modo:

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

e vedrai che è tutto ok!


17
Ciò non sarà corretto per il rendering parziale nidificato. È necessario aggiungere il nuovo prefisso al vecchio prefisso da helper.ViewData.TemplateInfo.HtmlFieldPrefixsotto forma di{oldprefix}.{newprefix}
Ivan Zlatev

@Mahmoud Il tuo codice funziona alla grande, ma ho scoperto che ViewData / ViewBag era vuoto quando è arrivato il momento di eseguire il codice nel Partial. Ho scoperto che l'helper, di tipo HtmlHelper <TModel> aveva una nuova proprietà ViewData, che nascondeva il modello di base. Con quello, ho sostituito new ViewDataDictionary(helper.ViewData)con new ViewDataDictionary(((HtmlHelper)helper).ViewData). Vedi qualche problema con quello?
Pat Newell

@IvanZlatev Grazie. Correggerò il post dopo averlo testato.
Mahmoud Moravej

2
@IvanZlatev è corretto. Prima di impostare il nome, dovresti farlostring oldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix; if (oldPrefix != "") name = oldPrefix + "." + name;
kennethc

2
Una soluzione semplice per i modelli nidificati consiste nell'usare HtmlFieldPrefix = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName (nome)
Aman Mahajan

95

finora, stavo cercando la stessa cosa che ho trovato in questo recente post:

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
Grazie per il link, questa è di gran lunga l'opzione migliore elencata qui
BZ

32
Super tardi per questa festa, ma se si esegue questo approccio, è necessario utilizzare il ViewDataDictionarycostruttore che prende la corrente ViewData, altrimenti si perderanno errori di stato del modello, dati di convalida, ecc.
bhamlin

9
basandosi sul commento di bhamlin. puoi anche passare un prefisso annidato come (sry this is vb.net ex): Html.RenderPartial ("AnotherViewModelControl", Model.PropX, New ViewDataDictionary (ViewData) With {.TemplateInfo = New TemplateInfo () With {.HtmlFieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix & ".PropX"}})
hubson bropa

1
Ottima risposta, +1. Forse varrebbe la pena modificarlo per tenere conto del commento di
@bhamlin

1
Nota che se devi restituire questo parziale da un controller dovrai impostare questo prefisso anche lì. stackoverflow.com/questions/6617768/…
The Muffin Man

12

La mia risposta, basata sulla risposta di Mahmoud Moravej compreso il commento di 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);
    }

Modifica: la risposta di Mohamoud non è corretta per il rendering parziale nidificato. È necessario aggiungere il nuovo prefisso al vecchio prefisso, solo se necessario. Questo non era chiaro nelle ultime risposte (:


6
Sarebbe utile per gli altri se elaborassi su come quanto sopra migliora la risposta esistente.
Leigh

2
Dopo un errore di copia e incolla dalle dita grasse, questo ha funzionato perfettamente per ASP.NET MVC 5. +1.
Greg Burghardt

9

Usando MVC2 puoi ottenere questo risultato.

Ecco la vista fortemente tipizzata:

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

Ecco la vista fortemente tipizzata per la classe figlia (che deve essere memorizzata in una sottocartella della directory della vista chiamata 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 />

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

Ecco le classi personalizzate:

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

E la fonte di output:

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

Ora questo è completo. Impostare un punto di interruzione nell'azione Crea controller post da verificare. Tuttavia, non utilizzarlo con gli elenchi perché non funzionerà. Vedi la mia domanda sull'uso di EditorTemplates con IEnumerable per ulteriori informazioni.


Sì, sembra proprio così. I problemi sono che gli elenchi non funzionano (mentre i modelli di visualizzazione annidati sono generalmente in elenchi) e che è v2 ... che non sono ancora pronto per l'uso in produzione. Ma è comunque bello sapere che sarà qualcosa di cui ho bisogno ... quando arriverà (quindi +1).
queen3,

Mi sono imbattuto anche in questo l'altro giorno, matthidinger.com/archive/2009/08/15/… , potresti voler vedere se riesci a capire come estenderlo per gli editor (anche se ancora in MVC2). Ci ho impiegato alcuni minuti, ma ho continuato a incontrare problemi perché non sono ancora all'altezza delle espressioni. Forse puoi fare meglio di me.
Nick Larsen

1
Un link utile, grazie. Non che io pensi sia possibile far funzionare EditorFor qui, perché suppongo che non genererà indici [0] (scommetto che non lo supporta ancora). Una soluzione sarebbe rendere l'output di EditorFor () in stringa e modificare manualmente l'output (aggiungere i prefissi richiesti). Un trucco sporco, però. Hm! Posso fare il metodo di estensione per Html.Helper (). UsePrefix () che sostituirà semplicemente name = "x" con name = "prefix.x" ... in MVC v1. Ancora un po 'di lavoro ma non così tanto. E Html.WithPrefix ("prefix"). RenderPartial () che funziona in coppia.
Queen3

+1 per aver consigliato il Html.EditorFormetodo per eseguire il rendering del form figlio. Questo è esattamente ciò per cui dovrebbero essere utilizzati i modelli di editor. Questa dovrebbe essere la risposta.
Greg Burghardt,

9

Questa è una vecchia domanda, ma per chiunque arrivi qui alla ricerca di una soluzione, considera l'utilizzo EditorFor, come suggerito in un commento in https://stackoverflow.com/a/29809907/456456 . Per passare da una visualizzazione parziale a un modello di editor, segui questi passaggi.

  1. Verificare che la visualizzazione parziale sia associata a ComplexType .

  2. Spostare la visualizzazione parziale in una sottocartella EditorTemplates della cartella della visualizzazione corrente o nella cartella Shared . Ora è un modello di editor.

  3. Cambia @Html.Partial("_PartialViewName", Model.ComplexType)in @Html.EditorFor(m => m.ComplexType, "_EditorTemplateName"). Il modello dell'editor è facoltativo se è l'unico modello per il tipo complesso.

Gli elementi di input HTML verranno automaticamente denominati ComplexType.Fieldname.


8

PartailFor per asp.net Core 2 nel caso qualcuno ne avesse bisogno.

    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

Mi sono imbattuto anche in questo problema e dopo molto dolore ho scoperto che era più facile riprogettare le mie interfacce in modo tale da non dover postare indietro gli oggetti del modello nidificati. Questo mi ha costretto a cambiare i flussi di lavoro dell'interfaccia: sicuramente ora richiedo all'utente di fare in due passaggi ciò che sognavo di fare su uno, ma l'usabilità e la manutenibilità del codice del nuovo approccio sono di maggior valore per me ora.

Spero che questo aiuti alcuni.


1

Potresti aggiungere un helper per RenderPartial che prende il prefisso e lo inserisce in ViewData.

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

Quindi un ulteriore aiuto che concatena il valore ViewData

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

e così nella vista ...

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

nel parziale ...

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

Sì, è quello che ho descritto in un commento a stackoverflow.com/questions/1488890/… . Una soluzione ancora migliore sarebbe RenderPartial che accetta anche lambda, che è facile da implementare. Tuttavia, grazie per il codice, immagino che sia l'approccio più indolore e senza tempo.
queen3

1
perché non utilizzare helper.ViewData.TemplateInfo.HtmlFieldPrefix = prefix? Lo sto usando nel mio assistente e sembra funzionare bene.
ajbeaven

0

Come te, aggiungo la proprietà Prefix (una stringa) ai miei ViewModels che aggiungo prima dei nomi di input associati al modello. (YAGNI impedisce il seguito)

Una soluzione più elegante potrebbe essere un modello di visualizzazione di base che abbia questa proprietà e alcuni HtmlHelpers che controllano se il modello di visualizzazione deriva da questa base e in tal caso aggiungono il prefisso al nome di input.

Spero che aiuti,

Dan


1
Hmm, peccato. Posso immaginare molte soluzioni eleganti, con HtmlHelpers personalizzati che controllano proprietà, attributi, rendering personalizzati, ecc ... Ma ad esempio se uso MvcContrib FluentHtml, li riscrivo tutti per supportare i miei hack? È strano che nessuno ne parli, come se tutti
usassero

In effetti, dopo aver utilizzato il framework per un certo periodo di tempo e aver cercato di ottenere un codice di visualizzazione pulita, penso che il ViewModel a livelli sia inevitabile. Ad esempio, il Basket ViewModel all'interno di Order ViewModel.
Daniel Elliott,

0

Che ne dici di appena prima di chiamare RenderPartial che fai

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

Quindi nel tuo parziale hai

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

1
Questo è fondamentalmente lo stesso che passarlo manualmente (è sicuro derivare tutti i modelli di visualizzazione da IViewModel con "IViewModel SetPrefix (stringa)") ed è molto brutto, a meno che io non riscriva tutti gli helper Html e RenderPartial in modo che gestiscano automaticamente Questo. Il problema non è come farlo, l'ho già risolto; il problema è che può essere fatto automaticamente.
Queen3

poiché non esiste un posto comune in cui puoi inserire codice che influirà su tutti gli helper html, non sarai in grado di farlo automaticamente. vedi altra risposta ...
Anthony Johnston,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.