#asp.net-mvc #model-binding
#asp.net-mvc #привязка модели
Вопрос:
- Visual Studio 2012
- MVC 5.2.0
- jQuery 2.1.1
- Объединенный пользовательский интерфейс jQuery 1.10.4
- Json.Net 6.0.3
- Kendo UI MVC 2014.1.528
Я прочитал много похожих сообщений. Я пытался применить то, что я видел, но безрезультатно. Я бросаюсь к ногам ваших клавиатур для вашего милосердия и помощи.
У меня есть сайт, похожий на опрос, поэтому для простоты я составил пример проекта, чтобы дублировать проблему. Я тоже могу загрузить это.
У меня есть модель с дочерним объектом Address — там проблем нет — она привязывается.
В модели также есть коллекция вопросов. Она никогда не привязывается к post, и в этом проблема здесь.
Давайте начнем с моделей:
Сам опрос:
public class Survey
{
[ScaffoldColumn(false)]
public int Id { get; set; }
public string Name { get; set; }
[DisplayName("Phone #")]
[StringLength(15)]
[DataType(DataType.PhoneNumber)]
public string ContactMethodPhone { get; set; }
[DisplayName("Email")]
[StringLength(120)]
[EmailAddress]
public string ContactMethodEmail { get; set; }
public virtual Address Address { get; set; }
public virtual List<Question> Questions { get; set; }
}
и адрес:
public class Address
{
[ScaffoldColumn(false)]
public int AddressId { get; set; }
[DisplayName("Address line 1")]
[Required]
[StringLength(200)]
public string Street { get; set; }
[DisplayName("Address line 2")]
[StringLength(200)]
public string Street2 { get; set; }
[DisplayName(" ")]
[StringLength(50)]
public string ApartmentNum { get; set; }
[DisplayName("Type")]
[StringLength(50)]
public string Tenement { get; set; }
[DisplayName("City")]
[Required]
[StringLength(200)]
public string City { get; set; }
[DisplayName("Province/State")]
[StringLength(20)]
public string State { get; set; }
[Required]
[DisplayName("Postal/Zip Code")]
[StringLength(10)]
public string MailCode { get; set; }
[DisplayName("Country")]
[StringLength(30)]
public string Country { get; set; }
[NotMapped]
public List<SelectListItem> Cities { get; set; }
[NotMapped]
public List<SelectListItem> Provinces { get; set; }
[NotMapped]
public List<SelectListItem> Countries { get; set; }
[NotMapped]
public List<SelectListItem> MailCodes { get; set; }
[NotMapped]
public List<SelectListItem> Tenements { get; set; }
}
и вопросы:
public class Question
{
[ScaffoldColumn(false)]
public int Id { get; set; }
[ScaffoldColumn(false)]
public int ImageID { get; set; } // Question Image
[DisplayName("Question: ")]
public string InformationIntakeGroupValue { get; set; } // Question: ie Did you graguate high school
public int ImageID_Topic { get; set; } // Topic Image
[DisplayName("Topic: ")]
public string InformationIntakeTopicValue { get; set; } // Topic: ie Education
[ScaffoldColumn(false)]
public string InformationIntakeTypeCode { get; set; } // Type of question (date, bool, text)
// below not filled by select;
// the key from the EntityAffilliateIntake record insert
public int PersonId { get; set; } // Person anwering question
// this is the user response area
[DisplayName("Answer: ")]
public string InformationIntakeValue { get; set; }
[DisplayName("Choice: ")]
public string InformationIntakeValueBool { get; set; }
[DisplayName("Date: ")]
public DateTime InformationIntakeValueDate { get; set; }
[ForeignKey("Survey")]
public int SurveyId { get; set; }
public virtual Survey Survey { get; set; }
}
(Note: fyi, I’ve tried the models without foreign keys as well — but perhaps it’s not defined correctly)
The controller :
// GET: /Inquiry/Create
public ActionResult Create()
{
var geoIpData = Strings._download_serialized_json_data<GeoData>(StringConstants.UrlForGeoService);
SurveyModel = new Survey
{
Id=1,
Address = new AddressController().GetAddressModel(geoIpData.country, geoIpData.regionName, geoIpData.city, ""),
Questions = new QuestionController().GetQuestions(1).ToList()
};
return View(SurveyModel);
}
// POST: /Inquiry/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
public ActionResult Create([Bind(Include = "Id, Name, ContactMethodPhone, ContactMethodEmail, Address, Questions")] Survey survey)
{
if (ModelState.IsValid)
{
int i = 0;
}
if (survey.Address.Cities == null)
{
survey.Address.Cities = SurveyModel.Address.Cities;
survey.Address.Countries = SurveyModel.Address.Countries;
survey.Address.MailCodes = SurveyModel.Address.MailCodes;
survey.Address.Provinces = SurveyModel.Address.Provinces;
survey.Address.Tenements = SurveyModel.Address.Tenements;
}
if (survey.Questions == null)
{
survey.Questions = SurveyModel.Questions;
}
return View(survey);
}
The view:
@model BindCollection.Models.Survey
@{
ViewBag.Title = "Create";
}
<h2>Create</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
// I use <div class="container">, <fieldset> and <div class="row">
// instead of <div class="form-horizontal"> and <div class="form-group">
<div class="container">
<fieldset>
<legend></legend>
<h4>Survey</h4>
<hr />
@Html.ValidationSummary(true)
@Html.HiddenFor(model => model.Id)
<div class="row">
@Html.LabelFor(model => model.Name, new { @class = "control-label col-md-2 col-md-2 col-lg-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Name)
@Html.ValidationMessageFor(model => model.Name)
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
Your address information:<br />
<br />
</div>
</div>
@* no problem here with address *@
@{ var vddAddress = new ViewDataDictionary(ViewData) { TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "Address" } };}
@Html.Partial("_AddressPartial", Model.Address, @vddAddress)
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
How we can contact you? :<br />
<br />
</div>
</div>
<div class="row">
@Html.LabelFor(model => model.ContactMethodPhone, new { @class = "control-label col-sm-2 col-md-2 col-lg-2" })
<div class="col-sm-10 col-md-10 col-lg-10">
@Html.EditorFor(model => model.ContactMethodPhone)
@Html.ValidationMessageFor(model => model.ContactMethodPhone)
</div>
</div>
<div class="row">
@Html.LabelFor(model => model.ContactMethodEmail, new { @class = "control-label col-sm-2 col-md-2 col-lg-2" })
<div class="col-sm-10 col-md-10 col-lg-10">
@Html.EditorFor(model => model.ContactMethodEmail)
@Html.ValidationMessageFor(model => model.ContactMethodEmail)
</div>
</div>
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
Some Questions<br />
<br />
</div>
</div>
@*Here is the evil one! Beware!*@
@{ var vddQuestions = new ViewDataDictionary(ViewData) { TemplateInfo = new TemplateInfo() { HtmlFieldPrefix = "Questions" } };}
@Html.Partial("_QuestionsPartial", Model.Questions, @vddQuestions)
<hr />
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</fieldset>
</div>
}
The address partial view is insignificant as there’s no problem there.
Here’s the partial view for the questions:
@model IEnumerable<BindCollection.Models.Question>
@using BindCollection
@using Kendo.Mvc.UI
@{
string CurrentTopic = string.Empty;
bool FirstTime = true;
}
@foreach (var item in Model)
{
if (CurrentTopic != item.InformationIntakeTopicValue)
{
CurrentTopic = item.InformationIntakeTopicValue;
if (!FirstTime)
{
FirstTime = false;
item.InformationIntakeTopicValue = string.Empty;
}
}
else
{
item.InformationIntakeTopicValue = string.Empty;
}
@Html.EditorFor(m=>item, "Question")
<br />
<br />
}
and, of course, I made an EditorTemplate for a question, as you can see a few lines above…
@model BindCollection.Models.Question
@Html.HiddenFor(m=>m.Id)
@{if (!string.IsNullOrWhiteSpace(Model.InformationIntakeTopicValue))
{
<hr />
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
<br />
<br />
<h4>
@Html.DisplayFor(m => m.InformationIntakeTopicValue, new { @class = "control-label col-sm-10 col-md-10 col-lg-10" })
</h4>
<br />
</div>
</div>
}}
@*type of value to ask for is denoted by item.InformationIntakeTypeCode*@
<div class="row">
<div class="control-label col-sm-9 col-md-9 col-lg-9">
@Html.DisplayFor(m => m.InformationIntakeGroupValue, null)
</div>
</div>
<div class="row">
<div class="col-sm-12 col-md-12 col-lg-12">
@{
var outputBool = false;
var outputDate = false;
var outputText = false;
if(Model.InformationIntakeTypeCode.ToLower().Contains("date") )
{
outputDate = true;
}
else if (Model.InformationIntakeTypeCode.ToLower().Contains("bool"))
{
outputBool = true;
}
else if (Model.InformationIntakeTypeCode.ToLower().Contains("string"))
{
outputText = true;
}
}
@if(outputBool)
{
@(Html.Kendo().DropDownListFor(m => m.InformationIntakeValueBool)
.HtmlAttributes(new { style ="width: 100px;", id="InformationIntakeValueBool" Model.Id.ToString() })
.DataTextField("Text")
.DataValueField("Value")
.BindTo(new List<SelectListItem>() {
new SelectListItem() {
Text = "Please Select...",
Value = "0"
},
new SelectListItem() {
Text = "Yes",
Value = "true"
},
new SelectListItem() {
Text = "No",
Value = "false"
}
}).Value("0")
)
}
@if(outputDate)
{
@(Html.Kendo().DatePickerFor(m => m.InformationIntakeValueDate)
.HtmlAttributes(new { style = "width: 100px;", id="InformationIntakeValueDate" Model.Id.ToString() })
)
}
@if (outputText)
{
@Html.Kendo().AutoCompleteFor(m => m.InformationIntakeValue).HtmlAttributes(new { style = "width: 80%;", id="InformationIntakeValue" Model.Id.ToString()})
}
</div>
</div>
So… When I POST, an odd thing occurs. All form values are passed, but the ones for Questions look strange:
Id 1
Name sally
Address.Street 123 Avenue Steet
Address.Street2 Building C
Address.Tenement Suite
Address.ApartmentNum 111
Address.City_input Sarnia
Address.City Sarnia
Address.State_input Ontario
Address.State Ontario
Address.Country_input Canada
Address.Country Canada
Address.MailCode_input N6B 2K0
Address.MailCode N6B 2K0
ContactMethodPhone 555-555-5555
ContactMethodEmail r@r.com
Questions.item.Id 1
Questions.item.InformationIntakeValueBool true
Questions.item.Id 2
Questions.item.InformationIntakeValueDate 2/4/2014
Questions.item.Id 3
Questions.item.InformationIntakeValue Speckled
Questions.item.Id 4
Questions.item.InformationIntakeValueBool true
Questions.item.Id 5
Questions.item.InformationIntakeValue Lightly Toasted
Questions.item.Id 7
Questions.item.InformationIntakeValueBool true
Questions.item.Id 8
Questions.item.InformationIntakeValue Nothing!
Questions.item.Id 6
Questions.item.InformationIntakeValueBool true
Questions.item.Id 9
Questions.item.InformationIntakeValueDate 6/29/2014
Я подумал, как я видел в других сообщениях, что элементы вопроса должны выглядеть следующим образом:
Questions[0].Id 1
Questions[0].InformationIntakeValueBool true
Questions[1].Id 2
Questions[1].InformationIntakeValueDate 2/4/2014
Questions[2].Id 3
Questions[2].InformationIntakeValue Speckled
Итак, я не уверен, почему моя выглядит так.
На стороне сервера запрос показывает только одну переменную для каждого:
Request.Form.Results View Expanding the Results View will enumerate the IEnumerable
[0] "__RequestVerificationToken" object {string}
[1] "Id" object {string}
[2] "Name" object {string}
[3] "Address.Street" object {string}
[4] "Address.Street2" object {string}
[5] "Address.Tenement" object {string}
[6] "Address.ApartmentNum" object {string}
[7] "Address.City_input" object {string}
[8] "Address.City" object {string}
[9] "Address.State_input" object {string}
[10] "Address.State" object {string}
[11] "Address.Country_input" object {string}
[12] "Address.Country" object {string}
[13] "Address.MailCode_input" object {string}
[14] "Address.MailCode" object {string}
[15] "ContactMethodPhone" object {string}
[16] "ContactMethodEmail" object {string}
[17] "Questions.item.Id" object {string}
[18] "Questions.item.InformationIntakeValueBool" object {string}
[19] "Questions.item.InformationIntakeValueDate" object {string}
[20] "Questions.item.InformationIntakeValue" object {string}
Смотрите последние 4 элемента? Как насчет других записей?
Я предполагаю, что есть что-то странное с ViewDataDictionary, который я отправляю на частичный просмотр вопроса.
Любая помощь была бы оценена. Это должно быть простым…
Комментарии:
1. Всякий раз, когда я вижу вопрос, в котором говорится «модель не привязана к post», я думаю: «Держу пари, он использует частичное представление или foreach. в 95% случаев я прав. Вы используете оба.
2. хорошо … и … никто не должен использовать частичные представления? Я неправильно ее использую? Это интересное замечание, но не очень полезное. Я использую частичные представления, чтобы я мог повторно использовать эту логику в другом месте. Частичное представление моего адреса используется во многих формах. Мне больше не нужно думать об этом. Все адреса собираются одинаковым образом. Это работает.
3. Да, и вам придется возиться с HtmlPrefix, чтобы заставить его работать. Проще просто использовать EditorTemplate, который делает это за вас.
4. Посмотрите, пожалуйста, выше — я использую шаблон редактора.
5. Ты очень расстраивающий человек. Вы не используете шаблон редактора в тех частях, с которыми у вас возникли проблемы. В этом суть. Вы используете циклы partials и foreach. Используйте шаблоны редактора вместо partial и используйте шаблоны редактора вместо использования foreach, потому что EditorFor автоматически выполнит цикл по коллекции и вызовет ваш шаблон редактора для каждого элемента.
Ответ №1:
Ни частичные представления, ни инструкции foreach не содержат необходимой информации для правильной привязки коллекций. Я всегда использую EditorTemplates для всего. Тем более, что EditorTemplates автоматически выполняет итерацию по коллекции.
Однако, если вы привязаны и полны решимости использовать цикл, тогда используйте цикл for, а затем проиндексируйте модель. В вашем случае:
@for(int i = 0; i < Model.Count; i )
{
if (CurrentTopic != Model[i].InformationIntakeTopicValue)
...
@Html.EditorFor(m => Model[i]) // don't have to specify the template name
// since it's the same name as the type
}
Тем не менее, я бы просто сделал это:
На ваш взгляд, сделайте это:
@*Here is the evil one! Beware!*@
@Html.EditorFor(m => m.Questions)
Тогда ваш вопрос.cshtml, как обычно, который будет автоматически повторен редактором для
Однако в начале вопроса.cshtml (после объявления модели) добавьте следующий код, это все, что необходимо для достижения того, что вы пытаетесь сделать. Вам вообще не нужен частичный просмотр.
@{
if (ViewBag.CurrentTopic != Model.InformationIntakeTopicValue)
{
ViewBag.CurrentTopic = Model.InformationIntakeTopicValue;
}
else
{
Model.InformationIntakeTopicValue = string.Empty;
}
}
Комментарии:
1. Я использую EditorTemplate — я пытался просто использовать EditorFor без имени шаблона (того же имени, что и тип), и это не сработало. Он просто попытался создать свой собственный шаблон на основе моей модели. Мне пришлось указать имя шаблона.
2. Я постараюсь сделать это вообще без частичного просмотра, с вашим модом для моей пустой логики темы.
3. Если это не вызывает ваш шаблон редактора, то для этого есть причина. Либо вы не размещаете ее в нужном месте, либо происходит что-то еще.
4. Мой шаблон редактора выполняет итерацию, но мне пришлось принудительно использовать уникальные идентификаторы элементов управления для библиотеки пользовательского интерфейса Kendo, чтобы подключить ее скрипты. Мне интересно, не портит ли это, в свою очередь, привязку? ‘Name’ всегда будет одним и тем же, но мне нужно заставить идентификаторы быть уникальными: см. id=»InformationIntakeValueBool» Model.Id.toString() и т.д. Выше.
5. Приложение нашло все другие шаблоны редактора (дата / Электронная почта / и т.д.) В каталоге Views Shared EditorTemplates, но не соответствующий редактор для моего типа. Я посмотрю поближе на случай, если я опечатался — завтра я загружу свое тестовое решение.