I'm a bit confused because I thought this a very straight-forward thing, it's possibly something simple tripping me up.
I have a view:
#model IEnumerable<CarViewModel>
#using (Html.BeginForm("SummarySaveAll", "VroomVroom", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div>
<table>
<thead>
<tr>
<th width="1">
#Html.DisplayNameFor(model => model.Driver)
</th>
<th width="1">
#Html.DisplayNameFor(model => model.Colour.Name)
</th>
</tr>
</thead>
<tbody>
#foreach (var element in Model)
{
<tr>
<td width="1">
#Html.DisplayFor(m => element.Driver)
</td>
<td width="1">
#Html.DropDownListFor(m => element.Colour, element.Colours, "Unknown")
</td>
</tr>
}
</tbody>
</table>
<div>
<input type="submit" value="Save Changes" class="btn" />
#Html.ActionLink("Cancel Changes", "Index", null, new { #class = "btn" })
</div>
</div>
}
and the list/enumerable of CarViewModel is supposed to bounce back to the VroomVroom controller, action SummarySaveAll which it does - but the viewmodel on the page doesn't get passed back to it:
[HttpPost]
public ActionResult SummarySaveAll(IEnumerable<CarViewModel> summaries)
{
// Want to do stuff with summaries but it's always null
return View();
}
I tried to encapsulate the List in another ViewModel and cycle through elements using a for i loop but that wouldn't pass back to the controller either.
Surely it's possible to send a List or IEnumerable of models back to a controller?
My CarVM:
public class CarViewModel
{
[MaxLength(150)]
[Display(AutoGenerateField = true, Name = "Entered By")]
public string Driver { get; set; }
[Display(AutoGenerateField = true)]
public Colour Colour { get; set; }
[Key]
[Display(AutoGenerateField = false)]
public int Id { get; set; }
[Display(AutoGenerateField = false)]
public bool IsDeleted { get; set; } = false;
public IEnumerable<SelectListItem> Colours { get; set; }
public CarViewModel() { }
public CarViewModel(Model CarModel summaryModel, CarPropertyCollection propertyCollection)
{
Driver = summaryModel.Driver;
Id = summaryModel.Id;
IsDeleted = summaryModel.IsDeleted;
Colour = summaryModel.Colour == null ? null :
propertyCollection.Colours.Where(x => x.Id == summaryModel.Colour.Id).FirstOrDefault();
Colours = propertyCollection.Colours.Select(x => new SelectListItem { Value = x.Id.ToString(), Text = x.Name });
}
}
}
Must stress that Colour is a custom class but only has Id and Name properties
Colours doesn't relate to a specific car, it relates to cars in general, so rather than using a collection as your view model, create a wrapper:
class EditCarsViewModel
{
public IEnumerable<SelectListItem> Colours { get; set; }
public IList<CarViewModel> Cars { get; set; }
}
Then your view:
#model EditCarsViewModel
#for (int i = 0; i < Model.Cars.Length; i++)
{
<td>
#Html.DropDownListFor(m => Model.Cars[i].Colour, Model.Colours, "Unknown")
</td>
}
Any other CarViewModel properties will need their own input as well. HiddenFor can be used if they should be readonly:
#model EditCarsViewModel
#for (int i = 0; i < Model.Cars.Length; i++)
{
#Html.HiddenFor(m => Model.Cars[i].Id)
#Html.HiddenFor(m => Model.Cars[i].Driver)
<!-- etc. -->
<td>
#Html.DropDownListFor(m => Model.Cars[i].Colour.Id, Model.Colours, "Unknown")
</td>
}
And your controller:
[HttpPost]
public ActionResult SummarySaveAll(EditCarViewModel model)
{
// model.Cars should be populated
return View();
}
Note that an indexable collection, such as IList<T> should be used, as the form field names need to include the index to differentiate the items.
Edit by OP
The Colour class consists of a [Key] int Id property and a string Name property. For DropDownList items I had to make sure the Id property was specified on the m => Model.Cars[i].Colour.Id line otherwise that particular prop was coming back as null even though other items were coming through fine.
try
[HttpPost]
public ActionResult SummarySaveAll(IList<CarViewModel> summaries)
{
// Want to do stuff with summaries but it's always null
return View(summaries);
}
I've also added this model as a param for your view
This how you do it:
First my View which posts back to a controller named Home and an action named ListView:
#model List<MyModel>
#{
ViewData["Title"] = "Using a list as model";
}
<h1>#ViewData["Title"]</h1>
#using (Html.BeginForm("ListView", "Home", FormMethod.Post))
{
#Html.AntiForgeryToken()
<div>
<table>
<thead>
<tr>
<th width="1">
Name
</th>
<th width="1">
Description
</th>
</tr>
</thead>
<tbody>
#for (var i = 0; i < Model.Count; i++)
{
<tr>
<td width="1">
#Html.DisplayFor(m => Model[i].Name)
</td>
<td width="1">
#Html.TextBoxFor(m => Model[i].Description)
</td>
</tr>
}
</tbody>
</table>
<div>
<input type="submit" value="Save Changes" class="btn" />
#Html.ActionLink("Cancel Changes", "Index", null, new { #class = "btn" })
</div>
</div>
}
Notice how I used an indexer to render the controls [i]
This is my model:
public class MyModel
{
public string Name { get; set; }
public string Description { get; set; }
}
This is my controller action:
[HttpPost]
public IActionResult ListView(IEnumerable<MyModel> model)
{
return View(model);
}
And this is the result:
Related
I'm getting myself acquainted with ASP.NET MVC but i'm running into something probably trivial. I have a model called ToDoList, this is a complex type with a list of ToDoItems:
public class ToDoList
{
public Guid Id {get;set;}
public string Name { get; set; }
public virtual ICollection<ToDoItem> Items {get;set;}
}
public class ToDoItem
{
public int Id { get; set; }
public string Task { get; set; }
public bool IsDone { get; set; }
public virtual ToDoList ToDoList { get; set; }
}
My Details page with form looks like this:
#model DataLayer.TomTest.Entities.ToDoList
<h2>#Model.Name</h2>
#using (#Html.BeginForm())
{
#Html.AntiForgeryToken()
<table>
<tr>
<th>
#Html.DisplayNameFor(model => model.Items.First().Id)
</th>
<th>
#Html.DisplayNameFor(model => model.Items.First().Task)
</th>
<th>
#Html.DisplayNameFor(model => model.Items.First().IsDone)
</th>
</tr>
#foreach (var toDoItem in Model.Items)
{
<tr>
<td>
#toDoItem.Id
</td>
<td>
#Html.EditorFor(model => toDoItem.Task)
</td>
<td>
#Html.EditorFor(model => toDoItem.IsDone, new {htmlAttributes = new {#Style = "margin-left: 10px;"}})
</td>
</tr>
}
</table>
<input type="submit" value="Save" class="btn btn-default"/>
}
And this is the method it posts to:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Details([Bind(Include = "Id,Name,Items")] ToDoList todoList)
{
if (ModelState.IsValid)
{
_context.Entry(todoList).State = EntityState.Modified;
await _context.SaveChangesAsync();
return View();
}
return View();
}
As you can see I included the [Bind] attribute as I read somewhere that would ensure i get the correct properties passed. When I debug this however, only the Id property is filled the rest remains null.
What can I do to fix this? Is it a mistake in the View? Or is it possible Entity Framework isn't setup correctly?
Thanks in advance for your help.
Model binding to a list doesn't work with a foreach; you need to use a for loop instead.
You'll also need hidden inputs for any properties which don't have editors within the loop.
#for (int index = 0; index < Model.Items.Count; index++)
{
<tr>
<td>
#Html.HiddenFor(m => m.Items[index].Id)
#Model.Items[index].Id
</td>
<td>
#Html.EditorFor(m => m.Items[index].Task)
</td>
<td>
#Html.EditorFor(m => m.Items[index].IsDone, new { htmlAttributes = new { #Style = "margin-left: 10px;" } })
</td>
</tr>
}
ASP.NET Wire Format for Model Binding to Arrays, Lists, Collections, Dictionaries - Scott Hanselman's Blog
I create a table in my CSHTML, I want to pass the array of nr of items == aantal back to my controller however this doesn't seem to work. Any idea whats wrong or why I get a null reference in my controller?
CSHTML
#using (Html.BeginForm("OrderConfirm", "Beurs", new { vm = Model.Aantal }, method: FormMethod.Post))
{
<table class="table table-striped table-condensed table-bordered">
<tr>
<th>
Naam
</th>
<th>
Prijs
</th>
<th>
Minimum prijs
</th>
<th>
Factor
</th>
<th> Actie</th>
<!--
<th>Edit</th>-->
</tr>
#foreach (var item in Model.ItemLijstVm)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Naam)
</td>
<td>
€ #Html.DisplayFor(modelItem => item.Prijs)
</td>
<td>
€ #Html.DisplayFor(modelItem => item.MinimumPrijs)
</td>
<td>
#Html.DisplayFor(modelItem => item.Factor)
</td>
<td>
#Html.TextBoxFor(m => m.Aantal[item.Id - 1], new {type = "number" })
</td>
</tr>
}
</table>
<input type="submit" value=" Bevestig bestelling " width="120" />
}
ViewModel
public class BeursLijstViewModel
{
public IEnumerable<BeursItemViewModel> ItemLijstVm{get; set;}
public string Naam { get; set; }
public double Crash { get; set; }
//References naar animated gif
public bool Event { get; set; }
public string GifPath { get; set; }
public int[] Aantal { get; set; }
public int VerhoogAllePrijzen { get; set; }
public double Totaal { get; set; }
public SelectListItem Categorie { get; set; }
public BeursLijstViewModel(Beurs beurs)
{
ItemLijstVm= beurs.Items.Select(g => new BeursItemViewModel(g));
Naam = beurs.Naam;
Aantal = new int[beurs.Items.Count()];
Totaal = beurs.Totaal;
}
}
Controller
[HttpPost]
public ActionResult OrderConfirm(int[] vm) //VM is null but should be array
{
//Some more code
}
The reference I get on my post from my model is null, but if i declare it in my foreach loop like this, it works. I really don't have a clue what goes wrong:
#using (Html.BeginForm("Add", "Beurs", new { id = item.Id, aantal = Model.Aantal }, method: FormMethod.Post))
{
#Html.TextBoxFor(m => m.Aantal[item.Id - 1], new { type = "number" })
<input type="submit" value=" + " width="120"/>
}
I think you have to pass back the actual model not just array expecting to get result, since when it binds it goes several layers like name='Model.item[z]'
[HttpPost]
public ActionResult OrderConfirm(BeursLijstViewModel vm)
{
foreach (var item in vm.ItemLijstVm)
{
var response= vm.Aantal[item.Id - 1]
}
}
#using (Html.BeginForm("OrderConfirm", "Beurs", new { vm = Model.Aantal }, method: FormMethod.Post))
changing the above part in combination with the answer of #COLD TOLD to
#using (Html.BeginForm("OrderConfirm", "Beurs"))
fixed my problem. Thanks for the help!
I have a Page that contains 3 forms each form is displayed depending on the selected value of the radio button in the previous form (i use viewbag to control the visibility hope to find something better) and all that forms use the same view model .Is that possible to post the 3 forms to the same action and make the ViewModel holds all the selected Values to be send to another page or even stored in database ? i tried to do so put after each post the old selected value became null in the ViewModel.
I also tried the solution posted here but still cannot hold all selected values together in the ViewModel so what is the best way to store my selected values to later use ?
RouteList.cs my ViewModel
public class RoutesList
{
public int ID { get; set; }
public List<Route> Routes
{
get
{
Queries.BookingHandler handler = new Queries.BookingHandler();
return handler.GetAllRoutes();
}
set { }
}
public List<Route> OppositeRoutes
{
get
{
Queries.BookingHandler handler = new Queries.BookingHandler();
return handler.GetRoutDirections(long.Parse(SelectedRouteID));
}
set { }
}
public List<RouteStation> RouteStations
{
get
{
Queries.BookingHandler handler = new Queries.BookingHandler();
return handler.GetRouteStations(long.Parse(SelectedOppositeRouteID));
}
set { }
}
public string SelectedRouteID { get; set; }
public string SelectedOppositeRouteID { get; set; }
public string SelectedFromStationID { get; set; }
public string SelectedToStationID { get; set; }
public int Count { get; set; }
}
and my Controller has an index action for both Get and post
public ActionResult Index()
{
return View(new RoutesList());
}
[HttpPost]
public ActionResult Index(RoutesList route, FormCollection frm)
{
if (!string.IsNullOrEmpty(route.SelectedRouteID))
ViewBag.isDirection = true;
if (!string.IsNullOrEmpty(route.SelectedOppositeRouteID))
ViewBag.isStations = true;
if(!string.IsNullOrEmpty(route.SelectedFromStationID)&&!string.IsNullOrEmpty(route.SelectedToStationID))
return RedirectToAction("Index", "Time", new { id = route.SelectedRouteID });
return View(route);
}
and my View Index.cshtml
#model BusStarBackend.Models.RoutesList
#using (Html.BeginForm())
{
<table class="table table-hover">
<tr>
<th>Trips</th>
<th>Price</th>
</tr>
#foreach (var item in Model.Routes)
{
<tr>
<td>
#Html.RadioButtonFor(m => m.SelectedRouteID, item.RouteID)
#Html.DisplayFor(model => item.RouteName)
</td>
<td>#Html.DisplayFor(m => item.TicketPrice)</td>
<td> </td>
</tr>
}
</table>
<input type="submit" value="next" class="btn btn-default" />
}
#{
using (Html.BeginForm())
{
if (ViewBag.isDirection != null && ViewBag.isDirection)
{
<table class="table">
<tr>
<th>
Please selected your Direction
</th>
<th></th>
</tr>
#foreach (var item in Model.OppositeRoutes)
{
<tr>
<td>
#Html.RadioButtonFor(m => Model.SelectedOppositeRouteID, item.RouteID)
#Html.DisplayFor(modelItem => item.RouteName)
</td>
</tr>
}
</table>
<input type="submit" value="next" class="btn btn-default" />
}
}
}
#{
if (ViewBag.isStations != null && ViewBag.isStations)
{
using (Html.BeginForm())
{
<div id="stations">
<table class="table">
<tr>
<th>
From Station
</th>
<th>
To Station
</th>
<th></th>
</tr>
#foreach (var item in Model.RouteStations)
{
<tr>
<td>
#Html.RadioButtonFor(model => model.SelectedFromStationID, item.StationID)
#Html.DisplayFor(modelItem => item.Station.Name)
</td>
<td>
#Html.RadioButtonFor(model => model.SelectedToStationID, item.StationID)
#Html.DisplayFor(modelItem => item.Station.Name)
</td>
</tr>
}
</table>
<input type="submit" value="next" class="btn btn-default" />
</div>
}
}
}
For the "easiest" fix, you should use your "if's" before the begin form, something like this:
#model BusStar.Models.RoutesList
#if (ViewBag.isDirection != null && ViewBag.isDirection)
{...
#using (Html.BeginForm())
{.....
For a better solution you should use the 'Html.Action' method, build a partial view that contains your each of your forms, and render only the one you need based on the value from radio button.
Something like this:
2 partials view for each form - this is for the direction form: (called _directionForm.cshtml)
#model BusStar.Models.RoutesList
<table class="table">
<tr>
<th>
Please selected your Direction
</th>
<th></th>
</tr>
#foreach (var item in Model.OppositeRoutes)
{
<tr>
<td>
#Html.RadioButtonFor(m => Model.SelectedOppositeRouteID, item.RouteID)
#Html.DisplayFor(modelItem => item.RouteName)
</td>
</tr>
}
</table>
<input type="submit" value="next" class="btn btn-default" />
Your controller:
public ActionResult Index()
{
return View(new RoutesList());
}
public ActionResult PartialForm(RoutesList route)
{
if (!string.IsNullOrEmpty(route.SelectedRouteID))
return view("__directionForm", route);
return view("...", route); //your other view
}
[HttpPost]
public ActionResult Index(RoutesList route, FormCollection frm)
{
//if (!string.IsNullOrEmpty(route.SelectedRouteID)) ViewBag.isDirection = true;
//if (!string.IsNullOrEmpty(route.SelectedOppositeRouteID)) ViewBag.isStations = true;
if(!string.IsNullOrEmpty(route.SelectedFromStationID)&&!string.IsNullOrEmpty(route.SelectedToStationID))
return RedirectToAction("Index", "Time", new { id = route.SelectedRouteID });
return View(route);
}
And in your old view replace the two forms with:
Html.Action("PartialForm", Model)
I'm still trying to get my head wrapped around using ViewModels and IEnumerable/List as properties. I'm obviously missing something that is not allowing me to see my IEnumerable in my View.
What I'm ultimately trying to do is have a view that will show a list of unassigned users not assigned to a Group (called Patrols in this case and would have PatrolId=0). The table will have a checkbox next to each member. Above that table will be a DropDownList of the available Patrols. The Admin will come to the page to see those that aren't assigned, select a Patrol from the DDL at the top, check the users he want's to assign to that Patrol, and then submit the form that will pass the PatrolID from the DDL and find all the selected members in order to update their record with that PatrolId.
In the View below when I go to loop through the Scout property, I would assume I would be able to use Model.Scout in this part in order to loop through and write out all the members in that IEnumerable.
#for (var i = 0; i < Model.Count(); i++)
However, when I try to use intellisense to see the Scout property when using the Model, I don't see the property. In addition to that I would think I would need to first check the Scout property to see if there is a count before I write out all the rows. Again, I can't see the Scout property in order to check it's count.
ViewModel
public class PatrolMemberViewModel
{
[Key]
public int MemberId { get; set; }
public int PatrolId { get; set; }
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Phone")]
public string PhonePrimary { get; set; }
[Display(Name = "Email")]
public string EmailPrimary { get; set; }
public bool IsSelected { get; set; }
}
public class PatrolUnassignedViewModel
{
public SelectList Patrols { get; set; }
public IEnumerable<PatrolMemberViewModel> Scout { get; set; }
}
Controller
// GET:
public ViewResult Unassigned()
{
PatrolUnassignedViewModel unassinged = new PatrolUnassignedViewModel();
unassinged.Patrols = new SelectList(repository.SelectAllPatrols());
unassinged.Scout = repository.SelectAllUnassigned();
return View(unassinged);
}
Repository
public IEnumerable<PatrolMemberViewModel> SelectAllUnassigned()
{
using (DataContext db = new DataContext())
{
var results = (from p in db.Person
where p.IsActive == true
&& p.IsScout == true
&& p.PatrolId == 0
select new PatrolMemberViewModel
{
MemberId = p.PID,
FirstName = p.FirstName ?? string.Empty,
LastName = p.LastName ?? string.Empty,
EmailPrimary = p.EmailPrimary ?? string.Empty,
PhonePrimary = p.PhonePrimary ?? string.Empty,
PatrolId = p.PatrolId,
IsSelected = false
}
).OrderBy(o => o.LastName).ThenBy(o => o.FirstName).ToList();
return results;
}
}
public IEnumerable<PatrolName> SelectAllPatrols()
{
return db.PatrolNames;
}
View
#model IList<ProjectName.ViewModels.PatrolUnassignedViewModel>
#{
ViewBag.Title = "Unassigned";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Patrols</h2>
<h4>Assign Scouts to a Patrol.</h4>
#using (Html.BeginForm("Update", "Patrol", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(false, "", new { #class = "alert alert-danger" })
<table class="table table-bordered table-striped table-hover table-condensed tbackground">
<tr>
<th class="text-center">
</th>
<th class="text-center">
First Name
</th>
<th class="text-center">
Last Name
</th>
<th class="text-center">
Email
</th>
<th class="text-center">
Phone
</th>
</tr>
#for (var i = 0; i < Model.Count(); i++)
{
<tr>
<td class="text-center">
#Html.CheckBoxFor(m => m[i].IsSelected)
</td>
<td>
#Html.DisplayFor(m => m[i].FirstName)
</td>
<td>
#Html.DisplayFor(m => m[i].LastName)
</td>
<td>
#Model[i].EmailPrimary
</td>
<td class="text-center">
#Html.DisplayFor(m => m[i].PhonePrimary)
</td>
</tr>
}
</table>
<div class="control-wrapper">
<input type="submit" id="btnSubmit" value="Assign Scouts" class="btn btn-success" />
</div>
}
<p> </p>
You controller is returning a single instance of class PatrolUnassignedViewModel
public ViewResult Unassigned()
{
PatrolUnassignedViewModel unassinged = new PatrolUnassignedViewModel();
unassinged.Patrols = new SelectList(repository.SelectAllPatrols());
unassinged.Scout = repository.SelectAllUnassigned();
return View(unassinged);
}
Your view is expecting an IList
#model IList<ProjectName.ViewModels.PatrolUnassignedViewModel>
When it should be expecting
#model ProjectName.ViewModels.PatrolUnassignedViewModel
Your scout is an IEnumerable so doesn't have a count method so should be
public IList<PatrolMemberViewModel> Scout { get; set; }
public IList<PatrolMemberViewModel> SelectAllUnassigned()
{
}
You should be doing your loop like this
#for (var i = 0; i < Model.Scout.Count(); i++)
{
<tr>
<td class="text-center">
#Html.CheckBoxFor(m => m.Scout[i].IsSelected)
</td>
<td>
#Html.DisplayFor(m => m.Scout[i].FirstName)
</td>
<td>
#Html.DisplayFor(m => m.Scout[i].LastName)
</td>
<td>
#Model.Scout[i].EmailPrimary
</td>
<td class="text-center">
#Html.DisplayFor(m => m.Scout[i].PhonePrimary)
</td>
</tr>
}
Am I missing something here?
From your GET action method, you are passing a single object of PatrolUnassignedViewModel. But your view is bound to a collection of PatrolUnassignedViewModel. So change your view to be like
#model PatrolUnassignedViewModel
Now you can use the Model.Scout property which is a collection.
#model PatrolUnassignedViewModel
<h2>Total : #Model.Scout.Count()</h2>
#foreach(var item in Model.Scout)
{
<label>#item.FirstName</label>
}
I need additional eyes to see:
What I am doing wrong as I try to pass a collection of objects to a MVC controller and all I get is sgList = null.
How can I check so that I only save the rows that being changed.
[HttpPost]
public ActionResult Index(IList<EZone_ServiceGroup> sgList)
{
try
{
foreach (EZone_ServiceGroup sg in sgList)
svcGroupRepo.UpdateServiceGroup(sg);
return RedirectToAction("Index");
}
catch
{
return View();
}
}
View:
#model IEnumerable<KTCEzone.Domain.Entities.EZone_ServiceGroup>
#{
ViewBag.Title = "Index";
}
#using (Html.BeginForm())
{
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
<div class="row">
<table class="table table-condensed table-bordered table-hover table-striped small" id="sgTable">
<tr>
<th class="col-sm-12">#Html.DisplayNameFor(model => model.GroupID)</th>
<th>#Html.DisplayNameFor(model => model.GroupName)</th>
<th>#Html.DisplayNameFor(model => model.ParentGroupID)</th>
<th>#Html.DisplayNameFor(model => model.Active)</th>
<th>#Html.DisplayNameFor(model => model.OrderIndex)</th>
</tr>
#{var items = Model.ToArray();}
#for (int i = 0; i < items.Length; i++)
{
<tr>
<td>#Html.DisplayFor(modelItem => items[i].GroupID)</td>
<td>#Html.EditorFor(modelItem => items[i].GroupName) </td>
<td>#Html.EditorFor(modelItem => items[i].ParentGroupID) </td>
<td>#Html.CheckBoxFor(modelItem => items[i].Active) </td>
<td>#Html.EditorFor(modelItem => items[i].OrderIndex) </td>
</tr>
}
</table>
</div>
}
Model:
public class EZone_ServiceGroup
{
public int GroupID { get; set; }
public string GroupName { get; set; }
public bool Active { get; set; }
public int OrderIndex { get; set; }
public int ParentGroupID { get; set; }
}
Change your model to #model IList<KTCEzone.Domain.Entities.EZone_ServiceGroup>, and remove #{var items = Model.ToArray();} from the view and use
#for (int i = 0; i < Model.Count; i++)
{
<tr>
<td>#Html.DisplayFor(m => m[i].GroupID)</td>
<td>#Html.EditorFor(m=> m[i].GroupName)</td>
<td>#Html.EditorFor(m=> m[i].ParentGroupID)</td>
<td>#Html.CheckBoxFor(m=> m[i].Active) </td>
<td>#Html.EditorFor(m=> m[i].OrderIndex) </td>
</tr>
}
which will correctly name your elements. If you cannot change the collection to IList, then you need to use a custom EditorTemplate for the type of the model, and use in conjunction with #Html.EditorFor()
As for "How can I check so that I only save the rows that being changed", all controls will be posted back, so you need to compare the posted values with the original values in the controller.