title: How to handle model binding of Complex Type with List of interfaces and nested list of interfaces with possibly no values
body: <p>I have successfully(maybe not elegantly) created a model binder that will bind a List of Interfaces on post. Each interface has separate properties and some have a nested List of another interface. The list of interfaces get displayed correctly in the view and so do the nested list items. On post everything works, the custom model binders get called and the correct types get built. The issue that has me stuck is that if a nested List of interfaces has no items to display, on post back the model binder will not build that object up and any objects after that.</p>
  
  <p>I am using razor pages and their respective page models. I make use of the 
  <code>[BindProperty]</code> annotation inside the pagemodel.</p>
  
  <h2>Interfaces and objects</h2>
  
  <p>Trimmed down Interfaces with concrete implementations: I have trimmed down the classes and omitted unnecessary code with ..</p>
  
  <pre class="lang-cs prettyprint-override"><code>public interface IQuestion
  {
      Guid Number{ get; set; }
      string Text{ get; set; }
      List&lt;IAnswer&gt; AnswerList{ get; set; }
      ..
  }
  </code></pre>
  
  <pre><code>public interface IAnswer
      {
          string Label { get; set; }
          string Tag { get; set; }
          ..
      }
  </code></pre>
  
  <pre><code>public class MetaQuestion: IQuestion
      {
          public int Number{ get; set; }
          public string Text{ get; set; }
          public List&lt;IAnswer&gt; AnswerList{ get; set; }
          ..
      }
  </code></pre>
  
  <pre><code>public class Answer: IAnswer
      {
          public string Label { get; set; }
          public string Tag { get; set; }
          ..
      }
  </code></pre>
  
  <h2>Razor page model</h2>
  
  <pre><code>public class TestListModel : PageModel
      {
          private readonly IDbSession _dbSession;
  
          [BindProperty]
          public List&lt;IQuestion&gt; 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&lt;Questions&gt;();
  
              if (allQuestions == null)
              {
                  return NotFound($"Unable to load questions.");
              }
              else
              {                
                  Questions = allQuestions;
              }
          }
  
          public async Task&lt;IActionResult&gt; OnPostAsync()
          {
              //do something random with the data from the post back
              var question = Questions.FirstOrDefault();
              ..          
              return Page();
          }
      }
  </code></pre>
  
  <h2>Generated Html</h2>
  
  <p>This is the generated html of the code that does not work. One of the Question items specifically the second item in the list, does not have any <code>Answers</code> in the <code>AnswerList</code>. </p>
  
  <p>As we can see, the second Question in the list has no 'Answer' items in the AnswerList'. This means that on post back, I only receive the first Question in the list. If I remove the second Question from the list then I get all the questions back.</p>
  
  <p>I have removed all styling, classes and divs for the sake of brevity.</p>
  
  <p>For <strong>Question 1</strong>:</p>
  
  <pre><code>&lt;input id="Questions_0__Number" name="Questions[0].Number" type="text" value="sq1"&gt;
  &lt;input id="Questions_0__Text" name="Questions[0].Text" type="text" value="Are you:"&gt;
  &lt;input name="Questions[0].TargetTypeName" type="hidden" value="Core.Model.MetaData.MetaQuestion, Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"&gt;
  &lt;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"&gt;
  &lt;input id="Questions_0__AnswerList_0__Label" name="Questions[0].AnswerList[0].Label" type="text" value="Male"&gt;
  &lt;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"&gt;
  </code></pre>
  
  <p>For <strong>Question 2</strong>:</p>
  
  <pre><code>&lt;input id="Questions_1__Number" name="Questions[1].Number" type="text" value="sq1"&gt;
  &lt;input id="Questions_1__Text" name="Questions[1].Text" type="text" value="Are you:"&gt;
  &lt;input name="Questions[1].TargetTypeName" type="hidden" value="Core.Model.MetaData.MetaQuestion, Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"&gt;
  </code></pre>
  
  <p>The rest of the questions after question 2 are similar to question 1.</p>
  
  <h2>Custom Model Binders and providers</h2>
  
  <p>I understand that this isn't the best way to do this, and including the <code>TargetTypeName</code> is not ideal. There really isn't much out there that I could find that helps with this problem. I am newbie when it comes to ASP web dev.</p>
  
  <pre><code>public class IQuestionModelBinder : IModelBinder
      {
          private readonly IDictionary&lt;Type, ComplexTypeModelBinder&gt; modelBuilderByType;
  
          private readonly IModelMetadataProvider modelMetadataProvider;
  
          public IQuestionModelBinder(IDictionary&lt;Type, ComplexTypeModelBinder&gt; 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 &amp;&amp; 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;
          }
      }
  </code></pre>
  
  <p>And the provider:</p>
  
  <pre><code> 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 =&gt; !t.IsInterface || !t.IsAbstract)
                      .Where(t =&gt; t.BaseType.Equals(typeof(IQuestion)))
                      .ToList();
  
                  var modelBuilderByType = new Dictionary&lt;Type, ComplexTypeModelBinder&gt;();
  
                  foreach (var type in metaquestionClasses)
                  {
                      var propertyBinders = new Dictionary&lt;ModelMetadata, IModelBinder&gt;();
                      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;
          }
  </code></pre>
  
  <p>Similar for the IAnswer interface (could potentially refactor to not have 2 binders):</p>
  
  <pre><code>  public class IAnswerModelBinder : IModelBinder
      {
          private readonly IDictionary&lt;Type, ComplexTypeModelBinder&gt; modelBuilderByType;
  
          private readonly IModelMetadataProvider modelMetadataProvider;
  
          public IAnswerModelBinder(IDictionary&lt;Type, ComplexTypeModelBinder&gt; 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 &amp;&amp; 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;
          }
      }
  </code></pre>
  
  <p>And the provider:</p>
  
  <pre><code> 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 =&gt; y.BaseType != null &amp;&amp; typeof(IAnswer).IsAssignableFrom(y) &amp;&amp; !y.IsInterface)
                      .ToList();
  
                  var modelBuilderByType = new Dictionary&lt;Type, ComplexTypeModelBinder&gt;();
  
                  foreach (var type in metaquestionClasses)
                  {
                      var propertyBinders = new Dictionary&lt;ModelMetadata, IModelBinder&gt;();
                      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;
          }
  </code></pre>
  
  <p>These are both registered as follows:</p>
  
  <pre><code>  services.AddMvc(
                  options =&gt;
                  {
                      // 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));
  </code></pre>
  
  <p>I have tried to provide as much as info as possible.</p>
  
  <p>I have been on this for days and eventually have got all the bindings to work except for this one case. </p>
  
  <p>SO posts that helped get this far:</p>
  
  <ul>
  <li><a href="https://stackoverflow.com/questions/47643025/how-to-extend-complextypemodelbinder">How to extend complextypemodelbinder</a></li>
  <li><a href="https://stackoverflow.com/questions/39062386/post-a-listinterface-net-core-1-0">Post a list interface</a></li>
  </ul>
  
  <p>I understand that the model binders work with recursion, which leads me to believe that something is happening that is stopping execution as soon as it hits the <code>Question</code> with no <code>AnswerList</code> values.</p>
  
  <p>The only thing I noticed is that the <code>AnswerList</code> <code>Tag</code> property in the html has the <code>data-val</code> set to true and <code>data-val-required</code> as well.</p>
  
  <p><code>&lt;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"</code></p>
  
  <p>I am not sure why this would be the case. I have not explicitly set this. The class is in a different namespace and we would rather not apply data annotations all over the classes.</p>
  
  <p>This could be what is breaking the binding as it is expecting a value, however I cannot be sure.</p>
  
  <p>Is this problem normal behaviour? If so what could the solution be?</p>