web-dev-qa-db-ja.com

ASP.NET MVC部分ビュー:入力名のプレフィックス

私のような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モデル; }。ただし、既存のソリューションまたは承認済みのソリューションを希望します。

更新:答えのない同様の質問があります here

118
queen3

これにより、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") %>

そして、あなたはすべてが大丈夫であることを見るでしょう!

109
Mahmoud Moravej

これまでのところ、私はこの最近の投稿を見つけたのと同じものを探していました:

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

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の答えは、ネストされた部分レンダリングでは正しくありません。必要な場合にのみ、新しいプレフィックスを古いプレフィックスに追加する必要があります。これは最新の回答では明確ではありませんでした(:

12
asoifer1879

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を使用することに関する私の質問を参照してください。

9
Nick Larsen

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

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

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

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

Html入力要素には、自動的にComplexType.Fieldnameという名前が付けられます。

8
R. Schreurs

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

    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

私もこの問題に遭遇し、多くの苦痛の後、ネストされたモデルオブジェクトをポストバックする必要がないようにインターフェイスを再設計する方が簡単であることがわかりました。これにより、インターフェイスワークフローを変更せざるを得なくなりました。ユーザーに1つのステップで夢見ていたことを2つのステップで実行するように要求するようになりましたが、新しいアプローチの使いやすさとコードの保守性は今では大きな価値があります。

これが助けになることを願っています。

3
Matt Kocaj

RenderPartialのヘルパーを追加して、プレフィックスを取得してViewDataにポップすることができます。

    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) %>
1

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

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

お役に立てば幸いです

ダン

0
Daniel Elliott

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

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

次に、あなたのパーシャルにあなたが持っています

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