我已经成功地(也许不是优雅地)创建了一个模型绑定,将绑定一个列表的接口在帖子。每个接口都有单独的属性,有些接口有另一个接口的嵌套列表。接口列表在视图中正确显示,嵌套列表项也是如此。在post所有的工作,自定义模型绑定被调用和正确的类型被建立。我遇到的问题是,如果一个嵌套的接口列表没有要显示的项,那么在回发时,模型绑定器将不会构建该对象以及之后的任何对象。
我正在使用剃须刀页面和它们各自的页面模型。我使用页面模型中的[BindProperty]注释。
接口和对象
用具体的实现来减少接口:我已经减少了类,并且省略了一些不必要的代码。
public interface IQuestion
{
Guid Number{ get; set; }
string Text{ get; set; }
List<IAnswer> AnswerList{ get; set; }
..
}public interface IAnswer
{
string Label { get; set; }
string Tag { get; set; }
..
}public class MetaQuestion: IQuestion
{
public int Number{ get; set; }
public string Text{ get; set; }
public List<IAnswer> AnswerList{ get; set; }
..
}public class Answer: IAnswer
{
public string Label { get; set; }
public string Tag { get; set; }
..
}剃须刀页面模型
public class TestListModel : PageModel
{
private readonly IDbSession _dbSession;
[BindProperty]
public List<IQuestion> Questions { get; set; }
public TestListModel(IDbSession dbSession)
{
_dbSession= dbSession;
}
public async Task OnGetAsync()
{
//just to demonstrate where the data is comming from
var allQuestions = await _dbSession.GetAsync<Questions>();
if (allQuestions == null)
{
return NotFound($"Unable to load questions.");
}
else
{
Questions = allQuestions;
}
}
public async Task<IActionResult> OnPostAsync()
{
//do something random with the data from the post back
var question = Questions.FirstOrDefault();
..
return Page();
}
}生成的Html
这是生成的不工作代码的html。其中一个问题项--特别是列表中的第二个项目--在Answers中没有任何AnswerList。
正如我们所看到的,清单上的第二个问题没有“答案”项目。这意味着,在回发后,我只收到名单上的第一个问题。如果我把第二个问题从列表中删除,那么我会把所有的问题都拿回来。
为了简洁起见,我删除了所有的样式、类和div。
关于问题1
<input id="Questions_0__Number" name="Questions[0].Number" type="text" value="sq1">
<input id="Questions_0__Text" name="Questions[0].Text" type="text" value="Are you:">
<input name="Questions[0].TargetTypeName" type="hidden" value="Core.Model.MetaData.MetaQuestion, Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<input data-val="true" data-val-required="The Tag field is required." id="Questions_0__AnswerList_0__Tag" name="Questions[0].AnswerList[0].Tag" type="text" value="1">
<input id="Questions_0__AnswerList_0__Label" name="Questions[0].AnswerList[0].Label" type="text" value="Male">
<input id="Questions_0__AnswerList_0__TargetTypeName" name="Questions[0].AnswerList[0].TargetTypeName" type="hidden" value="Core.Common.Implementations.Answer, Core.Common, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">关于问题2
<input id="Questions_1__Number" name="Questions[1].Number" type="text" value="sq1">
<input id="Questions_1__Text" name="Questions[1].Text" type="text" value="Are you:">
<input name="Questions[1].TargetTypeName" type="hidden" value="Core.Model.MetaData.MetaQuestion, Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">问题2之后的其余问题与问题1相似。
自定义模型绑定器和提供程序
我知道这不是最好的方法,包括TargetTypeName是不理想的。在这个问题上,我实在找不到什么帮助的地方。当涉及到ASP网页开发时,我是新手。
public class IQuestionModelBinder : IModelBinder
{
private readonly IDictionary<Type, ComplexTypeModelBinder> modelBuilderByType;
private readonly IModelMetadataProvider modelMetadataProvider;
public IQuestionModelBinder(IDictionary<Type, ComplexTypeModelBinder> modelBuilderByType, IModelMetadataProvider modelMetadataProvider)
{
this.modelBuilderByType = modelBuilderByType ?? throw new ArgumentNullException(nameof(modelBuilderByType));
this.modelMetadataProvider = modelMetadataProvider ?? throw new ArgumentNullException(nameof(modelMetadataProvider));
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var str = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "TargetTypeName");
var modelTypeValue = bindingContext.ValueProvider.GetValue(ModelNames.CreatePropertyModelName(bindingContext.ModelName, "TargetTypeName"));
if (modelTypeValue != null && modelTypeValue.FirstValue != null)
{
Type modelType = Type.GetType(modelTypeValue.FirstValue);
if (this.modelBuilderByType.TryGetValue(modelType, out var modelBinder))
{
ModelBindingContext innerModelBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
this.modelMetadataProvider.GetMetadataForType(modelType),
null,
bindingContext.ModelName);
modelBinder.BindModelAsync(innerModelBindingContext);
bindingContext.Result = innerModelBindingContext.Result;
return Task.CompletedTask;
}
}
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
}供应商:
public class IQuestionModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(IQuestion))
{
var assembly = typeof(IQuestion).Assembly;
var metaquestionClasses = assembly.GetExportedTypes()
.Where(t => !t.IsInterface || !t.IsAbstract)
.Where(t => t.BaseType.Equals(typeof(IQuestion)))
.ToList();
var modelBuilderByType = new Dictionary<Type, ComplexTypeModelBinder>();
foreach (var type in metaquestionClasses)
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
var metadata = context.MetadataProvider.GetMetadataForType(type);
foreach (var property in metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
modelBuilderByType.Add(type, new ComplexTypeModelBinder(propertyBinders: propertyBinders));
}
return new IMetaQuestionModelBinder(modelBuilderByType, context.MetadataProvider);
}
return null;
}类似于IAnswer接口(可能重构为没有2个绑定程序):
public class IAnswerModelBinder : IModelBinder
{
private readonly IDictionary<Type, ComplexTypeModelBinder> modelBuilderByType;
private readonly IModelMetadataProvider modelMetadataProvider;
public IAnswerModelBinder(IDictionary<Type, ComplexTypeModelBinder> modelBuilderByType, IModelMetadataProvider modelMetadataProvider)
{
this.modelBuilderByType = modelBuilderByType ?? throw new ArgumentNullException(nameof(modelBuilderByType));
this.modelMetadataProvider = modelMetadataProvider ?? throw new ArgumentNullException(nameof(modelMetadataProvider));
}
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var str = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "TargetTypeName");
var modelTypeValue = bindingContext.ValueProvider.GetValue(ModelNames.CreatePropertyModelName(bindingContext.ModelName, "TargetTypeName"));
if (modelTypeValue != null && modelTypeValue.FirstValue != null)
{
Type modelType = Type.GetType(modelTypeValue.FirstValue);
if (this.modelBuilderByType.TryGetValue(modelType, out var modelBinder))
{
ModelBindingContext innerModelBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
this.modelMetadataProvider.GetMetadataForType(modelType),
null,
bindingContext.ModelName);
modelBinder.BindModelAsync(innerModelBindingContext);
bindingContext.Result = innerModelBindingContext.Result;
return Task.CompletedTask;
}
}
bindingContext.Result = ModelBindingResult.Failed();
return Task.CompletedTask;
}
}供应商:
public class IAnswerModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.ModelType == typeof(IAnswer))
{
var exportedTypes = typeof(IAnswer).Assembly.GetExportedTypes();
var metaquestionClasses = exportedTypes
.Where(y => y.BaseType != null && typeof(IAnswer).IsAssignableFrom(y) && !y.IsInterface)
.ToList();
var modelBuilderByType = new Dictionary<Type, ComplexTypeModelBinder>();
foreach (var type in metaquestionClasses)
{
var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
var metadata = context.MetadataProvider.GetMetadataForType(type);
foreach (var property in metadata.Properties)
{
propertyBinders.Add(property, context.CreateBinder(property));
}
modelBuilderByType.Add(type, new ComplexTypeModelBinder(propertyBinders: propertyBinders));
}
return new IAnswerModelBinder(modelBuilderByType, context.MetadataProvider);
}
return null;
}这两项登记情况如下:
services.AddMvc(
options =>
{
// add custom binder to beginning of collection (serves IMetaquestion binding)
options.ModelBinderProviders.Insert(0, new IMetaQuestionModelBinderProvider());
options.ModelBinderProviders.Insert(0, new IAnswerModelBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2));我已经尽力提供尽可能多的信息。
我已经用了好几天了,除了这个案子之外,所有的绑定都开始工作了。
因此,帮助实现这一目标的帖子:
我理解模型绑定与递归一起工作,这使我相信,在没有AnswerList值的情况下,当它到达AnswerList时,会立即停止执行。
我注意到的唯一一点是,html中的AnswerList Tag属性也将data-val设置为true和data-val-required。
<input data-val="true" data-val-required="The Tag field is required." id="Questions_0__AnswerList_0__Tag" name="Questions[0].AnswerList[0].Tag" type="text" value="1"
我不知道为何会出现这种情况。我没有明确地设置这一点。该类位于不同的名称空间中,我们不愿在所有类中应用数据注释。
这可能是破坏绑定的原因,因为它需要一个值,但是我不能确定。
这个问题是否正常的行为?如果是这样的话,解决办法是什么?
发布于 2019-04-19 13:32:27
我将着手回答我自己的问题。这解决了问题。这就是我的编辑器模板在Question中的样子
@model MetaQuestion
<div class="card card form-group" style="margin-top:10px;">
<div class="card-header">
<strong>
@Html.TextBoxFor(x => x.Number, new { @class = "form-control bg-light", @readonly = "readonly", @style = "border:0px;" })
</strong>
</div>
<div class="card-body text-black-50">
<h6 class="card-title mb-2 text-muted">
@Html.TextBoxFor(x => x.Text, new { @class = "form-control", @readonly = "readonly", @style = "background-color:white; border:0px;" })
</h6>
@for (int i = 0; i < Model.AnswerList.Count; i++)
{
<div class="row">
<div class="col-1">
@Html.TextBoxFor(x => x.AnswerList[i].PreCode, new { @class = "form-control", @readonly = "readonly", @style = "background-color:white; border:0px;" })
</div>
<div class="col">
@Html.TextBoxFor(x => x.AnswerList[i].Label, new { @class = "form-control", @readonly = "readonly", @style = "background-color:white; border:0px;" })
</div>
<div class="col-1">
@Html.HiddenFor(x => x.AnswerList[i].TargetTypeName)
</div>
<div class="col-1">
<input name="@(ViewData.TemplateInfo.HtmlFieldPrefix + ".TargetTypeName")" type="hidden" value="@this.Model.GetType().AssemblyQualifiedName" />
</div>
</div>
}
</div>
</div>最后,您可以看到有2列包含HiddenFor帮助程序。我使用这些来识别接口是什么类型,这允许我的问题中提到的定制模型绑定选择相关类型。
对我来说不明显的是,当“问题”没有“答案”时,它忽略了for循环内和之后的所有值。因此,定制绑定无法找到Question的类型,因为数据完全丢失了。
从那以后,我开始重新订购解决这个问题的Html.HiddenFor助手.我的编辑现在看起来如下:
@model MetaQuestion
<div class="card card form-group" style="margin-top:10px;">
<div class="card-header">
<input name="@(ViewData.TemplateInfo.HtmlFieldPrefix + ".TargetTypeName")" type="hidden" value="@this.Model.GetType().AssemblyQualifiedName" />
<strong>
@Html.TextBoxFor(x => x.Number, new { @class = "form-control bg-light", @readonly = "readonly", @style = "border:0px;" })
</strong>
</div>
<div class="card-body text-black-50">
<h6 class="card-title mb-2 text-muted">
@Html.TextBoxFor(x => x.Text, new { @class = "form-control", @readonly = "readonly", @style = "background-color:white; border:0px;" })
</h6>
@for (int i = 0; i < Model.AnswerList.Count; i++)
{
@Html.HiddenFor(x => x.AnswerList[i].TargetTypeName)
<div class="row">
<div class="col-1">
@Html.TextBoxFor(x => x.AnswerList[i].PreCode, new { @class = "form-control", @readonly = "readonly", @style = "background-color:white; border:0px;" })
</div>
<div class="col">
@Html.TextBoxFor(x => x.AnswerList[i].Label, new { @class = "form-control", @readonly = "readonly", @style = "background-color:white; border:0px;" })
</div>
</div>
}
</div>
</div>把它放在前面,以确保它始终存在。这可能不是处理整个问题的最好办法,但至少它已经解决了问题。
https://stackoverflow.com/questions/55759979
复制相似问题