厳密に型指定されたビューからの複数モデル フォーム送信時のモデル バインディング

StackOverflow https://stackoverflow.com/questions/1064998

質問

複数のモデルが送信されているフォームにバインドする際に問題が発生します。苦情情報と 1 対多の苦情者を含む苦情フォームがあります。フォームを送信しようとしていますが、バインド時にエラーが発生します。ModelState.IsValid は常に false を返します。

デバッグして ModelState エラーを表示すると、次のようなメッセージが表示されます。「EntityCollection はすでに初期化されています。InitializeManyCollection メソッドは、オブジェクト グラフの逆シリアル化中に新しい EntityCollection を初期化するためにのみ呼び出す必要があります。

また、デバッグ時に、苦情モデルにフォーム送信からの苦情が入力されていることがわかり、その部分は機能しているようです。

私がやっていることはデフォルトの ModelBinder では不可能なのか、それとも単に正しい方法で行っていないのかはわかりません。これに関する具体的な例やドキュメントが見つからないようです。非常によく似た問題が stackoverflow で見つかります。 ここ ただし、強く型付けされたビューは処理していないようです。

コントローラーコード:

    public ActionResult Edit(int id)
    {
        var complaint = (from c in _entities.ComplaintSet.Include("Complainants")
                     where c.Id == id
                     select c).FirstOrDefault();

        return View(complaint);
    }

    //
    // POST: /Home/Edit/5
    [AcceptVerbs(HttpVerbs.Post)]
    public ActionResult Edit(Complaint complaint)
    {
        if (!ModelState.IsValid)
        {
            return View();
        }
        try
        {
            var originalComplaint = (from c in _entities.ComplaintSet.Include("Complainants")
                                     where c.Id == complaint.Id
                                     select c).FirstOrDefault();
            _entities.ApplyPropertyChanges(originalComplaint.EntityKey.EntitySetName, complaint);
            _entities.SaveChanges();
            return RedirectToAction("Index");
        }
        catch
        {
            return View();
        }
    }

ビュー コード (これはビューの作成/編集によって呼び出される部分的なビューであり、Complaint で厳密に型指定されます):

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

<%= Html.ValidationSummary() %>
<% using (Html.BeginForm()) {%>

<table cellpadding="0" cellspacing="0" class="table">
    <tr>
        <td>
        <label for="DateReceived">Date Received:</label>
            <%= Html.TextBox("DateReceived") %>
            <%= Html.ValidationMessage("DateReceived", "*") %>    
        </td>
        <td>
            <label for="DateEntered">Date Entered:</label>
            <%= Html.TextBox("DateEntered")%>
            <%= Html.ValidationMessage("DateEntered", "*") %>
        </td>
    </tr>
    <tr>
        <td>
            <label for="Concluded">Concluded:</label>
            <%= Html.CheckBox("Concluded")%>
            <%= Html.ValidationMessage("Concluded", "*") %>
            </td>
        <td>
            <label for="IncidentDate">Incident Date:</label>
            <%= Html.TextBox("IncidentDate")%>
            <%= Html.ValidationMessage("IncidentDate", "*") %></td>
    </tr>
</table>
    <hr />
    <table>
    <% if (Model != null) {
           int i = 0;
       foreach (var complainant in Model.Complainants){ %>
       <%= Html.Hidden("Complainants[" + i + "].Id", complainant.Id)%>
    <tr>
        <td>
            <label for="Surname">Surname:</label>

            <%= Html.TextBox("Complainants[" + i + "].Surname", complainant.Surname)%>
            <%= Html.ValidationMessage("Surname", "*")%>
        </td>
        <td>
            <label for="GivenName1">GivenName1:</label>
            <%= Html.TextBox("Complainants[" + i + "].GivenName1", complainant.GivenName1)%>
            <%= Html.ValidationMessage("GivenName1", "*")%>
        </td>
    </tr>
    <% i++; %>
    <% }} %>
    <tr>
        <td colspan=2>
            <input type="submit" value="Submit" />
        </td>
    </tr>
</table>
<% } %>
<div>
    <%=Html.ActionLink("Back to List", "Index") %>
</div>
役に立ちましたか?

解決

盲目的な推測:

変化:

<%= Html.TextBox("Complainants[" + i + "].Surname", complainant.Surname)%>

と:

<%= Html.TextBox("Complaint.Complainants[" + i + "].Surname",  
complainant.Surname)%>

それぞれ「苦情」を追加します。 「申立人[...」の前

編集:

これは正しい答えではありません。適切な答えが表示されるまで、価値が追加される可能性があるため、削除せずにそのままにしておきます。

編集2:

私は間違っているかもしれませんが、私にとっては、エンティティフレームワーク(または、その使用方法)に問題があるようです。つまり、asp.net mvc はリクエストから値を読み取ることはできますが、苦情者のコレクションを初期化することはできません。

ここ それは書いてあります:

InitializeManyCollection(TTargetEntity) メソッドは、既定のコンストラクターを使用して作成された既存の EntityCollection(TEntity) を初期化します。EntityCollection(TEntity) は、指定された関係名とターゲット ロール名を使用して初期化されます。

InitializeManyCollection(TTargetEntity) メソッドは、逆シリアル化中にのみ使用されます。

さらに詳しい情報:

例外:

  • 無効な操作例外

条件:

  • 提供されたEntityCollection(TEntity)がすでに初期化されている場合。
  • リレーションシップ マネージャーが既に ObjectContext にアタッチされている場合。
  • リレーションシップ マネージャーに、この名前とターゲット ロールとのリレーションシップが既に含まれている場合。

どういうわけか、InitializeManyCollection が 2 回起動されます。残念なことに、具体的になぜなのか、私には明るいアイデアがありませんでした。おそらく、この小さな調査は、EF の経験が豊富な他の人にとって役立つでしょう。:)

