Search code examples
c#asp.net-corerazorviewmodelasp.net-core-3.1

Bind HTML table TH dynamically using View Model Failed


I am trying to bind the HTML table using the View Model of type Object on the Razor Page. Code is as below :

index.cshtml.cs

[BindProperty]
public List<object> TableData { get; set; }

public class Cainfo
{
   public string Ca_id { get; set; }
   public object Nca { get; set; }
}

public async Task<IActionResult> OnGetAsync()
{
   List<object> tablerows = GetTableRows(tabledata);
   TableData = tablerows;
}

public List<object> GetTableRows(GetTableRowsResponse getTableRowsResponse)
{
       List<object> tableRows = new List<object>();
            
       var tables = getTableRowsResponse.rows;
       foreach (var table in tables)
       {
             var tab = JsonConvert.SerializeObject(table);
             var row = JsonConvert.DeserializeObject<Cainfo>(tab);
             tableRows.Add(row);
       }
       return tableRows;
}

index.cshtml

<table class="resultTable">
   <thead class="grid-header">                                                 
      <tr>                             
        @foreach (var property in @Model.TableData.GetType().GetGenericArguments()[0].GetProperties())
        {
          <th class="col-lg-1">@property.Name</th>
        }
       </tr>
    </thead>
    <tbody class="grid-body">
       @if (@Model.TableData != null)
       {
           @if ((@Model.TableData.Count != 0))
           {
              @foreach (var row in Model.TableData)
              {
                 <tr>
                  @foreach (var property in @row.GetType().GetProperties())
                  {
                     <td class="col-lg-1">@property.GetValue(@row)</td>
                  }
                 </tr>
              }
           }
       }                                
     </tbody>
</table>

var tables = getTableRowsResponse.rows; return the JSON data. Problem is that table <th> is not getting bind. @Model.TableData.GetType().GetGenericArguments()[0].GetProperties() is getting empty. <td> is getting bind as expected. Maybe I am doing a mistake somewhere, I am new to the asp .net core. Now, I am using the Model Cainfo but in future, I need to set different models according to data and bind the same table. That's why I am giving View Model type as Object. Please help.


Solution

  • I would not use reflection like this when you can design common models for the view. That's the art of designing which makes things easier. However here assume that you want to stick with that solution anyway, I'll show where it's wrong and how to fix it and how it's limited.

    First this Model.TableData.GetType().GetGenericArguments()[0] will return Type of object. And that Type of course contains no properties. That's why you get no <th> rendered. The generic argument type exactly reflects what's declared for Model.TableData which is a List<object>.

    Now to fix it, assume that all the items in the List<object> are of the same type, you can get the first item's Type, like this:

    @foreach (var property in @Model.TableData?.FirstOrDefault()
                                              ?.GetType()?.GetProperties() 
                                              ?? Enumerable.Empty<PropertyInfo>())
    {
       <th class="col-lg-1">@property.Name</th>
    }
    

    That has a limit in case the Model.TableData contains no item. No <th> will be rendered. If that's acceptable (instead of rendering an empty table with headers, you will render nothing or just some message) then just go that way. Otherwise, you need to provide a Type for the element/row's Typevia your model, such as via a property like Model.RowType. Then you can use that instead of this:

     Model.TableData?.FirstOrDefault()?.GetType()
    

    The remaining code is just the same.