ASP.NET MVC-モデルがカスタムViewModelにラップされていない場合、フォームはNullモデルを返します
-
10-07-2019 - |
質問
アプリケーションには、モデル項目の1つに対して同じエディターテンプレートを表示するビューのペアがあります。 2つのビュー("追加"と"編集")、"編集"正常に動作しますが、「追加」コントローラーアクションが投稿を処理するときにモデルにnullを返します。
「追加」を与えると、 Modelオブジェクト全体でEditorFor()を呼び出すのではなく、カスタムViewModelを表示して Html.EditorFor(p => p.PageContent)
を呼び出します- Html.EditorFor(p => p)
の場合、フォームは正しい非nullモデルを返しますが、クライアント側のスクリプティングIDとコントロールIDに関連する他の問題が発生します(現在、すべてのフィールドには" PageContent_"が付いています)。私はアプリケーション全体のいくつかの異なる場所で同じEditor Templateテクニックを使用していますが、ViewModelにこの奇妙な依存関係を示しているものはありません。
他の誰かが同様の問題を経験したことはありますか?
ビューの編集
<%@ Page Title="" Language="C#" MasterPageFile="~/Areas/Admin/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<PageContent>" %>
<% using (Html.BeginForm())
{ %>
<%=Html.Hidden("PageID", Model.Page.ID) %>
<%=Html.EditorFor(p => p)%>
<input type="submit" name="btnSave" value="Save" />
<input type="submit" name="btnCancel" value="Cancel" class="cancel" />
<% }
アクション(動作中)
[HttpPost, ValidateInput(false)]
public ActionResult EditContent(int id, FormCollection formCollection) {}
ビューを追加
<%@ Page Title="" Language="C#" MasterPageFile="~/Areas/Admin/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<PageContent>" %>
<% using (Html.BeginForm())
{ %>
<%=Html.Hidden("PageID", ViewData["PageID"]) %>
<%=Html.EditorFor(p => p)%>
<input type="submit" name="btnSave" value="Save" />
<input type="submit" name="btnCancel" value="Cancel" class="cancel" />
<% } %>
アクション(失敗)
// content is ALWAYS null
[HttpPost, ValidateInput(false)]
public ActionResult AddContent(PageContent content, FormCollection formCollection) {}
泣く前に&quot;複製&quot;
この質問はこれに関連していますが、この質問より一般的な質問ではなく、私が経験している特定の問題を対象としています。
解決
問題を追跡しましたが、かなり興味深い問題です。
DefaultModelBinderがモデルアイテムを解決しようとするとき、最初に行うことの1つは、バインドされているデータにプレフィックスフィールドがあるかどうかを確認することです。これは、モデルオブジェクトの名前で始まるフォームアイテムをチェックすることで行われます(私に尋ねると、これは非常にarbitrary意的のようです)。 「接頭辞付き」がある場合フィールドが見つかると、異なるバインディングロジックが呼び出されます。
ASP.NET MVC 2 Preview 2 BindModel()Source
public virtual object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext) {
if (bindingContext == null) {
throw new ArgumentNullException("bindingContext");
}
bool performedFallback = false;
if (!String.IsNullOrEmpty(bindingContext.ModelName) && !DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, bindingContext.ModelName)) {
// We couldn't find any entry that began with the prefix. If this is the top-level element, fall back
// to the empty prefix.
if (bindingContext.FallbackToEmptyPrefix) {
/* omitted for brevity */
};
performedFallback = true;
}
else {
return null;
}
}
// Simple model = int, string, etc.; determined by calling TypeConverter.CanConvertFrom(typeof(string))
// or by seeing if a value in the request exactly matches the name of the model we're binding.
// Complex type = everything else.
if (!performedFallback) {
/* omitted for brevity */
}
if (!bindingContext.ModelMetadata.IsComplexType) {
return null;
}
return BindComplexModel(controllerContext, bindingContext);
}
Addアクションを処理するために定義したコントローラーアクションは、&quot; content&quot;と呼ばれるPageContentアイテムを定義します。私のドメインにはPageContentに&quot; Content&quot;というプロパティがあります「一致」したものモデル名が「content」の場合したがって、DefaultModelBinderは、実際には単なるPageContentのメンバーであるにもかかわらず、接頭辞付きの値を持っていると想定します。署名を変更することで
from:
[HttpPost, ValidateInput(false)]
public ActionResult AddContent(PageContent content, FormCollection formCollection) {}
宛先:
[HttpPost, ValidateInput(false)]
public ActionResult AddContent(PageContent pageContent, FormCollection formCollection) {}
DefaultModelBinderは、再びPageContentモデルアイテムに正しくバインドできました。編集ビューにもこの動作が表示されなかった理由はわかりませんが、どちらにしても問題の原因を突き止めました。
この問題は「バグ」に非常に近いようです。状態。 「コンテンツ」が最初にViewModelで機能するのは理にかなっています。 &quot; PageContent_&quot;という接頭辞が付けられていましたが、このようなコアフレームワークの機能/バグは修正されていないはずです。