C# ASP.NET 5中ViewModel使用接口时视图绑定与ModelBinder绑定问题
解决方案
不用合并PersonUL和PersonIP,有几个可落地的方案:
方案1:直接在asp-for中做类型转换(最简单)
你原来的代码首先有个笔误:if (Model.DocumentAutors.Authors is PersonUL )是判断整个列表类型,应该判断单个元素。修改后直接在asp-for里做安全转换即可,Tag Helper会保留完整的模型路径,生成正确的name属性:
@if (Model.DocumentAutors.Authors != null && Model.DocumentAutors.Authors.Count > 0) { for (int i = 0; i < Model.DocumentAutors.Authors.Count; i++) { // 先判断当前元素是PersonUL类型 if (Model.DocumentAutors.Authors[i] is PersonUL) { <div class="form-group"> <label asp-for="@((Model.DocumentAutors.Authors[i] as PersonUL).Name)" class="col-md-10 control-label"></label> <div class="col-md-10"> <input asp-for="@((Model.DocumentAutors.Authors[i] as PersonUL).Name)" class="form-control" /> <span asp-validation-for="@((Model.DocumentAutors.Authors[i] as PersonUL).Name)" class="text-danger"></span> </div> </div> } // 同理处理PersonIP类型 else if (Model.DocumentAutors.Authors[i] is PersonIP) { <div class="form-group"> <label asp-for="@((Model.DocumentAutors.Authors[i] as PersonIP).FirstName)" class="col-md-10 control-label"></label> <div class="col-md-10"> <input asp-for="@((Model.DocumentAutors.Authors[i] as PersonIP).FirstName)" class="form-control" /> <span asp-validation-for="@((Model.DocumentAutors.Authors[i] as PersonIP).FirstName)" class="text-danger"></span> </div> </div> } } }
因为前面已经做了类型判断,as转换不会出现空引用,生成的name属性会自动保留DocumentAutors.Authors[0].Name的完整路径,模型绑定可以正常工作。
方案2:手动指定name属性
如果觉得多次写as转换太繁琐,也可以用Html.NameFor方法手动生成正确的name属性,同时绑定转换后的值:
@if (Model.DocumentAutors.Authors != null && Model.DocumentAutors.Authors.Count > 0) { for (int i = 0; i < Model.DocumentAutors.Authors.Count; i++) { if (Model.DocumentAutors.Authors[i] is PersonUL ul) { <div class="form-group"> <label class="col-md-10 control-label">@nameof(ul.Name)</label> <div class="col-md-10"> <input name="@Html.NameFor(m => m.DocumentAutors.Authors[i].Name)" value="@ul.Name" class="form-control" /> <span asp-validation-for="@Model.DocumentAutors.Authors[i].Name" class="text-danger"></span> </div> </div> } } }
方案3:自定义IPerson模型绑定器(适合多场景复用)
如果项目中大量用到IPerson接口类型的绑定,可以自定义一个模型绑定器,自动根据提交的字段判断实例化PersonUL还是PersonIP,一劳永逸解决绑定问题:
- 先定义绑定器:
public class PersonModelBinder : IModelBinder { public Task BindModelAsync(ModelBindingContext bindingContext) { if (bindingContext.ModelType != typeof(IPerson)) return Task.CompletedTask; // 检查提交的字段里有没有Name,有就是PersonUL var nameValue = bindingContext.ValueProvider.GetValue("Name").FirstValue; if (!string.IsNullOrEmpty(nameValue)) { var person = new PersonUL(); bindingContext.Model = person; if (long.TryParse(bindingContext.ValueProvider.GetValue("Id").FirstValue, out var id)) person.Id = id; person.Name = nameValue; person.Address = bindingContext.ValueProvider.GetValue("Address").FirstValue; bindingContext.Result = ModelBindingResult.Success(person); return Task.CompletedTask; } // 有FirstName就是PersonIP var firstNameValue = bindingContext.ValueProvider.GetValue("FirstName").FirstValue; if (!string.IsNullOrEmpty(firstNameValue)) { var person = new PersonIP(); bindingContext.Model = person; if (long.TryParse(bindingContext.ValueProvider.GetValue("Id").FirstValue, out var id)) person.Id = id; person.FirstName = firstNameValue; person.SecondNAme = bindingContext.ValueProvider.GetValue("SecondNAme").FirstValue; person.PostAddress = bindingContext.ValueProvider.GetValue("PostAddress").FirstValue; bindingContext.Result = ModelBindingResult.Success(person); return Task.CompletedTask; } return Task.CompletedTask; } }
- 在Program.cs里注册绑定器:
builder.Services.AddControllersWithViews(options => { options.ModelBinderProviders.Insert(0, new BinderTypeModelBinderProvider(typeof(PersonModelBinder))); });
以上三个方案都可以解决你的问题,不需要合并两个实体类。
内容的提问来源于stack exchange,提问作者Sergey
相关产品推荐
相关产品推荐

