Я успешно (возможно, не элегантно) создал связыватель модели, который будет связывать Список Интерфейсов при публикации.Каждый интерфейс имеет отдельные свойства, а некоторые имеют вложенный список другого интерфейса.Список интерфейсов отображается правильно в представлении, как и вложенные элементы списка.После публикации все работает, вызывается привязка пользовательских моделей и создаются правильные типы.Проблема в том, что я застрял в том, что если во вложенном Списке интерфейсов нет элементов для отображения, то при повторной публикации связыватель модели не будет создавать этот объект и любые объекты после этого.
Я использую бритвенные страницы иих соответствующие модели страниц.Я использую аннотацию [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));
Я пытался предоставить как можно больше информации.
Я занимался этим несколько дней и в итоге получил все привязки, чтобы работать, кроме одного этого случая.
ТАК сообщения, которые помогли получить это далеко:
Я понимаю, что связыватели моделей работают с рекурсией, что приводит меня к мысли, что происходит что-то, что останавливает выполнение, как только оно достигает Question
без значений AnswerList
.
Единственное, что я заметил, это то, что свойство AnswerList
Tag
в html имеет 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"
Я не уверен, почему это так.Я не установил это явно.Класс находится в другом пространстве имен, и мы бы предпочли не применять аннотации данных ко всем классам.
Это может быть то, что нарушает привязку, так как она ожидает значения, однако я не уверен.
Это проблема нормального поведения?Если да, то каким может быть решение?