SelectTagHelper 不显示模型中没有显式 ID 属性 的所选项目
SelectTagHelper not displaying selected item without explicit Id property in Model
我正在尝试在 MVC Core (2.2) 中为编辑操作创建下拉列表。
我想使用 SelectListItems 的 Enumerable 作为我的数据源,而不向我的 ViewModel 添加额外的 属性 来保存当前选择的值,因为这些值已经存储在 SelectListItems 中。
我使用的是自定义的 SelectTagHelper(删除了 multiple 属性),我认为这可能是问题所在,但我已经使用标准 SelectTagHelper 进行了测试,但我找不到捕获所选项目的方法来自 SelectListItems。
有没有办法让默认的 SelectTagHelper 像这样工作,或者我是否需要扩展自定义的 SelectTagHelper?
我尝试过使用 SelectList 和 SelectListItems 的 IEnumerable,但都没有按照我想象的方式工作。
调试时,项目正确返回(在下面的示例中有一个选择值为 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>
正如我在评论中所写,select 标签助手将使用 asp-for
元素绑定到视图模型。这意味着指定 属性 中模型的值用于匹配标签助手的可用项目的值,以确定当前 selected 的项目。无论 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>
这是因为现在,表单正在呈现 属性 的表单控件,这是一个列表。所以标签助手假定它必须显示一个 select 允许 多个 select 离子。此外,该逻辑现在将尝试将 SelectListItem.Value
与 Model.ProjectId
的值相匹配,并且仅使用它来确定某些内容是否被 selected.
如评论中所述,这不是您通常在这里使用 select 标签助手的方式。相反,您将有一个单独的 属性 用于 selected 值 和一个单独的 属性 用于可用项目:
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
属性,但现在已隐式 selected。这是因为模型中 ProjectId
的当前值恰好等于 SelectListItem
的 Value
。这正是它使用的逻辑。
与在 SelectListItem
中使用 Selected
属性 相比,这种方法有一个巨大的好处:现在,提交表单时发送的数据非常清楚。由于 selected option
的值是要发送的内容,而 select
标记的 name
是该值用于的键,因此提交表单现在将有效地发送 ProjectId=value-3
。
然后当该模型被绑定为控制器中 POST 操作的一部分时,该值可以正确地反序列化到模型的 ProjectId
属性 中:
[HttpPost]
public IActionResult Edit(TaskEditViewModel model)
{
var selectedProject = model.ProjectId; // "value-3"
// …
}
我决定扩展我使用的自定义 TagHelper 来解决这个问题,因为我想提供一个 TagHelper 来涵盖 Select 输入的创建和编辑实例。
此自定义 TagHelper 删除了 multiple 属性,并使用从通过模型传递的 SelectListItems 中获取的选定值创建了一个选项列表。
标签助手:
[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>
我正在尝试在 MVC Core (2.2) 中为编辑操作创建下拉列表。
我想使用 SelectListItems 的 Enumerable 作为我的数据源,而不向我的 ViewModel 添加额外的 属性 来保存当前选择的值,因为这些值已经存储在 SelectListItems 中。
我使用的是自定义的 SelectTagHelper(删除了 multiple 属性),我认为这可能是问题所在,但我已经使用标准 SelectTagHelper 进行了测试,但我找不到捕获所选项目的方法来自 SelectListItems。
有没有办法让默认的 SelectTagHelper 像这样工作,或者我是否需要扩展自定义的 SelectTagHelper?
我尝试过使用 SelectList 和 SelectListItems 的 IEnumerable,但都没有按照我想象的方式工作。
调试时,项目正确返回(在下面的示例中有一个选择值为 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>
正如我在评论中所写,select 标签助手将使用 asp-for
元素绑定到视图模型。这意味着指定 属性 中模型的值用于匹配标签助手的可用项目的值,以确定当前 selected 的项目。无论 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>
这是因为现在,表单正在呈现 属性 的表单控件,这是一个列表。所以标签助手假定它必须显示一个 select 允许 多个 select 离子。此外,该逻辑现在将尝试将 SelectListItem.Value
与 Model.ProjectId
的值相匹配,并且仅使用它来确定某些内容是否被 selected.
如评论中所述,这不是您通常在这里使用 select 标签助手的方式。相反,您将有一个单独的 属性 用于 selected 值 和一个单独的 属性 用于可用项目:
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
属性,但现在已隐式 selected。这是因为模型中 ProjectId
的当前值恰好等于 SelectListItem
的 Value
。这正是它使用的逻辑。
与在 SelectListItem
中使用 Selected
属性 相比,这种方法有一个巨大的好处:现在,提交表单时发送的数据非常清楚。由于 selected option
的值是要发送的内容,而 select
标记的 name
是该值用于的键,因此提交表单现在将有效地发送 ProjectId=value-3
。
然后当该模型被绑定为控制器中 POST 操作的一部分时,该值可以正确地反序列化到模型的 ProjectId
属性 中:
[HttpPost]
public IActionResult Edit(TaskEditViewModel model)
{
var selectedProject = model.ProjectId; // "value-3"
// …
}
我决定扩展我使用的自定义 TagHelper 来解决这个问题,因为我想提供一个 TagHelper 来涵盖 Select 输入的创建和编辑实例。
此自定义 TagHelper 删除了 multiple 属性,并使用从通过模型传递的 SelectListItems 中获取的选定值创建了一个选项列表。
标签助手:
[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>