Model item bind in List - c#

I have passed the model to partial view,And then i need to bind some text fields to the model in the view.
#model SRINews.Domain.NewsTotal
#using (Html.BeginForm("UpdateNewsItem", "Home", FormMethod.Post))
{
<table class="table table-borderless table-cart" id="mytable" data-addclass-on-smdown="table-sm">
<tbody>
#foreach (var item in Model.items)
{
<tr class="newsRow" id="#item.ItemId">
<td class="cart-img nostretch">
<img src="#item.ImageUrl" alt="">
</td>
</tr>
<tr>
<td>
<input type="text" class="form-control" placeholder="Personalized Name">
//#Html.TextboxFor(x=>x)
// I want to bind PersonalizedName to model
</td>
</tr>
<tr>
<td>
<input type="text" class="form-control" placeholder="Country">
// I want to bind Country to model
</td>
</tr>
}
</tbody>
</table>
<input type="submit" class="btn btn-primary" value="Personal Details" />
}
Model
public class Items
{
public int ItemId { get; set; }
public string ItemCode { get; set; }
public string PersonalizedName {get;set;}
public string Country {get;set;}
}
public class NewsTotal
{
public int BaseItem { get; set; }
public string BaseName {get;set;}
public List<Items> items { get; } = new List<Items>();
}
Public ActionResult UpdateNewsItem(NewsTotal nTotal)
{
return View();
}

You want to use a traditional for loop so you can use the index to bind to your List<T> in the model, you'll also need to make items mutable, so you'll need to have a set for it as well or else you won't be able to submit anything:
//You'll need to make this mutable, so it can post the edited values
public List<Items> items { get; set; } = new List<Items>();
Then in your View:
#for(int i = 0; i < Model.items.Count; i++)
{
#Html.HiddenFor(x => Model.items[i].ItemId)
#Html.HiddenFor(x => Model.items[i].ItemCode)
<tr class="shoppingCartRow" id="#Model.items[i].ItemId">
<td class="cart-img nostretch">
<img src="#Model.items[i].ImageUrl" alt="">
</td>
</tr>
<tr>
<td>
#Html.TextboxFor(x=> Model.items[i].PersonalizedName, new { #placeholder = "Personalized Name"})
</td>
</tr>
<tr>
<td>
#Html.TextboxFor(x=> Model.items[i].Country, new { #placeholder = "Country"})
</td>
</tr>
}

Related

Return the same ViewData twice from controller to View result in NULL

