2

次のコードはかなり削除されていますが、基本的に私が達成しようとしているのは次のとおりです。

ページから質問/回答の選択肢を動的に追加/削除できるようにしながら、質問とそれに含まれる回答の選択肢を編集できるようにしたいと考えています。理想的には、アイテムの HtmlFieldPrefix はノンシーケンシャルですが、Html.EditorFor() はシーケンシャル インデックスを使用します。

回答の選択肢の IEnumerable を含む Question ViewModel があります。

public class QuestionViewModel
{
    public int QuestionId { get; set; }
    public IEnumerable<AnswerChoiceViewModel> AnswerChoices { get; set; }
}

私の質問の部分ビュー (Question.ascx) には、次のようなものがあります。

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.QuestionViewModel>" %>

<%=Html.HiddenFor(m => m.QuestionId)%>
<%=Html.EditorFor(m => m.AnswerChoices) %>

Answer Choice エディター テンプレート (AnswerChoiceViewModel.ascx):

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<Models.AnswerChoiceViewModel>" %>

<%=Html.HiddenFor(m => m.AnswerChoiceId)%>
<%=Html.TextBoxFor(m => m.Name)%>

Question.ascx をレンダリングすると、出力は次のようになります。

<input type="hidden" id="QuestionId" value="1" />
<input type="hidden" id="Question.AnswerChoices[0].AnswerChoiceId" value="1" />
<input type="hidden" id="Question.AnswerChoices[0].Name" value="Answer Choice 1" />

<input type="hidden" id="QuestionId" value="2" />
<input type="hidden" id="Question.AnswerChoices[1].AnswerChoiceId" value="2" />
<input type="hidden" id="Question.AnswerChoices[1].Name" value="Answer Choice 2" />

私が知りたいのは、ページが次のようにレンダリングされるように、EditorFor にカスタム GUID インデックスを提供する方法です。

<input type="hidden" id="QuestionId" value="1" />
<input type="hidden" id="Question.AnswerChoices[e1424d5e-5585-413c-a1b0-595f39747876].AnswerChoiceId" value="1" />
<input type="hidden" id="Question.AnswerChoices[e1424d5e-5585-413c-a1b0-595f39747876].Name" value="Answer Choice 1" />

<input type="hidden" id="QuestionId" value="2" />
<input type="hidden" id="Question.AnswerChoices[633db1c3-f1e6-470b-9c7f-c138f2d9fa71].AnswerChoiceId" value="2" />
<input type="hidden" id="Question.AnswerChoices[633db1c3-f1e6-470b-9c7f-c138f2d9fa71].Name" value="Answer Choice 2" />

現在のコンテキストのプレフィックス インデックスを取得し、それを非表示の ".Index" フィールドに格納するヘルパー メソッドを既に作成して、非連続インデックスを正しくバインドできるようにしました。EditorFor がどのようにインデックスを割り当てているかを知りたいだけなので、それをオーバーライドできます (または他の実用的なソリューション)。

4

5 に答える 5

2

少し前に私はこの問題に取り組み、S. Sanderson (Knockoutjs の作成者) からの投稿に出くわし、そこで彼は同様の問題を説明して解決しました。私は彼のコードの一部を使用し、私のニーズに合わせて修正しようとしました。以下のコードをいくつかのクラス (例: Helpers.cs) に配置し、web.config に名前空間を追加します。

    #region CollectionItem helper
    private const string idsToReuseKey = "__htmlPrefixScopeExtensions_IdsToReuse_";

    public static IDisposable BeginCollectionItem(this HtmlHelper html, string collectionName)
    {
        var idsToReuse = GetIdsToReuse(html.ViewContext.HttpContext, collectionName);
        string itemIndex = idsToReuse.Count > 0 ? idsToReuse.Dequeue() : Guid.NewGuid().ToString();

        // autocomplete="off" is needed to work around a very annoying Chrome behaviour whereby it reuses old values after the user clicks "Back", which causes the xyz.index and xyz[...] values to get out of sync.
        html.ViewContext.Writer.WriteLine(string.Format("<input type=\"hidden\" name=\"{0}.index\" autocomplete=\"off\" value=\"{1}\" />", collectionName, itemIndex));

        return BeginHtmlFieldPrefixScope(html, string.Format("{0}[{1}]", collectionName, itemIndex));
    }

    public static IDisposable BeginHtmlFieldPrefixScope(this HtmlHelper html, string htmlFieldPrefix)
    {
        return new HtmlFieldPrefixScope(html.ViewData.TemplateInfo, htmlFieldPrefix);
    }

    private static Queue<string> GetIdsToReuse(HttpContextBase httpContext, string collectionName)
    {
        // We need to use the same sequence of IDs following a server-side validation failure,  
        // otherwise the framework won't render the validation error messages next to each item.
        string key = idsToReuseKey + collectionName;
        var queue = (Queue<string>)httpContext.Items[key];
        if (queue == null)
        {
            httpContext.Items[key] = queue = new Queue<string>();
            var previouslyUsedIds = httpContext.Request[collectionName + ".index"];
            if (!string.IsNullOrEmpty(previouslyUsedIds))
                foreach (string previouslyUsedId in previouslyUsedIds.Split(','))
                    queue.Enqueue(previouslyUsedId);
        }
        return queue;
    }

    private class HtmlFieldPrefixScope : IDisposable
    {
        private readonly TemplateInfo templateInfo;
        private readonly string previousHtmlFieldPrefix;

        public HtmlFieldPrefixScope(TemplateInfo templateInfo, string htmlFieldPrefix)
        {
            this.templateInfo = templateInfo;

            previousHtmlFieldPrefix = templateInfo.HtmlFieldPrefix;
            templateInfo.HtmlFieldPrefix = htmlFieldPrefix;
        }

        public void Dispose()
        {
            templateInfo.HtmlFieldPrefix = previousHtmlFieldPrefix;
        }
    }

    #endregion

