SelectTagHelper не отображает выбранный элемент без явного свойства Id в модели
Я пытаюсь создать выпадающий список в MVC Core (2.2) для действия Edit.
Я хочу использовать Enumerable из SelectListItems в качестве источника данных без добавления дополнительного свойства в мою ViewModel для хранения текущих выбранных значений, поскольку они уже хранятся в SelectListItems.
Я использовал настроенный SelectTagHelper (который удалил множественный атрибут), который, по моему мнению, мог быть проблемой, но я протестировал со стандартным SelectTagHelper, и я не могу найти способ захватить выбранный элемент (ы) из SelectListItems.
Есть ли способ заставить работать SelectTagHelper по умолчанию, как это, или мне нужно расширить пользовательский SelectTagHelper?
Я попытался с SelectList и IEnumerable из SelectListItems, и ни один из них не работает так, как я думал, было бы возможно.
При отладке элементы возвращаются правильно (один из которых имеет значение true в примере ниже). Я предполагаю, что в стандартном SelectTagHelper есть что-то, что препятствует анализу выбранного элемента из коллекции Items.
Код следующим образом:
Модель (с SelectListItems):
public class TaskEditViewModel
{
public int Id { get; set; }
public string Description { get; set; }
public IEnumerable<SelectListItem> ProjectId { get; set; }
public string Week { get; set; }
}
контроллер:
[HttpGet]
public IActionResult Edit(int Id)
{
var model = _repo.GetTaskEdit(Id);
if (model != null)
{
return View(model);
}
return NotFound();
}
Repository:
public TaskEditViewModel GetTaskEdit(int Id)
{
var query = _context.Task.Where(t => t.Id == Id);
var model = query
.ProjectTo<TaskEditViewModel>(_mapper.ConfigurationProvider)
.Single();
if (model != null)
{
var selected = query.First().ProjectId; //gets currently selected value as int
var list = _context.Project.Select(x => new SelectListItem(x.Name, x.Id.ToString(), true ? x.Id == selected : false));
model.ProjectId = list;
return model;
}
return null;
}
HTML:
<div class="form-group">
<label asp-for="ProjectId" class="control-label"></label>
<selectOne asp-for="ProjectId" class="form-control" asp-items="Model.ProjectId"></selectOne>
<span asp-validation-for="ProjectId" class="text-danger"></span>
</div>
Debugger:
Сгенерированный HTML:
<div class="form-group">
<label class="control-label" for="ProjectId">Project</label>
<select class="form-control" data-val="true" data-val-required="Please select a project." id="ProjectId" multiple="multiple" name="ProjectId">
<option value="1">7 Day SAT</option>
<option value="2">UEC</option>
</select>
<span class="text-danger field-validation-valid" data-valmsg-for="ProjectId" data-valmsg-replace="true"></span>
</div>
2 ответа
Я решил расширить пользовательский TagHelper, который я использовал, чтобы покрыть это, поскольку я хотел предоставить один TagHelper, чтобы охватить как Create, так и Edit экземпляры ввода Select.
Этот пользовательский TagHelper удаляет множественный атрибут и создает список параметров с выбранным значением, взятым из элементов SelectListItems, переданных через модель.
TagHelper:
[HtmlTargetElement("selectOne", Attributes = "asp-for")]
public class SingleSelectTagHelper : SelectTagHelper
{
public SingleSelectTagHelper(IHtmlGenerator generator)
: base (generator)
{
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
base.Process(context, output);
output.TagName = "select";
var index = output.Attributes.IndexOfName("multiple");
output.Attributes.RemoveAt(index);
output.PreContent.AppendHtml("<option value=\"\">Please select an option</option>");
output.PostContent.Reinitialize();
foreach(var item in Items)
{
if(item.Selected)
{
output.PostContent.AppendHtml($"<option selected='selected' value=" + item.Value + ">" + item.Text + "</option>");
}
else
{
output.PostContent.AppendHtml($"<option value=" + item.Value + ">" + item.Text + "</option>");
}
}
}
}
Использование:
<div class="form-group">
<label asp-for="ProjectId" class="control-label"></label>
<selectOne asp-for="ProjectId" class="form-control" asp-items="Model.ProjectId"></selectOne>
<span asp-validation-for="ProjectId" class="text-danger"></span>
</div>
Представляет как:
<div class="form-group">
<label class="control-label" for="ProjectId">Project</label>
<select class="form-control" data-val="true" data-val-required="Please select a project." id="ProjectId" name="ProjectId">
<option value="">Please select an option</option>
<option value=1>Option 1</option>
<option selected='selected' value=2>Option 2</option>
</select>
<span class="text-danger field-validation-valid" data-valmsg-for="ProjectId" data-valmsg-replace="true"></span>
</div>
Как я уже писал в комментарии, помощник по тегу select будет использовать asp-for
элемент для привязки к представлению модели. Это означает, что значение модели в указанном свойстве используется для сопоставления со значением доступных элементов помощника по тегам, чтобы определить, какие элементы выбраны в данный момент. Этот процесс работает независимо от того, что SelectListItem
имеет свой Selected
свойство установлено в true
,
Это означает, что если вы не используете asp-for
Тогда вы можете полностью получить эту работу так, как вам нравится:
// in the controller action
return View(new TaskEditViewModel
{
ProjectId = new List<SelectListItem>()
{
new SelectListItem { Text = "Item 1", Value = "value-1" },
new SelectListItem { Text = "Item 2", Value = "value-2", Selected = false },
new SelectListItem { Text = "Item 3", Value = "value-3" },
new SelectListItem { Text = "Item 4", Value = "value-4" },
},
});
// in the view
<select class="form-control" asp-items="Model.ProjectId"></select>
Если вы выполните это, это будет обработанный вывод:
<select class="form-control">
<option value="value-1">Item 1</option>
<option value="value-2">Item 2</option>
<option selected="selected" value="value-3">Item 3</option>
<option value="value-4">Item 4</option>
</select>
Это только если вы добавите asp-for
что это перестанет работать.
<select class="form-control" id="ProjectId" multiple="multiple" name="ProjectId">
<option value="value-1">Item 1</option>
<option value="value-2">Item 2</option>
<option value="value-3">Item 3</option>
<option value="value-4">Item 4</option>
</select>
Это потому, что теперь форма отображает элемент управления формы для свойства, представляющего собой список чего-либо. Таким образом, помощник по тегам предполагает, что он должен отображать выборку, допускающую несколько выборок. Кроме того, логика теперь будет пытаться соответствовать SelectListItem.Value
со значением Model.ProjectId
и только используйте это, чтобы определить, выбрано ли что-то.
Как отмечается в комментариях, вы обычно не используете здесь вспомогательный тег select. Вместо этого у вас будет отдельное свойство для выбранного значения и отдельное свойство для доступных элементов:
public class TaskEditViewModel
{
// …
public string ProjectId { get; set; }
public IEnumerable<SelectListItem> AvailableProjects { get; set; }
}
// in the controller
return View(new TaskEditViewModel
{
ProjectId = "value-3",
AvailableProjects = new List<SelectListItem>()
{
new SelectListItem { Text = "Item 1", Value = "value-1" },
new SelectListItem { Text = "Item 2", Value = "value-2" },
new SelectListItem { Text = "Item 3", Value = "value-3" },
new SelectListItem { Text = "Item 4", Value = "value-4" },
},
});
// in the view
<select asp-for="ProjectId" class="form-control" asp-items="Model.AvailableProjects"></select>
Теперь это HTML-код, который вы получите:
<select class="form-control" id="ProjectId" name="ProjectId">
<option value="value-1">Item 1</option>
<option value="value-2">Item 2</option>
<option selected="selected" value="value-3">Item 3</option>
<option value="value-4">Item 4</option>
</select>
Обратите внимание, что пункт 3 теперь неявно выбран, хотя его SelectListItem
не было Selected
набор свойств. Это потому, что текущая стоимость ProjectId
в модели случается равным Value
из SelectListItem
, И это именно та логика, которая используется.
Этот подход имеет огромное преимущество по сравнению с Selected
недвижимость в SelectListItem
Теперь очень ясно, какие данные отправляются при отправке формы. Поскольку значение выбрано option
это то, что отправляется, и name
из select
тег является ключом, для которого используется значение, отправка формы будет эффективно отправлять ProjectId=value-3
сейчас.
И когда эта модель затем связывается как часть действия POST в контроллере, это значение можно правильно десериализовать в ProjectId
свойство вашей модели:
[HttpPost]
public IActionResult Edit(TaskEditViewModel model)
{
var selectedProject = model.ProjectId; // "value-3"
// …
}