編集3:
これはこの特定の問題に対する解決策ではなく、むしろ回避策であり、mvc のモデル部分を処理する適切な方法です。

プレゼンテーションのみを目的としてビューモデルを作成します。純粋な POCO からも新しいドメイン モデルを作成します (EF は次のバージョンでのみサポートするため)。使用 オートマッパー EFDataContext<=>Model<=>ViewModel をマップします。

多少の労力はかかりますが、そのように対処する必要があります。このアプローチでは、モデルからプレゼンテーション責任が削除され、ドメイン モデルがクリーンアップされ (モデルから EF 要素が削除され)、バインディングの問題が解決されます。

他のヒント

public ActionResult Edit([Bind(Exclude = "Complainants")]Complaint model)
{
  TryUpdateModel(model.Complainants, "Complainants");
  if (!ModelState.IsValid)
  {
      // return the pre populated model
      return View(model);
  }

}

これは、私の作品!

私は苦情オブジェクトが作成されるとき、その「申立」コレクションが(理由は、エンティティフレームワークの自動ロジックの)初期化されると、その後、モデルバインダーは、エラーの原因となるだけでなく、コレクション自体を作成しようと思います。 我々は、手動でモデルを更新しようとすると、しかし、その後のコレクションはすでに初期化されますが、モデルバインダーは再びそれを初期化するように要求されていません。

これはケース・バイ・ケースなしで仕事を得るためには、あなたがあなた自身のモデルバインダーとオーバーライドメソッドてSetPropertyを作成する必要が回避策:

public class MyDefaultModelBinder : DefaultModelBinder
{
    protected override void SetProperty(ControllerContext controllerContext, ModelBindingContext bindingContext, PropertyDescriptor propertyDescriptor, object value)
    { 
        ModelMetadata propertyMetadata = bindingContext.PropertyMetadata[propertyDescriptor.Name]; 
        propertyMetadata.Model = value;
        string modelStateKey = CreateSubPropertyName(bindingContext.ModelName, propertyMetadata.PropertyName);

        // Try to set a value into the property unless we know it will fail (read-only 
        // properties and null values with non-nullable types)
        if (!propertyDescriptor.IsReadOnly) { 
        try {
            if (value == null)
            {
            propertyDescriptor.SetValue(bindingContext.Model, value);
            }
            else
            {
            Type valueType = value.GetType();

            if (valueType.IsGenericType && valueType.GetGenericTypeDefinition() == typeof(EntityCollection<>))
            {
                IListSource ls = (IListSource)propertyDescriptor.GetValue(bindingContext.Model);
                IList list = ls.GetList();

                foreach (var item in (IEnumerable)value)
                {
                list.Add(item);
                }
            }
            else
            {
                propertyDescriptor.SetValue(bindingContext.Model, value);
            }
            }

        }
        catch (Exception ex) {
            // Only add if we're not already invalid
            if (bindingContext.ModelState.IsValidField(modelStateKey)) { 
            bindingContext.ModelState.AddModelError(modelStateKey, ex); 
            }
        } 
        }
    }
}

Global.asaxの中であなたのバインダを登録することを忘れないでください。

ModelBinders.Binders.DefaultBinder = new MyDefaultModelBinder();

私は次のようにしてModelBinding例外を中心に働いていました

// Remove the error from ModelState which will have the same name as the collection.
ModelState.Remove("Complaints"/*EntityCollection*/); 
if (ModelState.IsValid) // Still catches other errors.
{
    entities.SaveChanges(); // Your ObjectContext
}

主な欠点は、例外がスローされ、まだ、それは実行時に高価なことができるということです。エレガントな回避策は、既存のDefaultBinderのラッパーを作成して、すでにEFによって行われた、再びEntityCollectionをインスタンス化を防止することです。そして、フォームの値(FormCollection)にそのコレクションを結合。

あなたが複数のコレクションを結合している場合、あなたはそれぞれのコレクションのためのエラーを削除する必要があります覚えておいてください。

は、私の実験では、コレクションはコレクションはの一部であったグラフでルートオブジェクトと同様に正常に保存されます。

他の誰かを助け

希望ます。

私は同じ問題を抱えていました!最後に、あなたは、フレームワークは、複雑なモデルを扱うことができないということがわかります。

私はポストに複雑なバインディングを初期化することができます少しバインディングコンポーネントを書いています。

しかし、基本的に何をしなければならないことはアーニスL.は言っているものです。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top