So I've got a partial view which accepts a GeneralViewModel. The GeneralViewModel contains a list of table structures. At the moment it only has one:
GeneralViewModel
public class GeneralViewModel
{
public List<Customers> Customers {get; set;}
}
Let's just say that Customers has some basic fields eg:
CustomersModel
public class Customers
{
public int CustomerID {get; set;}
public string CustomerName {get; set;}
}
I've made the View generic so it can handle any type being passed in (at the moment it's only Customers).
View
@model MVC.Models.GeneralViewModel
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/_Layout.cshtml";
}
@section Styles {
<link href="@Url.Content("~/Content/Home.css")" rel="stylesheet" type="text/css" />
}
@{
//Get the properties of GeneralViewModel
var generalProps = @Model.GetType().GetProperties();}
<div class="container">
<h2>Index</h2>
@using (Html.BeginForm("Export", "Home", FormMethod.Post, new { app = @Model }))
{
<input type="submit" value="Export" />
<button>Import</button>
<button>Approve Changes</button>
<table>
@{
foreach(var generalProp in @generalProps)
{
//Get the List model within the GeneralViewModel and check if it's got items within there
var item = (System.Collections.IList)generalProp.GetValue(@Model);
if (item.Count > 0)
{
//Get the properties for the list in this case CustomerID, CustomerName
var props = item[0].GetType().GetProperties();
<tr>
@foreach (var prop in props)
{
<th>
//Show either the displayname or field as the header
@((System.ComponentModel.DisplayNameAttribute)Attribute.GetCustomAttribute(prop, typeof(System.ComponentModel.DisplayNameAttribute)) != null ?
((System.ComponentModel.DisplayNameAttribute)Attribute.GetCustomAttribute(prop, typeof(System.ComponentModel.DisplayNameAttribute))).DisplayName : prop.Name)
</th>
}
</tr>
//Loop through the List<Customers>
for (int i = 0; i < item.Count; i++)
{
<tr>
//Loop through the properties of Customers and post it
@for(int j = 0; j < props.Count(); j++)
{
<td><input type="text" value="@props[j].GetValue(item[i], null)" readonly/></td>
}
</tr>
}
}
}
}
</table>
}
</div>
The code displays the table correctly but it's just the Export button which doesn't seem to work as intended.
Controller
[HttpPost]
public ActionResult Export(GeneralViewModel table)
{
if(table.Customers != null)
{
Console.WriteLine("not null");
}
}
I used to have foreach loops instead and have changed them to for loops instead as I had found a few places where they stated it must be for loops. Other then that I've seen that the forms have the field hardcoded within the View. For example //Loop through the List<Customers>
section would be replaced with:
//Loop through the List<Customers>
for (int i = 0; i < item.Count; i++)
{
<tr>
<td><input type="text" value="item[i].CustomerID" readonly/></td>
<td><input type="text" value="item[i].CustomerName" readonly/></td>
</tr>
}
However this doesn't achieve what I want with GeneralViewModel containing more List models in the future. I know that I can create separate Views for the other models which will be added in the future but it would feel as if I'm just duplicating code.
In the controller what I get when I run it and inspect the table variable is that GeneralViewModel.Customers
is null
Managed to get it working it was due to input
element not having a name
field. Also the name
field has to be properly assigned according to the structure of the ViewModel.
So I had switched:
<td><input type="text" value="@props[j].GetValue(item[i], null)" readonly/></td>
with:
<td><input type="text" value="@props[j].GetValue(item[i], null)"
name="@(generalProp.Name)[@(i)].@props[j].Name" readonly/></td>