【问题标题】:get value from the drowdown to model in asp.net core从下拉列表中获取值以在 asp.net 核心中建模
【发布时间】:2021-01-30 23:11:09
【问题描述】:

我有一个带有 EntityFramework Core 的 .NET 5 应用程序,我尝试从下拉列表中链接实体的类型:

我有商务舱

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>

我的问题是如何将选择的用户选项与BarBarType 链接,以在数据库中创建一个具有相应类型ID 的有效Bar

【问题讨论】:

  • 尝试将模型 Bar 中字段 TypeBar 的类型更改为 BarType,因为目前,您的 Bar b>Bar 模型具有 TypeBar 类型的 TypeBar,而不是 BarType 当您的 View 具有类型为 的drowdopwn 时BarType,不适用于 TypeBar公共 BarType TypeBar { get;放; }

标签: c# .net asp.net-core drop-down-menu .net-5


【解决方案1】:

您的问题涉及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,所有Names 都是空的。 Select html 元素只能保存一个映射到一个属性的值,通常这就是键值。在您的情况下,模型实际上根本不需要BarType.Name。当我们处理可选择的数据时,我们只需要选择的 key/id。考虑到这一点,我们就完成了上面的代码。

如果您也想收到BarType.Name,我必须说它设计错误。除非可以编辑从客户端发送的BarType.Name,但在这种情况下显然它只是像BarType.Id 这样的常量。保存数据时,Id 是我们需要链接实体(建立关系),其他属性根本不重要,实际上可以从Id 派生/获取。

如果您仍然想收到BarType.Name,您至少有两个选择。第一个简单的方法是声明一个包含IdName 的非映射属性,以某种方式构造计算值,以便您可以从中提取每个单独的值。这是一个例子:

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 并支持缓存,或者至少是一个很好的方式来提供一些内存中的数据查找)但实际上它是标准的方式去。一旦你熟悉了这个逻辑,就会觉得很正常。

【讨论】:

  • 为了不搞乱业务和视图逻辑,我创建了一个 DTO 对象,同时具有 BarTypeIdBarType 属性。我使用 AutoMapper 将业务映射到 DTO 类
  • @Serge 我不确定您的意思,但model binding 的问题与您如何设计视图模型(或 dtos)无关。它只关心您在操作方法中使用的视图模型以及您从客户端发布的值。所以它的工作是将接收到的数据填充到视图模型中。在这种情况下,我知道您的视图模型是 Bar(公开 BarType 属性),与您发布的完全一样。如果您在应用我介绍的解决方案时遇到困难,请使用您的真实视图模型更新您的问题,以便我提供帮助。
  • 我更新了 OP 以反映我所做的更改
  • 也可在不和谐discord.gg/PNMq3pBSUe
  • @Serge 真的我希望你能附加你更新的内容,而不是覆盖旧的内容,这可能会使我回答的几乎没有意义。对于您更新的内容,我看到CreateAsync 不接受任何参数,那么您将如何获取从客户端发布的数据?了解实际问题是什么很重要。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-31
  • 1970-01-01
  • 1970-01-01
  • 2016-06-12
相关资源
最近更新 更多