首页 > 解决方案 > 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();            
}

存储库:

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>

调试器:

调试器

生成的 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>

标签: c#asp.net-coretag-helpers

解决方案


正如我在评论中已经写的那样,选择标签助手将使用该asp-for元素绑定到视图模型。这意味着指定属性中模型的值用于匹配标签助手的可用项的值,以确定当前选择了哪些项。无论SelectListItemSelected属性设置为什么,此过程都有效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模型中 的当前值恰好等于ValueSelectListItem。这正是它使用的逻辑。

Selected与在 中使用属性相比,这种方法有一个巨大的好处SelectListItem: 现在,当您提交表单时发送的数据非常清楚。由于 selected 的值option是正在发送的内容,并且name标签select的 是该值所用于的键,因此ProjectId=value-3现在提交表单将有效地发送。

然后,当该模型被绑定为控制器中 POST 操作的一部分时,该值可以正确反序列化为ProjectId模型的属性:

[HttpPost]
public IActionResult Edit(TaskEditViewModel model)
{
    var selectedProject = model.ProjectId; // "value-3"
    // …
}

推荐阅读