web-dev-qa-db-fra.com

Vues partielles ASP.NET MVC: préfixes de nom d'entrée

Supposons que j'ai ViewModel comme

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

Dans la vue, je peux rendre un partiel avec

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

Dans le partiel je ferai

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

Cependant, le problème est que les deux afficheront name = "Name" alors que j'ai besoin d'avoir name = "Child.Name" pour que le classeur de modèle fonctionne correctement. Ou, name = "Child2.Name" lorsque je rend la deuxième propriété en utilisant la même vue partielle.

Comment faire pour que ma vue partielle reconnaisse automatiquement le préfixe requis? Je peux le passer en paramètre mais c'est trop gênant. C'est encore pire quand je veux par exemple le rendre récursivement. Existe-t-il un moyen de rendre des vues partielles avec un préfixe, ou, mieux encore, avec une reconstitution automatique de l'expression lambda appelante afin que

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

ajoutera automatiquement le bon "enfant". préfixe aux chaînes de nom/id générées?

Je peux accepter n'importe quelle solution, y compris les moteurs et bibliothèques de vues tiers - j'utilise en fait Spark View Engine (je "résous" le problème en utilisant ses macros) et MvcContrib, mais je n'ai pas trouvé de XForms, InputBuilder, MVC v2 - tout outil/aperçu qui fournit cette fonctionnalité sera génial.

Actuellement, je pense à coder cela moi-même, mais cela semble être une perte de temps, je ne peux pas croire que ce truc trivial ne soit pas déjà implémenté.

De nombreuses solutions manuelles peuvent exister, et toutes sont les bienvenues. Par exemple, je peux forcer mes partiels à être basés sur IPartialViewModel <T> {public string Prefix; Modèle T; }. Mais je préfère plutôt une solution existante/approuvée.

MISE À JOUR: il y a une question similaire sans réponse ici .

118
queen3

Vous pouvez étendre la classe d'assistance Html par ceci:

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

    }

et utilisez-le simplement dans vos vues comme ceci:

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

et vous verrez que tout va bien!

109
Mahmoud Moravej

jusqu'à présent, je cherchais la même chose que j'ai trouvé ce post récent:

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" }
})
%>
92
Jokin

Ma réponse, basée sur la réponse de Mahmoud Moravej incluant le commentaire d'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: la réponse de Mohamoud est incorrecte pour le rendu partiel imbriqué. Vous devez ajouter le nouveau préfixe à l'ancien préfixe, uniquement si cela est nécessaire. Cela n'était pas clair dans les dernières réponses (:

12
asoifer1879

En utilisant MVC2, vous pouvez y parvenir.

Voici la vue fortement typée:

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

Voici la vue fortement typée pour la classe enfant (qui doit être stockée dans un sous-dossier du répertoire de vue appelé 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 />

Voici le contrôleur:

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

Voici les classes personnalisées:

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

Et la source de sortie:

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

Maintenant, c'est terminé. Définissez un point d'arrêt dans l'action Créer un contrôleur Post à vérifier. Cependant, ne l'utilisez pas avec des listes car cela ne fonctionnera pas. Voir ma question sur l'utilisation de EditorTemplates avec IEnumerable pour en savoir plus.

9
Nick Larsen

C'est une vieille question, mais pour tous ceux qui arrivent ici à la recherche d'une solution, envisagez d'utiliser EditorFor, comme suggéré dans un commentaire dans https://stackoverflow.com/a/29809907/456456 =. Pour passer d'une vue partielle à un modèle d'éditeur, procédez comme suit.

  1. Vérifiez que votre vue partielle est liée à ComplexType .

  2. Déplacer votre vue partielle vers un sous-dossier EditorTemplates du dossier de vue actuel, ou vers le dossier Partagé . Maintenant, c'est un modèle d'éditeur.

  3. Remplacez @Html.Partial("_PartialViewName", Model.ComplexType) par @Html.EditorFor(m => m.ComplexType, "_EditorTemplateName"). Le modèle d'éditeur est facultatif s'il s'agit du seul modèle pour le type complexe.

Les éléments d'entrée HTML seront automatiquement nommés ComplexType.Fieldname.

8
R. Schreurs

PartailFor pour asp.net Core 2 au cas où quelqu'un en aurait besoin.

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

Je suis également tombé sur ce problème et après beaucoup de douleur, j'ai trouvé qu'il était plus facile de reconcevoir mes interfaces de sorte que je n'avais pas besoin de publier des objets de modèle imbriqués. Cela m'a obligé à changer mes workflows d'interface: bien sûr, je demande maintenant à l'utilisateur de faire en deux étapes ce que je rêvais de faire sur une seule, mais l'utilisabilité et la maintenabilité du code de la nouvelle approche sont désormais plus importantes pour moi.

J'espère que cela aide certains.

3
Matt Kocaj

Vous pouvez ajouter un assistant pour le RenderPartial qui prend le préfixe et le place dans le ViewData.

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

Puis un autre assistant qui concatène la valeur ViewData

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

et donc dans la vue ...

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

dans le partiel ...

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

Comme vous, j'ajoute la propriété Prefix (une chaîne) à mes ViewModels que j'ajoute avant les noms d'entrée liés à mon modèle. (YAGNI empêchant ce qui suit)

Une solution plus élégante pourrait être un modèle de vue de base qui possède cette propriété et certains HtmlHelpers qui vérifient si le modèle de vue dérive de cette base et, dans ce cas, ajoutez le préfixe au nom d'entrée.

J'espère que ça t'as aidé,

Dan

0
Daniel Elliott

Que diriez-vous juste avant d'appeler RenderPartial

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

Ensuite, dans votre partielle, vous avez

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