ASP.NET MVC - Form возвращает нулевую модель, если только модель не заключена в пользовательскую ViewModel
-
10-07-2019 - |
Вопрос
У меня есть пара представлений в моем приложении, которые отображают один и тот же шаблон редактора для одного из элементов моей модели;из двух представлений ("Добавить" и "Редактировать") "Редактировать" работает нормально, но "Добавить" возвращает null для модели, когда мое действие контроллера обрабатывает post.
Я обнаружил, что если я предоставлю виду "Добавить" пользовательскую ViewModel и вызову Html.EditorFor(p => p.PageContent)
вместо того, чтобы просто вызывать EditorFor() для всего объекта модели- Html.EditorFor(p => p)
, затем форма возвращает правильную, ненулевую модель, но это порождает другие проблемы, связанные с моими клиентскими сценариями и идентификаторами элементов управления (поскольку теперь все поля имеют префикс "PageContent_").Я использую одну и ту же технику шаблона редактора в нескольких разных местах по всему моему приложению, и ни один из других не демонстрирует этой странной зависимости от 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) {}
Прежде чем ты закричишь: "дублируй".
Этот вопрос действительно относится к этот, но этот вопрос предназначен для решения конкретной проблемы, с которой я сталкиваюсь, а не для более общего вопроса, заданного там.
Решение
Я разобрался с проблемой, и она довольно интересная.
Когда DefaultModelBinder пытается разрешить элемент модели, одно из первых, что он делает, это проверяет, есть ли какие-либо поля с префиксами в привязываемых данных;он делает это, проверяя наличие любых элементов формы, которые начинаются с имени объекта модели (это кажется чрезвычайно произвольный, если вы спросите меня).Если найдены какие-либо поля с "префиксом", то это приводит к вызову другой логики привязки.
ASP.NET MVC 2 Предварительный просмотр 2 Исходный код bindModel()
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);
}
Действие контроллера, которое я определил для обработки действия добавления, определяет элемент PageContent с именем "content", и в моем домене PageContent имеет свойство с именем "Content", которое "совпадает" с именем модели "content", что заставляет DefaultModelBinder предполагать, что у меня было значение с префиксом, хотя на самом деле это был просто элемент PageContent .Изменив подпись-
От:
[HttpPost, ValidateInput(false)]
public ActionResult AddContent(PageContent content, FormCollection formCollection) {}
Для:
[HttpPost, ValidateInput(false)]
public ActionResult AddContent(PageContent pageContent, FormCollection formCollection) {}
DefaultModelBinder снова смог корректно привязаться к элементу моей модели PageContent.Я не уверен, почему в режиме редактирования также не отображалось такое поведение, но в любом случае я отследил источник проблемы.
Мне кажется, что эта проблема очень близка к статусу "ошибка".Имеет смысл, что мое представление изначально работало с ViewModel, потому что к слову "content" добавлялся префикс "PageContent_", но подобная основная функция / ошибка фреймворка не должна оставаться без внимания, ИМХО.