Hello guys I am still really new to Asp.net and C#. I am writing a management system overseeing office activities. I ran into a problem where I am passing a ViewModel Object that contains a list, Case and int to the view from the controller. If the newly created case has the same ID, it would return the same ViewModel Object back to the POST controller, but when the object got passed from view to the post controller,every list Object is null except for primitive types and the Case. I dont understand why the same object is passed around but when it passes to the controller from the view all the list that was just used to populate the html are null, is this because of injection or just how model binding works? THanks a lot
The View just has a for loop displaying every item in the TopTenList. it has the #model binding statement on the top.
Heres the View
#model LabManager.InputModels.CaseInputModel
#inject LabManager.Data.ApplicationDbContext _dbContext
#using LabManager.ModelExtensions
#{
ViewData["Title"] = "Overview";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#section Stylesheet{
<link rel="stylesheet" href="~/css/Case/index.css" />
}
<div id="home-view">
<div class="card">
<div class="card-header">
<a class="card-link" data-toggle="collapse" href="#topTen">
Top Ten Priority Cases
</a>
</div>
<div id="topTen" class="collapse show" data-parent="#home-view">
<div class="card-body">
<table class="table">
<thead>
<tr>bunch of names</tr>
</thead>
<tbody>
#{int i = 1;}
#foreach (var item in Model.TopTenList)
{
show somethings.
}
Heres the controller.
public ActionResult Create(int setPriority)
{
CaseInputModel input = new CaseInputModel {
SelectedPriority = setPriority,
};
input.SetTopTenList(_dbContext);
return View(input);
}
// POST: Takes info from the input model and creates a new case in the db
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(CaseInputModel inputs)
{
//if case id already exist in the database, return view()
if (DbContext.Cases.Any(m => m.CaseId == inputs.NewCase.CaseId))
{
inputs.ErrorMessage = "Error: Case ID already exists";
return View(inputs);
}
Hers the Model:
public class Case
{
[Required]
[RegularExpression("[^0-9]")]
public int Id { get; set; }
public int CaseId { get; set; }
public Status Status { get; set; }
public string Notes { get; set; }
}
Heres the CaseInputModel(ViewModel Object)
public class CaseInputModel
{
public CaseInputModel()
{
NewCase = new Case();
RegularCaseList = new List<Case>();
}
public string ErrorMessage { get; set; }
public int SelectedPriority { get; set; }
public Case NewCase { get; set; }
public List<Case> RegularCaseList { get; set; }
public List<Case> TopTenList { get; set; }
public void SetTopTenList(ApplicationDbContext dbContext)
{
TopTenList = PriorityNodeExtensions.ToSortedList(dbContext.TopTens,
dbContext);
}
Firsly,you need know that for each property of the complex type, model binding looks through the sources for the name pattern prefix.property_name. If nothing is found, it looks for just property_name without the prefix.
Reference:
https://learn.microsoft.com/en-us/aspnet/core/mvc/models/model-binding?view=aspnetcore-3.1#complex-types
Here is a simple demo about how to pass the data to the controller:
View:
#model InputModel
<form asp-action="Create">
<table class="table">
<thead>
<tr>bunch of names</tr>
</thead>
<tbody>
<tr>
<td>SelectedPriority</td>
<td><input asp-for="#Model.SelectedPriority" /></td>
</tr>
<tr>
<td>New Case</td>
<td><input asp-for="#Model.NewCase.Id" /></td>
<td><input asp-for="#Model.NewCase.CaseId" /></td>
<td><input asp-for="#Model.NewCase.Notes" /></td>
</tr>
#{int i = 0;}
#foreach (var item in Model.TopTenList)
{
<tr>
<td>TopTenList_#i</td>
<td><input name="TopTenList[#i].Id" asp-for="#item.Id" /></td>
<td><input name="TopTenList[#i].CaseId" asp-for="#item.CaseId" /></td>
<td><input name="TopTenList[#i].Notes" asp-for="#item.Notes" /></td>
</tr>
i++;
}
</tbody>
</table>
<input type="submit" value="create" />
</form>
Result:
Update:
return view with model it could render the model successfully,but you could not render the TopTenList.Be sure you have create the input for the model properties and debug the action to check if you have passed the TopTenList to the post method.If you only render the data by using #Model.Property it would not pass to the backend.
View:
#model CaseInputModel
<form asp-action="Create">
<table class="table">
<thead>
<tr>bunch of names</tr>
</thead>
<tbody>
#if (Model.ErrorMessage != null)
{
#Model.ErrorMessage
}
<tr>
<td>SelectedPriority</td>
<td><input asp-for="#Model.SelectedPriority" /></td>
</tr>
<tr>
<td>New Case</td>
<td><input asp-for="#Model.NewCase.Id" /></td>
<td><input asp-for="#Model.NewCase.CaseId" /></td>
<td><input asp-for="#Model.NewCase.Notes" /></td>
</tr>
#{int i = 0;}
#foreach (var item in Model.TopTenList)
{
<tr>
<td>
#item.Id
<input name="TopTenList[#i].Id" asp-for="#item.Id" hidden />
</td>
<td>
#item.CaseId
<input name="TopTenList[#i].CaseId" asp-for="#item.CaseId" hidden />
</td>
<td>
#item.Notes
<input name="TopTenList[#i].Notes" asp-for="#item.Notes" hidden />
</td>
</tr>
i++;
}
</tbody>
</table>
<input type="submit" value="create" />
</form>
Result:

Can't pass an enumerable model to a controller?

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:

mvc use more than one form in one page filtered with the selection of radio Button

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)

How can i get my ListBox working correctly

I am trying to get to get a list box working correctly, which will function very much like this.
The Available exercises are seeded from Migrations/Configuration. I need to be able to add multiples of each exercise from Available to Selected Regime. I am using a view model to access multiple models from another project(User and RegimeItems). However im simply at a loss at what to do next.
Controller.cs
[HttpGet]
public ActionResult Exercise(int? id)
{
User user = db.Users.Find(id);
UserExerciseViewModel model = new UserExerciseViewModel { AvailableExercises = GetAllExercises(), RequestedExercises = new List<RegimeItem>() };
return View(model);
}
//Post
[HttpPost]
public ActionResult Index(UserExerciseViewModel model, string add, string remove, string send, int id)
{
User user = db.Users.Find(id);
//ModelState.Clear();
RestoreSavedState(model);
if (!string.IsNullOrEmpty(add))
AddExercises(model);
else if (!string.IsNullOrEmpty(remove))
SaveState(model);
return View(model);
}
void SaveState(UserExerciseViewModel model)
{
model.SavedRequested = string.Join(",", model.RequestedExercises.Select(p => p.RegimeItemID.ToString()).ToArray());
model.AvailableExercises = GetAllExercises().Except(model.RequestedExercises).ToList();
}
void RemoveExercises(UserExerciseViewModel model)
{
if (model.RequestedSelected != null)
{
model.RequestedExercises.RemoveAll(p => model.RequestedSelected.Contains(p.RegimeItemID));
model.RequestedSelected = null;
}
}
void AddExercises(UserExerciseViewModel model)
{
if (model.AvailableSelected != null)
{
var prods = GetAllExercises().Where(p => model.AvailableSelected.Contains(p.RegimeItemID));
model.RequestedExercises.AddRange(prods);
model.AvailableSelected = null;
}
}
void RestoreSavedState(UserExerciseViewModel model)
{
model.RequestedExercises = new List<RegimeItem>();
//get the previously stored items
if (!string.IsNullOrEmpty(model.SavedRequested))
{
string[] prodids = model.SavedRequested.Split(',');
var prods = GetAllExercises().Where(p => prodids.Contains(p.RegimeItemID.ToString()));
model.RequestedExercises.AddRange(prods);
}
}
public ViewResult Done()
{
return View();
}
public List<RegimeItem> GetAllExercises()
{
var items = db.RegimeItems.ToList();
}
UserExerciseViewModel.cs
namespace FaceToFaceWebsite.Models
{
public class UserExerciseViewModel
{
public List<RegimeItem> AvailableExercises { get; set; }
public List<RegimeItem> RequestedExercises { get; set; }
public int[] AvailableSelected { get; set; }
public int[] RequestedSelected { get; set; }
public string SavedRequested { get; set; }
}
}
Migrations/Configuration.cs
protected override void Seed(FaceToFace.Model.F2FData context)
{
var ahPose = new Pose { Name = "Ah" };
There are lots of other pieces of information that specify the pose/Exercise however its not relevant to the question.
View - Exercise.cs
<%using(Html.BeginForm()){ %>
<div>
<table>
<thead>
<tr>
<th>Available</th>
<th>
</th>
<th>Selected</th>
</tr>
</thead>
<tbody>
<tr>
<td valign="top">
<%=Html.ListBoxFor(model => model.AvailableExercises,
new MultiSelectList(Model.AvailableExercises, "RegimeItemID",
"Name", Model.AvailableSelected))%>
</td>
<td valign="top">
<input type="submit" name="add"
id="add" value=">>" /><br />
<input type="submit" name="remove"
id="remove" value="<<" />
</td>
<td valign="top">
<%=Html.ListBoxFor(model => model.RequestedSelected,
new MultiSelectList(Model.RequestedExercises, "RegimeItemID",
"Name", Model.RequestedSelected))%>
</td>
</tr>
</tbody>
</table>
<br />
<%=Html.HiddenFor(model=>model.SavedRequested) %>
</div>
<%} %>
What i am trying to do is allow exercises to assigned specifically to each user. These excercises have already been seeded. When this works correctly it should allow the exercises to accessed in a descending order.
Update
As per Stephens suggestions i have got the code to at least not throw any errors.
However this an image of what i am receiving on the Exercise page now.
UPDATE 2
As per Stephen's help i have made changes to the Excercise.cshtml view below.
#model FaceToFaceWebsite.Models.UserExerciseViewModel
#using (Html.BeginForm())
{
<div>
<table>
<thead>
<tr>
<th>Available</th>
<th>
</th>
<th>Selected</th>
</tr>
</thead>
<tbody>
<tr>
<td valign="top">
#Html.ListBoxFor(model => model.AvailableExercises, new MultiSelectList(Model.AvailableExercises, "RegimeItemID", "RegimeExercise", Model.AvailableSelected))
</td>
<td valign="top">
<input type="submit" name="add"
id="add" value=">>" /><br />
<input type="submit" name="remove"
id="remove" value="<<" />
</td>
<td valign="top">
#Html.ListBoxFor(model => model.RequestedSelected, new MultiSelectList(Model.RequestedExercises, "RegimeItemID", "RegimeExercise", Model.RequestedSelected))
</td>
</tr>
</tbody>
</table>
<br />
#Html.HiddenFor(model => model.SavedRequested)
</div>
}
I am now getting this as the listbox shown in the image below.
I am not sure if this problem resides in site.css however its more likely that it's not finding the exercises that have been seeded.
As per Stephens help i was able to get it to work, on the other hand it appears that im using one of the models incorrectly.Stephen has provided the information that got me to a error-less build, regardless of my failings before i posted the question. The final issue is not with css as mentioned in the comments, well it might be but the problem before that is that the are multiple models i need to use to get the exercises to show.
This is the viewmodel.
namespace FaceToFaceWebsite.Models
{
public class UserExerciseViewModel
{
public List<RegimeItem> AvailableExercises { get; set; }
public List<RegimeItem> RequestedExercises { get; set; }
public int[] AvailableSelected { get; set; }
public int[] RequestedSelected { get; set; }
public string SavedRequested { get; set; }
}
}
Controller.cs
//GET
[HttpGet]
public ActionResult Exercise(int? id)
{
User user = db.Users.Find(id);
UserExerciseViewModel model = new UserExerciseViewModel { AvailableExercises = GetAllExercises(), RequestedExercises = new List<RegimeItem>() };
return View(model);
}
//Post
[HttpPost]
public ActionResult Index(UserExerciseViewModel model, string add, string remove, string send, int id)
{
User user = db.Users.Find(id);
//ModelState.Clear();
RestoreSavedState(model);
if (!string.IsNullOrEmpty(add))
AddExercises(model);
else if (!string.IsNullOrEmpty(remove))
SaveState(model);
return View(model);
}
void SaveState(UserExerciseViewModel model)
{
model.SavedRequested = string.Join(",", model.RequestedExercises.Select(p => p.RegimeItemID.ToString()).ToArray());
model.AvailableExercises = GetAllExercises().Except(model.RequestedExercises).ToList();
}
void RemoveExercises(UserExerciseViewModel model)
{
if (model.RequestedSelected != null)
{
model.RequestedExercises.RemoveAll(p => model.RequestedSelected.Contains(p.RegimeItemID));
model.RequestedSelected = null;
}
}
void AddExercises(UserExerciseViewModel model)
{
if (model.AvailableSelected != null)
{
var prods = GetAllExercises().Where(p => model.AvailableSelected.Contains(p.RegimeItemID));
model.RequestedExercises.AddRange(prods);
model.AvailableSelected = null;
}
}
void RestoreSavedState(UserExerciseViewModel model)
{
model.RequestedExercises = new List<RegimeItem>();
//get the previously stored items
if (!string.IsNullOrEmpty(model.SavedRequested))
{
string[] prodids = model.SavedRequested.Split(',');
var prods = GetAllExercises().Where(p => prodids.Contains(p.RegimeItemID.ToString()));
model.RequestedExercises.AddRange(prods);
}
}
public ViewResult Done()
{
return View();
}
private List<RegimeItem> GetAllExercises()
{
return db.RegimeItems.ToList();
}
and the View.
#model FaceToFaceWebsite.Models.UserExerciseViewModel
#using (Html.BeginForm())
{
<div>
<table>
<thead>
<tr>
<th>Available</th>
<th>
</th>
<th>Selected</th>
</tr>
</thead>
<tbody>
<tr>
<td valign="top">
#Html.ListBoxFor(model => model.AvailableExercises, new MultiSelectList(Model.AvailableExercises, "RegimeItemID", "RegimeExercise", Model.AvailableSelected))
</td>
<td valign="top">
<input type="submit" name="add"
id="add" value=">>" /><br />
<input type="submit" name="remove"
id="remove" value="<<" />
</td>
<td valign="top">
#Html.ListBoxFor(model => model.RequestedSelected, new MultiSelectList(Model.RequestedExercises, "RegimeItemID", "RegimeExercise", Model.RequestedSelected))
</td>
</tr>
</tbody>
</table>
<br />
#Html.HiddenFor(model => model.SavedRequested)
</div>
}

Another null collection being passed to MVC Controller

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.

Categories

Resources