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 = "Child.Name"が必要な一方で、両方がname = "Name"をレンダリングすることです。または、同じ部分ビューを使用して2番目のプロパティをレンダリングするとき、name = "Child2.Name"。

部分ビューに必要なプレフィックスを自動的に認識させるにはどうすればよいですか?パラメータとして渡すことはできますが、不便です。これは、たとえば再帰的にレンダリングしたい場合はさらに悪化します。プレフィックスを付けて部分的なビューをレンダリングする方法、または呼び出し側のラムダ式を自動調整して、

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

正しい「子」が自動的に追加されます。生成された名前/ ID文字列のプレフィックス?

サードパーティのビューエンジンやライブラリを含む、あらゆるソリューションを受け入れることができます-私は実際にはSpark View Engine(そのマクロを使用して問題を「解決」)とMvcContribを使用していますが、そこでソリューションは見つかりませんでした。XForms、InputBuilder、MVC v2-この機能を提供するあらゆるツール/洞察は素晴らしいでしょう。

現在私はこれを自分でコーディングすることを考えていますが、それは時間の無駄のようです。この些細なことはまだ実装されていないとは信じられません。

多くの手動による解決策が存在する可能性があり、それらはすべて歓迎されます。たとえば、パーシャルをIPartialViewModel <T> {public string Prefix;に基づくように強制できます。Tモデル; }。しかし、私はむしろいくつかの既存の/承認されたソリューションを好みます。

更新:ここに回答がない同様の質問があります

回答:


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 2012

2
@IvanZlatevは正しいです。名前を設定する前に、次のことを行う必要がありますstring oldPrefix = helper.ViewData.TemplateInfo.HtmlFieldPrefix; if (oldPrefix != "") name = oldPrefix + "." + name;
kennethc 2014

2
ネストされたテンプレートの簡単な修正は、HtmlFieldPrefix = helper.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name)
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
バムリンのコメントに基づいて構築。次のようなネストされた接頭辞を渡すこともできます(これはvb.net exです):Html.RenderPartial( "AnotherViewModelControl"、Model.PropX、New ViewDataDictionary(ViewData)With {.TemplateInfo = New TemplateInfo()With {.HtmlFieldPrefix = ViewData.TemplateInfo.HtmlFieldPrefix& ".PropX"}})
hubson bropa '21

1
すばらしい答え、+ 1。@bhamlinコメントを考慮に入れるには、編集する価値があるかもしれません
ken2k 14

1
コントローラからこのパーシャルを返す必要がある場合は、このプレフィックスもそこに設定する必要があることに注意してください。stackoverflow.com/questions/6617768/...
マフィンマン

12

私の答えは、Ivan Zlatevのコメントを含むMahmoud Moravejの答えに基づいています。

    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>

これで完了です。ポストコントローラーの作成アクションにブレークポイントを設定して確認します。ただし、これは機能しないため、リストでは使用しないでください。詳細については、IEnumerableでのEditorTemplatesの使用に関する私の質問を参照してください。


はい、そのように見えます。問題は、リストが機能しないこと(ネストされたビューモデルは通常リスト内にある)、それがv2であるということです...実稼働で使用する準備がまだ十分ではありません。しかし、それでも私が必要とするものになることを知っておくのは良いことです。
queen3 2009

先日もこの問題に遭遇しました、matthidinger.com / archive / 2009/08/15 /…。エディター用に拡張する方法を見つけられるかどうかを確認する必要があるかもしれません(まだMVC2です)。それに数分を費やしましたが、まだ表現に慣れていないため、問題が発生し続けました。たぶん、あなたは私よりもうまくやれるでしょう。
Nick Larsen、

