从下拉列表中获取价值以在 asp.net 核心中建模
get value from the drowdown to model in asp.net core
我有一个带有 EntityFramework 核心的 .NET 5 应用程序,我尝试 link 下拉列表中的实体类型:
我有生意class
public class Bar : IdEntity
{
public BarType BarType { get; set; }
public class BarType : IdEntity
{
public int Id { get; set; }
public string Name { get; set; }
为了不直接在视图中使用Bar
,我创建了一个DTO(数据传输对象,或者只是一个viewModel),像这样:
public class BarDTO : IdEntityDTO
{
public int BarTypeId { get; set; }
public BarType BarType { get; set; }
我在控制器中做:
public class BarController : IdEntityController<Bar, BarDTO>
{
public override async Task<IActionResult> CreateAsync()
{
var barTypes=await _repository.ListAsync<BarType>();
ViewBag.BarTypes = barTypes;
return await base.CreateAsync();
}
可见
@model MyApp.Web.ViewModels.BarDTO
<select asp-for="BarTypeId"
asp-items="@(new SelectList(ViewBag.BarTypes,
"Id", "Name"))">
<option value="">please select</option>
</select>
我的问题是如何 link select 用户选择 Bar
的 BarType
在数据库中创建一个有效的 Bar
类型 ID。
你的问题涉及asp.net核心中所谓的model binding
。更具体地说,您想将 int Id
转换为 BarType
的实例。从客户端发送的 form
数据只是与相应键配对的字符串值。这些键就像指向相应模型的路径 属性。因为服务器端的模型可能是具有深层属性图的复杂类型。所以 key
可以是一个长的点分隔路径。在您的情况下,您的路径只是 BarType
,它基本上针对错误的对应 Id
。模型绑定器不能简单地将 int
转换为 BarType
的实例。正确的路径是 BarType.Id
。所以你可以有这样的代码:
<select asp-for="BarType.Id" asp-items="@(new SelectList(ViewBag.BarTypes, "Id", "Name"))">
<option value="">please select</option>
</select>
这将有助于模型绑定器使用收到的 Id
自动创建 BarType
的实例。但是我们只发送 Id
,所以 BarType
的实例只有 Id
而所有 Name
都是空的。 Select
html 元素只能保存与一个 属性 映射的一个值,通常这是键值。在您的情况下,实际上模型根本不需要 BarType.Name
。当我们处理可选择的数据时,我们只需要选择的key/id。考虑到这一点,我们完成了上面的代码。
如果您也想收到 BarType.Name
,我必须说它设计有误。除非可以编辑从客户端发送的 BarType.Name
,但在这种情况下,显然它只是一个常量,就像 BarType.Id
。保存数据时,Id
是我们 link 实体(建立关系)所需要的,其他属性根本不重要,实际上可以 derived/fetched 来自 Id
.
如果您仍想收到 BarType.Name
,您至少有 2 个选择。第一个简单的方法是声明一个包含 Id
和 Name
的未映射 属性,以某种方式构造计算值,以便您可以从中提取每个单独的值。这是一个例子:
public class BarType : IdEntity
{
public int Id { get; set; }
public string Name { get; set; }
//this property should be configured as not-mapped (ignored)
public string Id_Name {
get {
if(_id_Name == null){
_id_Name = $"{Id}_{Name}";
}
return _id_Name;
}
set {
_id_Name = value;
}
}
string _id_Name;
//a method to parse back the Id & Name
public BarType ParseIdName(){
if(!string.IsNullOrWhiteSpace(Id_Name)){
var parts = Id_Name.Split(new[] {'_'}, 2);
Id = int.TryParse(parts[0], out var id) ? id : 0;
Name = parts.Length > 1 ? parts[1] : null;
}
return this;
}
}
现在,您使用 Id_Name
:
而不是对所选值使用 Id
<select asp-for="BarType.Id_Name" asp-items="@(new SelectList(ViewBag.BarTypes, "Id_Name", "Name"))">
<option value="">please select</option>
</select>
请注意,在控制器操作中实际使用模型中可用的绑定 BarType
之前,您需要手动调用方法 BarType.ParseIdName
,如下所示:
public override Task<IActionResult> Create(Bar entity)
{
entity.BarType?.ParseIdName();
//the entity is ready now ...
//...
return base.Create(entity);
}
第一个选项很简单但有点棘手,我个人不会使用它。更标准的方法是使用带有自定义 IModelBinder
的第二个选项。这针对模型类型 BarType
并将 Id
解析为 BarType
的实例。这个解决过程应该很快。
public class BarTypeModelBinder : IModelBinder {
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var fieldValue = bindingContext.ValueProvider.GetValue(bindingContext.FieldName).FirstValue;
//here we just instantiate an instance of BarType with Id but without Name
if(int.TryParse(fieldValue, out var id)){
bindingContext.Result = ModelBindingResult.Succeed(new BarType { Id = id });
}
else {
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
您应该像这样使用该模型活页夹:
[ModelBinder(typeof(BarTypeModelBinder))]
public class BarType : IdEntity
{
//...
}
差不多完成了。现在谈谈如何从 Id
解析 BarType
的实例。正如我所说,通常我们只需要 Id
所以只需要创建一个只包含 Id 的 BarType
实例(就像上面的代码一样)就足够了。这当然非常快。如果您还需要 Name
,您可能必须使用某些服务来解析 BarType
的实例。因为它需要快速,所以您确实需要某种 in-memory
查找(或缓存数据)。假设您有一项服务可以像这样从 Id
解析 BarType
实例:
public interface IBarTypeService {
BarType GetBarType(int id);
}
您可以像这样在 BarTypeModelBinder
中使用该服务:
if(int.TryParse(fieldValue, out var id)){
var barType = _barTypeService.GetBarType(id);
bindingContext.Result = ModelBindingResult.Succeed(barType);
}
else {
bindingContext.Result = ModelBindingResult.Failed();
}
第二个选项对于初学者来说可能有点复杂(因为它涉及一个很好的 IBarTypeService
支持缓存或者至少是一个很好的方式来为它提供一些内存中的数据查找)但它确实是标准的方法。一旦你熟悉了那个逻辑,就会觉得很正常。
我有一个带有 EntityFramework 核心的 .NET 5 应用程序,我尝试 link 下拉列表中的实体类型:
我有生意class
public class Bar : IdEntity
{
public BarType BarType { get; set; }
public class BarType : IdEntity
{
public int Id { get; set; }
public string Name { get; set; }
为了不直接在视图中使用Bar
,我创建了一个DTO(数据传输对象,或者只是一个viewModel),像这样:
public class BarDTO : IdEntityDTO
{
public int BarTypeId { get; set; }
public BarType BarType { get; set; }
我在控制器中做:
public class BarController : IdEntityController<Bar, BarDTO>
{
public override async Task<IActionResult> CreateAsync()
{
var barTypes=await _repository.ListAsync<BarType>();
ViewBag.BarTypes = barTypes;
return await base.CreateAsync();
}
可见
@model MyApp.Web.ViewModels.BarDTO
<select asp-for="BarTypeId"
asp-items="@(new SelectList(ViewBag.BarTypes,
"Id", "Name"))">
<option value="">please select</option>
</select>
我的问题是如何 link select 用户选择 Bar
的 BarType
在数据库中创建一个有效的 Bar
类型 ID。
你的问题涉及asp.net核心中所谓的model binding
。更具体地说,您想将 int Id
转换为 BarType
的实例。从客户端发送的 form
数据只是与相应键配对的字符串值。这些键就像指向相应模型的路径 属性。因为服务器端的模型可能是具有深层属性图的复杂类型。所以 key
可以是一个长的点分隔路径。在您的情况下,您的路径只是 BarType
,它基本上针对错误的对应 Id
。模型绑定器不能简单地将 int
转换为 BarType
的实例。正确的路径是 BarType.Id
。所以你可以有这样的代码:
<select asp-for="BarType.Id" asp-items="@(new SelectList(ViewBag.BarTypes, "Id", "Name"))">
<option value="">please select</option>
</select>
这将有助于模型绑定器使用收到的 Id
自动创建 BarType
的实例。但是我们只发送 Id
,所以 BarType
的实例只有 Id
而所有 Name
都是空的。 Select
html 元素只能保存与一个 属性 映射的一个值,通常这是键值。在您的情况下,实际上模型根本不需要 BarType.Name
。当我们处理可选择的数据时,我们只需要选择的key/id。考虑到这一点,我们完成了上面的代码。
如果您也想收到 BarType.Name
,我必须说它设计有误。除非可以编辑从客户端发送的 BarType.Name
,但在这种情况下,显然它只是一个常量,就像 BarType.Id
。保存数据时,Id
是我们 link 实体(建立关系)所需要的,其他属性根本不重要,实际上可以 derived/fetched 来自 Id
.
如果您仍想收到 BarType.Name
,您至少有 2 个选择。第一个简单的方法是声明一个包含 Id
和 Name
的未映射 属性,以某种方式构造计算值,以便您可以从中提取每个单独的值。这是一个例子:
public class BarType : IdEntity
{
public int Id { get; set; }
public string Name { get; set; }
//this property should be configured as not-mapped (ignored)
public string Id_Name {
get {
if(_id_Name == null){
_id_Name = $"{Id}_{Name}";
}
return _id_Name;
}
set {
_id_Name = value;
}
}
string _id_Name;
//a method to parse back the Id & Name
public BarType ParseIdName(){
if(!string.IsNullOrWhiteSpace(Id_Name)){
var parts = Id_Name.Split(new[] {'_'}, 2);
Id = int.TryParse(parts[0], out var id) ? id : 0;
Name = parts.Length > 1 ? parts[1] : null;
}
return this;
}
}
现在,您使用 Id_Name
:
Id
<select asp-for="BarType.Id_Name" asp-items="@(new SelectList(ViewBag.BarTypes, "Id_Name", "Name"))">
<option value="">please select</option>
</select>
请注意,在控制器操作中实际使用模型中可用的绑定 BarType
之前,您需要手动调用方法 BarType.ParseIdName
,如下所示:
public override Task<IActionResult> Create(Bar entity)
{
entity.BarType?.ParseIdName();
//the entity is ready now ...
//...
return base.Create(entity);
}
第一个选项很简单但有点棘手,我个人不会使用它。更标准的方法是使用带有自定义 IModelBinder
的第二个选项。这针对模型类型 BarType
并将 Id
解析为 BarType
的实例。这个解决过程应该很快。
public class BarTypeModelBinder : IModelBinder {
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var fieldValue = bindingContext.ValueProvider.GetValue(bindingContext.FieldName).FirstValue;
//here we just instantiate an instance of BarType with Id but without Name
if(int.TryParse(fieldValue, out var id)){
bindingContext.Result = ModelBindingResult.Succeed(new BarType { Id = id });
}
else {
bindingContext.Result = ModelBindingResult.Failed();
}
return Task.CompletedTask;
}
}
您应该像这样使用该模型活页夹:
[ModelBinder(typeof(BarTypeModelBinder))]
public class BarType : IdEntity
{
//...
}
差不多完成了。现在谈谈如何从 Id
解析 BarType
的实例。正如我所说,通常我们只需要 Id
所以只需要创建一个只包含 Id 的 BarType
实例(就像上面的代码一样)就足够了。这当然非常快。如果您还需要 Name
,您可能必须使用某些服务来解析 BarType
的实例。因为它需要快速,所以您确实需要某种 in-memory
查找(或缓存数据)。假设您有一项服务可以像这样从 Id
解析 BarType
实例:
public interface IBarTypeService {
BarType GetBarType(int id);
}
您可以像这样在 BarTypeModelBinder
中使用该服务:
if(int.TryParse(fieldValue, out var id)){
var barType = _barTypeService.GetBarType(id);
bindingContext.Result = ModelBindingResult.Succeed(barType);
}
else {
bindingContext.Result = ModelBindingResult.Failed();
}
第二个选项对于初学者来说可能有点复杂(因为它涉及一个很好的 IBarTypeService
支持缓存或者至少是一个很好的方式来为它提供一些内存中的数据查找)但它确实是标准的方法。一旦你熟悉了那个逻辑,就会觉得很正常。