在asp.net core中,从下拉菜单中获取值



我有一个带有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; }

In controller I do:

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

你的问题涉及所谓的model binding在asp.net核心。更具体地说,您希望将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都是空的。Selecthtml元素只能保存一个映射到一个属性的值,通常是键值。在您的情况下,实际上模型根本不需要BarType.Name。当我们处理可选择的数据时,我们只需要选择的键/id。记住这一点,我们就完成了上面的代码。

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

如果你仍然想要接收BarType.Name,你至少有2个选择。第一个简单的方法是声明一个包含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,或者至少是一个为它提供一些内存中数据查找的好方法),但实际上它是标准的方法。一旦你熟悉了这种逻辑,就会觉得很正常。

最新更新