SelectTagHelper не отображает выбранный элемент без явного свойства Id в модели - PullRequest
0 голосов
/ 21 февраля 2019

Я пытаюсь создать выпадающий список в MVC Core (2.2) для действия Edit.

Я хочу использовать Enumerable of 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();            
}

Репозиторий:

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 ]

0 голосов
/ 25 февраля 2019

Я решил расширить пользовательский 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>
0 голосов
/ 22 февраля 2019

Как я уже писал в комментарии, помощник тега 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"
    // …
}
...