このような EditorTemplate またはパーシャルを使用できるようになった後

@using (Html.BeginCollectionItem("AnswerChoices"))
{
@Html.HiddenFor(m => m.AnswerChoiceId)
@Html.TextBoxFor(m => m.Name)
}

そして、リスト レンダリング テンプレート (部分) を列挙します。

于 2012-06-29T19:13:50.723 に答える
2

これを理解するのに必要以上に時間がかかりました。誰もがこれを行うために一生懸命働いています。秘密のソースは、次の 4 行のコードです。

        @{
            var index = Guid.NewGuid();
            var prefix = Regex.Match(ViewData.TemplateInfo.HtmlFieldPrefix, @"^(.+)\[\d+\]$").Groups[1].Captures[0].Value;
            //TODO add a ton of error checking and pull this out into a reusable class!!!!
            ViewData.TemplateInfo.HtmlFieldPrefix = prefix + "[" + index + "]";
        }
        <input type="hidden" name="@(prefix).Index" value="@index"/>

さて、これは何をしているのでしょう?新しい GUID を取得します。これは、自動的に割り当てられた整数のインデックスを置き換えるための新しいインデックスです。次に、デフォルトのフィールド プレフィックスを取得し、不要な int インデックスを取り除きます。技術的負債が発生したことを確認した後、viewdata を更新して、呼び出しのすべてのエディターがそれを新しいプレフィックスとして使用するようにします。最後に、これらのフィールドを一緒にバインドするために使用する必要があるインデックスを指定して、モデル バインダーにポストバックされる入力を追加します。

この魔法はどこで起こる必要がありますか? エディター テンプレート内: /Views/Shared/EditorTemplates/Phone.cshtml

@using TestMVC.Models
@using System.Text.RegularExpressions
@model Phone
    <div class="form-horizontal">
        <hr />
        @{
            var index = Guid.NewGuid();
            var prefix = Regex.Match(ViewData.TemplateInfo.HtmlFieldPrefix, @"^(.+)\[\d+\]$").Groups[1].Captures[0].Value;
            //TODO add a ton of error checking and pull this out into a reusable class!!!!
            ViewData.TemplateInfo.HtmlFieldPrefix = prefix + "[" + index + "]";
        }
        <input type="hidden" name="@(prefix).Index" value="@index"/>
        <div class="form-group">
            @Html.LabelFor(model => model.Number, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Number, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Number, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.IsEnabled, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                <div class="checkbox">
                    @Html.EditorFor(model => model.IsEnabled)
                    @Html.ValidationMessageFor(model => model.IsEnabled, "", new { @class = "text-danger" })
                </div>
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.Details, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.TextAreaFor(model => model.Details, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Details, "", new { @class = "text-danger" })
            </div>
        </div>
    </div>

エディター テンプレート? 何?!どのように?!ファイル名にオブジェクト名を使用して、上記のディレクトリに配置するだけです。MVC 規約にその魔法を働かせましょう。メイン ビューから、その IEnumerable プロパティのエディターを追加するだけです。

<div class="form-group">
@Html.LabelFor(model => model.Phones, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
    @Html.EditorFor(model => model.Phones, new { htmlAttributes = new { @class = "form-control" } })
</div>
</div>

ここで、コントローラーに戻って、メソッド シグネチャを更新して、その ienumerable (Bind include Phones) を受け入れるようにします。

        [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Create([Bind(Include = "ContactId,FirstName,LastName,Phones")] Contact contact)
    {
        if (ModelState.IsValid)
        {

            db.Contacts.Add(contact);
            db.SaveChanges();
            //TODO need to update this to save phone numbers
            return RedirectToAction("Index");
        }

        return View(contact);
    }

ページでそれらをどのように追加および削除しますか? ボタンをいくつか追加し、JavaScript をバインドし、そのモデルのビューを返すメソッドをコントローラーに追加します。Ajax バックしてそれを取得し、ページに挿入します。現時点では忙しい仕事なので、詳細はお任せします。

于 2016-10-14T17:59:48.873 に答える
0

別のオプションは、次のように id 属性をオーバーライドすることです。

@Html.TextBoxFor(m => m.Name, new { id = @guid })

于 2012-06-29T19:14:19.787 に答える
0

Html.EditorForinputすべての適切な属性をレンダリングする、いわゆる Html ヘルパー メソッドにほかなりません。

私が頭に浮かぶ唯一の解決策は、独自のものを書くことです。それは非常に単純でなければなりません - 5-10 行が鳴ります。このMaking Custom Html Helpers Mvc を見てください。

于 2012-06-29T19:07:46.813 に答える
0

Steve Sanderson は、探していることを実行できる簡単な実装を提供しています。私は最近それを自分で使い始めました。完璧ではありませんが、機能します。BeginCollectionItem残念ながら、彼の方法を使用するには、少しマジック ストリングを実行する必要があります。私はそれを自分で回避しようとしています。

于 2012-06-29T19:08:01.470 に答える