1
便利なリンク、ありがとう。EditorForをここで機能させることは可能だとは思いませんが、[0]インデックスを生成しないためです(まだサポートしていないと思います)。1つの解決策は、EditorFor()出力を文字列にレンダリングし、手動で出力を微調整することです(必要なプレフィックスを追加します)。しかし、汚いハック。うーん!Html.Helper()。UsePrefix()の拡張メソッドを実行できます。これは、MVC v1でname = "x"をname = "prefix.x" ...に置き換えるだけです。まだ少し作業がありますが、それほど多くはありません。また、Html.WithPrefix( "prefix")。RenderPartial()はペアで機能します。
queen3 2009

Html.EditorFor子フォームをレンダリングするメソッドを推奨するための+1 。これは、正確にエディタテンプレートが使用されることになっているものです。これが答えです。
Greg Burghardt 2016

9

これは古い質問ですが、解決策を探してここに到着した人EditorForは、https://stackoverflow.com/a/29809907/456456のコメントで提案されているように、の使用を検討してください。部分ビューからエディターテンプレートに移動するには、次の手順に従います。

  1. 部分ビューがComplexTypeにバインドされていることを確認します。

  2. 部分ビューを現在のビューフォルダーのサブフォルダーEditorTemplates、またはフォルダーSharedに移動します。現在、これはエディターテンプレートです。

  3. に変更@Html.Partial("_PartialViewName", Model.ComplexType)@Html.EditorFor(m => m.ComplexType, "_EditorTemplateName")ます。エディターテンプレートは、複合型の唯一のテンプレートである場合はオプションです。

HTML入力要素には自動的に名前が付けられComplexType.Fieldnameます。


8

誰かがそれを必要とする場合のasp.net Core 2のPartailFor

    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つのステップでやりたかったことを2つのステップで実行する必要があることを確認しましたが、新しいアプローチの使いやすさとコードの保守性は、私にとって今より大きな価値があります。

これが一部に役立つことを願っています。


1

プレフィックスを受け取り、それをViewDataにポップするRenderPartialのヘルパーを追加できます。

    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であり、実装が簡単です。それでも、コードのおかげで、それは最も痛みがなく、時間をかけないアプローチだと思います。
クイーン3 2009

1
helper.ViewData.TemplateInfo.HtmlFieldPrefix = prefixを使用しないのはなぜですか?私はそれを私のヘルパーで使用しており、正常に動作しているようです。
ajbeaven

0

あなたと同じように、モデルにバインドされた入力名の前に追加するViewModelにPrefixプロパティ(文字列)を追加します。(YAGNIが以下を防ぐ)

よりエレガントなソリューションは、このプロパティを持つベースビューモデルと、ビューモデルがこのベースから派生しているかどうかをチェックし、そうである場合は入力名にプレフィックスを追加するHtmlHelpersです。

お役に立てれば幸いです。

ダン


1
残念です。カスタムHtmlHelpersがプロパティ、属性、カスタムレンダリングなどをチェックすることで、多くのエレガントなソリューションを想像できます。たとえば、MvcContrib FluentHtmlを使用する場合、ハックをサポートするためにそれらすべてを書き換えますか?誰もがフラットな単一レベルのViewModelを使用しているように、誰もそれについて
話さ

実際、フレームワークを任意の期間使用し、きれいなビューコードを作成した後、階層化されたViewModelは避けられないと思います。たとえば、注文ビューモデル内のバスケットビューモデル。
ダニエルエリオット

0

RenderPartialを呼び出す直前にどうですか

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

次に、あなたの部分であなたが持っています

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

1
これは基本的に手動で渡すのと同じです(すべてのビューモデルをIViewModelから "IViewModel SetPrefix(string)"で派生させるのはタイプセーフです)。すべてのHtmlヘルパーとRenderPartialを書き換えて自動的に管理しない限り、非常に醜いですこの。問題はそれを行う方法ではありません。私はすでにこれを解決しました。問題は、自動的に実行できるかどうかです。
queen3 2009

すべてのHTMLヘルパーに影響するコードを配置できる共通の場所がないため、これを自動的に行うことはできません。他の回答を参照してください...
Anthony Johnston、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.