I'm new to C# MVC so please be patient. I'm having some trouble displaying output for the ViewBag. The application is a Fantasy Football webpage so I have three tables (right now, more to come) one for the basic player info (dbo.Player), one for player background (dbo.PlayerBackground), and one is just a definition table for the team (dbo.Team).
In one of my pages I have a name search and search by position and want to return information across these three tables.
public ActionResult Index()
{
var players = (from p in db.Players
join pb in db.PlayerBackgrounds on p.playerId equals pb.playerID
join t in db.Teams on p.teamAbbre equals t.teamAbbre
select new { playerID = p.playerId, playerName = p.name, position = p.position,
height = pb.height, weight = pb.weight, college = pb.college, dob = pb.dob,
imageUrl = pb.imageUrl, years = pb.years,
teamName = t.name
}).ToList();
ViewBag.data = players;
return View();
}
The query works fine but in index.cshtml I keep getting errors.
#foreach (var player in ViewBag.data)
{
<tr class="success ui-dragable playerRow" style="display: none;">
<td>
<input type="checkbox" />
</td>
<td>
#Html.ActionLink( (string)player.playerName, "Details", new { id = player.playerID }, new { #class = "detailsLink" })
</td>
<td>
#player.teamName
</td>
<td>
#player.position
</td>....
From the research I've done, this seems like it should work. I've tried it both with and without the (string) cast. Without the cast it gives me red squiggly saying I should cast and when I do I get:
Exception Details: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'object' does not contain a definition for 'playerName'
As I step through, I can watch player and it has the various properties just as it should. Any idea what I'm doing wrong?
The problem is that you are using an anonymous object. I can't really take the credit for that finding though, the answer I found was right here (Go give him an up-vote). Refer to it for complete details.
Essentially, the short of the story is that Anonymous objects are emitted as internal by the compiler. This causes problems when trying to use them as Razor views because they are compiled into a separate assembly by the ASP.Net runtime (internal only allows access in the same assembly).
So, the solution is to define a view model:
public class PlayerViewModel
{
// Replace with the actual type of playerId
public int playerId { get; set; }
// etc...
}
And use it in your controller:
public ActionResult Index()
{
var players = (from p in db.Players
join pb in db.PlayerBackgrounds on p.playerId equals pb.playerID
join t in db.Teams on p.teamAbbre equals t.teamAbbre
select new PlayerViewModel { playerID = p.playerId, ... }).ToList();
return View(players); // Use the strongly-typed model property for your view
// instead of ViewBag.data (It's recommended)
}
And finally in your view:
#* At the beginning of your view *#
#model IEnumerable<PlayerViewModel>
...
#foreach (var player in #Model)
{
<tr class="success ui-dragable playerRow" style="display: none;">
<td>
<input type="checkbox" />
</td>
<td>
#Html.ActionLink(player.playerName, "Details", new { id = player.playerID }, new { #class = "detailsLink" })
</td>
<td>
#player.teamName
</td>
<td>
#player.position
</td>....
Related
Before I start, I'm relatively new to coding and was due to start my first junior role in the upcoming future, so my current skill level is pretty basic.
I'm creating a personal C# ASP.NET MVC web application to track music events that users go to, and the artists that are playing at those events, and which of the artists a user sees. This is all done by the user manually adding events from an Indexed view of all events within the database (SQL Server) which is show in the image linked below.
https://i.stack.imgur.com/HOUGG.png
The controller action:
public ActionResult GetEvents()
{
return View(_trackerService.GetEvents());
}
The markup for the view:
#model IEnumerable<Tracker.Data.tbl_events>
#{
ViewBag.Title = "GetEvents";
}
<h2>GetEvents</h2>
<p>
#Html.ActionLink("Create New", "CreateEvent")
</p>
<table class="table">
<tr><h2>Upcoming Events</h2>
<th>
#Html.DisplayNameFor(model => model.Event_ID)
</th><th>
#Html.DisplayNameFor(model => model.Event_Name)
</th>
<th>
#Html.DisplayNameFor(model => model.Event_Date)
</th>
<th>
#Html.DisplayNameFor(model => model.Event_Location)
</th>
</tr>
#foreach (var item in Model.Where( x => x.Event_Date >= System.DateTime.Now).OrderBy(x => x.Event_Date))
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.Event_ID)
</td>
<td>
#Html.DisplayFor(modelItem => item.Event_Name)
</td>
<td>
#Convert.ToString(string.Format("{0:dd/MM/yyyy}", item.Event_Date)) #*Converts the DateTime data type that ASP.NET uses by default into a string with the format of Date Only (https://stackoverflow.com/a/34990313/12764653)*#
</td>
<td>
#Html.DisplayFor(modelItem => item.Event_Location)
</td>
<td>
#*#Html.ActionLink("Edit", "Edit", new { id = item.Event_ID }) | *#
#Html.ActionLink("Details", "GetEventDetails", new { Event_ID = item.Event_ID }) |
#Html.ActionLink("Lineup", "../Event/GetLineup", new { Event_ID = item.Event_ID, Event_Name = item.Event_Name }) |
#if ((System.Web.HttpContext.Current.User != null) && System.Web.HttpContext.Current.User.Identity.IsAuthenticated)
{#*Checks to see if there is a current user*#
#Html.ActionLink("Add to my Events", "../Event/AddToUser", new { User_ID = Convert.ToString(System.Web.HttpContext.Current.User.Identity.GetUserId()).GetHashCode(), Event_ID = item.Event_ID })}
#*#Html.ActionLink("Delete", "Delete", new { id = item.Event_ID })*#
</td>
</tr>
}
</table>
Once a user adds an event to their profile, it creates an entry into the database with the fields (Linked Below):
https://i.stack.imgur.com/YQqHT.png
The controller method for the 'Add To My Events' function:
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult AddToUser(tbl_eventhistory _event)
{
try
{
_trackerService.AddToUser(_event);
return RedirectToAction("GetEvents");
}
catch
{
return View();
}
}
I understand that with the way it is currently, this would most likely have to be done either in the controller or View, and it would be done to check 'tbl_eventhistory' to see if an entry exists with the current users User_ID and a specific Event_ID, however I'm unsure of how to actually do this so any help would be greatly appreciated.
EDIT: The table shown is an indexed view of 'tbl_events'. When a user adds an event to their profile from this view, it creates an entry in a different table called 'tbl_eventhistory' using the parameters for that specific event, which is related through a foreign key on the Event_ID (tbl_event's PK). When an event is added to tbl_eventhistory by the user, I want to remove the link 'Add To User' from the view for that specific event only.
You already have a Delete link in your Razor view - that seems to be good enough:
#Html.ActionLink("Delete", "../Event/DeleteEvent", new { id = item.Event_ID })
And then your EventController would look something anlog these lines:
[AcceptVerbs(HttpVerbs.Get | HttpVerbs.Post)]
public ActionResult DeleteEvent(int id)
{
try
{
_trackerService.DeleteForUser(HttpContext.Current.User.Identity.GetUserId(), _event); // this is where you verify whether currently logged in user has access and delete their associated event.
//depending on your data access framework of choice you should aim to fire a query like so:
//DELETE FROM table WHERE User_Id = :user_id AND Event_Id = :id
//that would cover you for users deleing only items belonging to them
return RedirectToAction("GetEvents");
}
catch
{
return View();
}
}
now to point out two security issues with your AddEvent code:
#Html.ActionLink("Add to my Events", "../Event/AddToUser", new { User_ID = Convert.ToString(System.Web.HttpContext.Current.User.Identity.GetUserId()).GetHashCode(), Event_ID = item.Event_ID })}
GetHashCode() is not what you think it is: using it as identifier is unsecure because collisions on default implementation are very likely
there's no need to even pass the user Id back from client: your controllers have access to HttpContext object so you should be able to just grab that User_ID on server side
and to attempt to answer your questions in title:
How to check if an entry exists in a separate SQL Server table,
I don't believe you need it for purposes of deleting events - see comments in the code above
and change which function is linked depending on whether it exists or not
I an not sure if I get this part. I feel you might be able to answer both of these yourself after having a look through this other SO question on SQL JOIN. Otherwise I (and the community here) will need more details on your actual use case and goal
Managed to finally find a solution. Please forgive me if the terminology is incorrect.
I already had a function to return an indexed view of a specific users events (DAO Class code below)
public IList<tbl_eventhistory> GetUserEvents(string User_ID)
{
IQueryable<tbl_eventhistory> _eventHistory;
_eventHistory = from tbl_eventhistory in _context.tbl_eventhistory where tbl_eventhistory.User_ID == User_ID select tbl_eventhistory;
return _eventHistory.ToList<tbl_eventhistory>();
}
Thinking how to use this data in a view with a different bound Model, I thought about using a ViewBag and came across this: "How to check if a List in a ViewBag contains string". From this answer I modified my 'GetEvents' function to the following:
public ActionResult GetEvents()
{
List<string> UsersEvents = new List<string>();
foreach (var item in _trackerService.GetUserEvents(User_ID))
{
UsersEvents.Add(item.Event_ID.ToString());
}
ViewBag.MyEvents = _trackerService.GetUserEvents(User_ID);
ViewBag.MyEvents = UsersEvents;
return View(_trackerService.GetEvents());
}
If my understanding is correct, this creates a List which is populated by calling the 'GetUserEvents' action and loops through each entry in the result. Moving this list to a ViewBag then allows me to modify the 'GetEvents' ActionLinks to include an additional IF statement inside the existing statement checking whether a user is logged in:
#if ((System.Web.HttpContext.Current.User != null) && System.Web.HttpContext.Current.User.Identity.IsAuthenticated)
if (((IList<string>)ViewBag.MyEvents).Contains(item.Event_ID.ToString())){
<p>TEST</p> }
else{
#Html.ActionLink("Add to my Events", "../Event/AddToUser", new { User_ID = Convert.ToString(System.Web.HttpContext.Current.User.Identity.GetUserId()).GetHashCode(), Event_ID = item.Event_ID })}
}
Which resulted in "Test" being shown instead of the link to 'Add To User' where appropriate (shown below with the final event not being present in 'tbl_eventhistory'):
http://icecream.me/b459c9d17854b081a0804692f67c3aa3
I understand this is probably the furthest from being the most efficient way of doing it, but I'm just glad it now works after a LONG time trying to figure it out.
I have the following in a controller :
outputmodel.Add(new SP_RESULTS.RS_Plans()
{
id = Convert.ToDecimal(SPOutput["id"]),
name = Convert.ToString(SPOutput["name"]),
code = Convert.ToString(SPOutput["code"]),
from = Convert.ToDateTime(SPOutput["from"]),
to = Convert.ToDateTime(SPOutput["to"]),
days = Convert.ToDecimal(SPOutput["days"]),
type_id = convert.YoString(SPOutput["type_id"]),
package = Convert.ToString(SPOutput["package"]),
day = Convert.ToDecimal(SPOutput["day"]),
charge = SPOutput["charge"] as decimal?,
type = Convert.ToString(SPOutput["type"]),
percentage= SPOutput["percentage"] as decimal?,
taxes = Convert.ToDecimal(SPOutput["taxes"]),
order = Convert.ToDecimal(SPOutput["order"]),
level = SPOutput["level"] as decimal?,
Column15 = Convert.ToDecimal(SPOutput[15]),
type_order = (SPOutput["type_order"]) as decimal?,
adults = SPOutput["adults"] as decimal?,
});
var order = outputmodel.OrderBy(c => c.from);
ViewData["RS_Output"] = order;
grabbing output from an MS SQL stored procedure and storing in a viewdata (ordered by the FROM date).
My HTML has the following line to start to build the table
#foreach (var item in ViewData["RS_Output"] as Enumerable<app.Models.SP_RESULTS.RS_Plans>)
{
//basic <tr> <td> </td> </tr> table setup, using #item.variablename to pull info from the viewdata.
}
The output I am trying to achieve is for every TYPE under CODE, where the from date => current date, list the room type /package name etc.
and the output I am getting is
what I am trying to get is
What I think I need is a foreach after the current foreach, but I cannot for the life of me figure it out in my head.
I've changed the
var order line in my controller to now read
var order = outputmodel.OrderBy(c => c.rate);
..and I've put the HTML table create code in an if loop
#foreach (var item in ViewData["RS_Output"] as Enumerable<app.Models.SP_RESULTS.RS_Plans>)
{
if (item.to >= DateTime.now)
{
//basic <tr> <td> </td> </tr> table setup, using #item.variablename to pull info from the viewdata.
}
}
.. but, as I say, I am stumped.
I think I need another foreach within the newly created if loop, but I cannot figure out how.
#foreach (var item in ViewData["RS_Output"] as Enumerable<app.Models.SP_RESULTS.RS_Plans>)
{
if (item.to >= DateTime.now)
{
//other table headers/data
<tr>
<td>
#item.type
</td>
</tr>
<tr>
<td>
Room Type
</td>
<td>
Package / Service
</td>
<td>
Availablility
</td>
<td>
Charge
</td>
<td>
PAX
</td>
<td>
Level
</td>
</tr>
<tr>
==> #foreach (subitem = item.type)
==> {
==> foreach (item.type)
==> {
<td>
#item.type_id
</td>
<td>
#item.package
</td>
<td>
#item.Column15
</td>
<td>
#item.charge
</td>
<td>
#item.adults
</td>
<td>
#item.level
</td>
==> }
==> }
</tr>
}
}
can someone please advise?
thanks
UPDATE:
Hi, what I found worked was, if I create a variable called string previous_type =" " , and another called decimal previous_id =0 , then, in the view, I can amend with
if (item.to >= item.checkdate)
{
if ((previous_id != item.id) && (previous_type != item.type.ToString()) )
{
//some more code
if (item.type.ToString().Equals(previous_type) == false)
{
previous_type = item.type.ToString();
previous_date_from = item.date_from;
}
//etc
}
Thanks everyone for their help
OK, I think what you want is to first group the data, then show a table which then shows 'sub-tables' for each type of accomodation?
if so, then yes you can do this with nested foreach loops, but you'd still be better off strongly typing your view and doing the grouping stuff in the controller (or possibly better in some sort of service layer so it can be more easily tested/re-used)... but to get you started, something like this:
Models:
//Raw data
public class DataRowModel
{
public int Id { get; set; }
public string Class{ get;set;}
public string Description { get; set; }
public DateTime BookingDate { get; set; }
}
//Grouped data
public class GroupedDataRowModel
{
public string Class { get; set; }
public IEnumerable<DataRowModel> Rows { get; set; }
}
//View model
public class DataRowsViewModel
{
public IEnumerable<GroupedDataRowModel> Results { get; set; }
}
Controller Action:
public ActionResult TestData()
{
var PretendDatabaseCall = new List<DataRowModel>
{
new DataRowModel{
Id =1,
BookingDate =new DateTime(2017,1,1),
Description ="Booking 1",
Class="Room"
},
new DataRowModel{
Id =2,
BookingDate =new DateTime(2017,2,1),
Description ="Booking 2",
Class="Room"
},
new DataRowModel{
Id =3,
BookingDate =new DateTime(2017,3,1),
Description ="Booking 3",
Class="Suite"
},
new DataRowModel{
Id =4,
BookingDate =new DateTime(2017,4,1),
Description ="Booking 4",
Class="Room"
},
};
//We can now get the data from the database. We want to group by class so we can
//get a summary of items by class rather than a big flat list. Most LINQ to SQL implementations
//(e.g. Entity Framework) when working with Raw entities could convert this to SQL so the SQL server
//does the grouping, but if not it can happen in memory (get all records, then standard LINQ does it on
//the complete list)
var dataGroupedByClass = PretendDatabaseCall
//Minor Edit: apply filtering here not in the view!
.Where(x=>x.BookingDate >= Datetime.Now)
//Group by class.
.GroupBy(x => x.Class)
//for each class, get the records.
.Select(grpItem => new GroupedDataRowModel()
{
//'key' is the thing grouped by (class)
Class = grpItem.Key,
//grpItem has all the rows within it accessible still.
Rows = grpItem.Select(thisRow => thisRow)
});
var model = new DataRowsViewModel
{
Results = dataGroupedByClass
};
return View("~/Views/Home/TestData.cshtml", model);
}
And View:
#* Strongly typed view. saves any casting back and forth.*#
#model SimpleWeb.Models.DataRowsViewModel
#{
ViewBag.Title = "TestData";
}
<h2>TestData</h2>
<table>
<thead></thead>
<tbody>
#foreach (var groupEntry in Model.Results)
{
#*Add single row with just the class...*#
<tr><td>#groupEntry.Class</td></tr>
#*Header row for each class of booking*#
<tr>
<td>Id</td>
<td>Description</td>
<td>Date</td>
</tr>
foreach (var row in groupEntry.Rows)
{
#*add new row for each actual row*#
<tr>
<td>
#row.Id
</td>
<td>
#row.Description
</td>
<td>
#row.BookingDate
</td>
</tr>
}
}
</tbody>
</table>
This produces Data like I think you want:
Room
Id Description Date
1 Booking 1 01/01/2017 00:00:00
2 Booking 2 01/02/2017 00:00:00
4 Booking 4 01/04/2017 00:00:00
Suite
Id Description Date
3 Booking 3 01/03/2017 00:00:00
Obviously you want the 'Room' and 'Suite' parts to contain more information, but this should hopefully help get you started?
Trying to pass a list of confirmed orders to the supplier page (checked with breakpoint the list is being past) just having problems using a foreach to display the list in the view.
//SupplierController
public ActionResult Index()
{
BuyABicycle_Entities db1 = new BuyABicycle_Entities();
IEnumerable<BicycleOrder> All_Orders = (from c in db1.BicycleOrders
where c.Id >= 1
select c).ToList();
SupplierVM model = new SupplierVM { allOrders = All_Orders };
return View(model);
}
//SupplierVM
public class SupplierVM
{
public IEnumerable<BicycleOrder> allOrders { get; set; }
}
Views/Supplier/Index
#model BicycleShop.ViewModels.SupplierVM
#{
ViewBag.Title = "Supplier";
//var orders = (IList<BicycleOrder>) Model.;
// var orders = (List<BicycleOrder>) Model.Order);
}
#using (Html.BeginForm())
{
<table>
#foreach (var _Order in Model.allOrders)
{
<text>
<tr>
<td>#_Order.CustomerName</td>
</tr>
</text>
}
</table>
<input type="submit" />
}
This throws the error with #foreach (var _Order in Model.allOrders)
Compiler Error Message: CS0012: The type 'IdeaBlade.EntityModel.Entity' is defined in an assembly that is not referenced. You must add a reference to assembly 'IdeaBlade.EntityModel, Version=6.1.7.0, Culture=neutral, PublicKeyToken=287b5094865421c0'.
Foreach loop for tables in MVC4
do I need to declare a variable for the list at the top and then run through that
any help appreciated. thanks
Your view specifies the model as an IEnumerable<SupplierVM>. So to iterate over the orders, you would first have to iterate over the suppliers:
#foreach (var supplier in Model)
{
foreach (var order in supplier.allOrders)
{
...
}
}
However, it seems you're not actually passing many SupplierVM instances, but just one. Therefore, you should change the view's model to:
#model BicycleShop.ViewModels.SupplierVM
And, then you can directly iterate over the orders:
#foreach (var order in Model.allOrders)
{
...
}
maybe razor is getting confused between HTML and code:
try this:
#foreach (var _Order in Model)
{
<text>
<tr>
<td>#Html.TextBoxFor(x => x.allOrders)</td>
<td>#_Order.allOrders</td>
#<td>#Html.TextBoxFor(x => x.CustomerName, new { #readonly = true }) </td>
#foreach(var item in _Order)
{
item.ItemProp <br />
}
</tr>
</text>
}
inside the {} razor is expecting it all the be code, if you want to put HTML in there - multi-line use <text></text> for one line use #:
I am not sure if this is possible, and have not found any similar questions on this.
We have an Edit View that is NOT for a single record, but for the multiple members of a "parent" record. These "child" record need to be edited together (at the same time). ... if possible.
One field in each of these "child" records is a reference to another table, so a select list is required. We use DropDownListFor in all of our standard Edit Views, and the single record edits fine.
Our model for this issue is :
[Display(Name = "Team Member")]
public int Contact_ID { get; set; }
[Display(Name = "Team Member")]
public String Contact_Name { get; set; }
[Display(Name = "Type/Role")]
public int MemberTypeLookUp_ID { get; set; }
[Display(Name = "Type/Role")]
public String MemberTypeValue { get; set; }
[Display(Name = "Type/Role")]
public LookUpList MemberTypeLookUp { get; set; }
We retrieve the first 4 fields via a select from a database table. Straightforward and OK..
Our code to set up the DropDownListFor is :
(edit : new code added within the foreach() loop to manually set the .Selected property of the relevant option within each list to true. This still does not translate over to the actual displayed View...)
foreach (TeamEditViewItem tevi in this.members)
{
tevi.MemberTypeLookUp = new LookUpList("TeamMemberType");
foreach (SelectListItem item in tevi.MemberTypeLookUp.list)
{
if (item.Value == tevi.MemberTypeLookUp_ID.ToString())
{
item.Selected = true;
break;
}
}
}
For completion of this question, the LookUpList code is :
public class LookUpList
{
public SelectList list;
// Return all Active LookUp entries for the passed-in Category.
public LookUpList(String Category)
{
WorkpointContext _db = new WorkpointContext();
int Customer_ID = _db.GetCustomer_ID();
IList<LookUp> items = (from lookup in _db.LookUp
where (lookup.Category == Category)
&& (lookup.IsActive == true)
orderby lookup.DisplayOrder ascending
select lookup).ToList();
this.list = new SelectList(items, "ID", "Value");
}
}
As mentioned, the LookUpList code is fine for a single record on a standard Edit View.
After rendering the page, we get the multiple "child" records listed, however the DropDown List does not hold the existing value for each record. (This is an EDIT not a Create, so values have already been assigned via defaults and other logics - not via DropDown lists on the Create View.
When viewing the source of the page, I can see that each of the DropDown Lists have their own ID.
I have a feeling that our issue is due to the multiple DropDownListFor objects on the page, but cannot figure out WHAT the issue is and WHY we have the issue.
Our View has simple code for the DropDownList :
#Html.DropDownListFor(model => model.members[i].MemberTypeLookUp_ID, Model.members[i].MemberTypeLookUp.list, "--Select--")
#Html.ValidationMessageFor(model => model.members[i].MemberTypeLookUp_ID)
The third parameter has been added because we were always getting the first option in the DropDown Lists and needed to determine if there was a value or not.
We are constantly getting the "--Select--" option displayed in the DropDown Lists, which is a placeholder and not a valid option - therefore the Validation Message is displayed.
(Edit) I have added the complete Edit View cshtml code :
#model WebWorkPoint.Models.TeamEditView
<h3>Edit Team</h3>
#using (Html.BeginForm()) {
<fieldset>
#if (Model.members.Count>0)
{
<table>
<!-- table headings -->
<thead>
<tr>
<td style="text-align:center; border-bottom: 1px solid black; " >
<div class='editor-label'>
#Html.LabelFor(m => m.members.First().Contact_Name)
</div>
</td>
<td class="spacer-border"> </td>
<td style="text-align:center; border-bottom: 1px solid black; " >
<div class='editor-label'>
#Html.LabelFor(m => m.members.First().MemberTypeValue)
</div>
</td>
</tr>
</thead>
<!-- table rows -->
<tbody>
#for (int i = 0; i < Model.members.Count; i++)
{
<tr>
<td style="text-align:center; " >
#Html.HiddenFor(m => m.members[i].Contact_ID)
<div class="editor-field">
#Html.EditorFor(m => m.members[i].Contact_Name)
#Html.ValidationMessageFor(model => model.members[i].Contact_Name)
</div>
</td>
<td class="spacer"></td>
<td style="text-align:center; " >
<div class="editor-field">
#Html.DropDownListFor(model => model.members[i].MemberTypeLookUp_ID, Model.members[i].MemberTypeLookUp.list, "--Select--")
#Html.ValidationMessageFor(model => model.members[i].MemberTypeLookUp_ID)
</div>
</td>
</tr>
}
</table>
}
else
{
<p>There are currently no team members defined.</p>
}
<p>
<input type="submit" value="Update Team" />
#{
sAction = "/" + Model.TableNameValue + "/" + Model.TableNameValue + "Show/" + Model.TableRecord_ID.ToString();
sLinkText = "Cancel";
}
<button type="button" onclick="location.href='#sAction'" >#sLinkText</button>
</p>
</fieldset>
}
(end Edit)
Can anyone shed some light into our issue ? Thank you in advance for any help.
After reading this answer on Stack Overflow , we decided to try the same kind of resolution.
As it turns out, the FULL resolution went as follows :
We still needed to set up the LookUpList in the setup code (but did not need to attempt any select code) :
// other code above ...
foreach (TeamEditViewItem tevi in this.members)
{
tevi.MemberTypeLookUp = new LookUpList("TeamMemberType");
}
The LookUpList() code creates the SelectList as per the original issue/question - no changes required there.
We also needed to replace the DropDownListFor() call on the Edit View :
#Html.DropDownListFor(model => model.members[i].MemberTypeLookUp_ID, new SelectList(Model.members[i].MemberTypeLookUp.list, "Value", "Text", Model.members[i].MemberTypeLookUp_ID), "--Select--")
It seemed repetitive or redundant, but this is what was required. There may be something we could do to clean it further, but it "ain't broke" now, so why try to fix it ?
I must say thank you to #Stephen Muecke and #Mario Lopez for their input, to get us investigating and thinking further afield from what we were doing. Also, thank you to #ataravati for resolving the other issue linked above, to get us to try something else.
Hopefully our issue and resolution might help other coders out there ...
I think what is happening is that all the dropwdowns are being generated with the same Id = MemberTypeLookUp_ID. What I would do is creating a partial view for the child and call it from the main view inside a foreach and pass to this partial view only the child model that has to be populated for and not the whole parent model.
How can i close <tr> and open <tr> after 3 loop iterations? I have MVC 3 in .NET 4.0. How can I count loop iterations in MVC 3?
Current Code:
#foreach (var articleOnFirstPage in Model.ArticlesOnFirstSite)
{
<tr>
<td><div class="productsFrame"></div></td>
</tr>
}
I want to get this:
<tr>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
</tr>
<tr>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
</tr>
<tr>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
<td><div class="productsFrame"></div></td>
</tr>
You could perform the following pornography in your view:
#model IEnumerable<Foo>
<table>
#foreach (var item in from i in Model.Select((value, index) => new { value, index }) group i.value by i.index / 3 into g select g)
{
<tr>
#foreach (var x in item)
{
<td><div class="productsFrame">#x.SomeProperty</div></td>
}
</tr>
}
</table>
or simply use view models and do the grouping in your controller action which obviously is what I would recommend you. The sole fact that you need to do this means that your view model is not adapted to your view's requirements which is to group results by 3. So adapt it. Don't pass IEnumerable<Foo> to your view. Pass IEnumerable<MyViewModel> where obviously MyViewModel will contain the necessary grouping so that in your views you could simply loop or since I hate writing for and foreach loops in views simply use display templates. They will take care of everything and your view will simply look like this:
<table>
#HtmlDisplayForModel()
</table>
Looks better than the initial pornography isn't it?
As requested in the comments section here's how I would implement this using view models.
As always in an ASP.NET MVC application you start by defining the view models that will reflect the requirements of your view (which I repeat are: show a table with 3 columns):
public class ItemViewModel
{
public string Title { get; set; }
}
public class MyViewModel
{
public IEnumerable<ItemViewModel> Items { get; set; }
}
then you move on to the controller that will fill and pass this view model to the view:
public class HomeController : Controller
{
public ActionResult Index()
{
// Obviously in a real world application the data is your domain model
// and comes from a repository or a service layer depending on the complexity
// of your application. I am hardcoding it here for the
// purposes of the demonstration
var data = Enumerable.Range(1, 30).Select(x => new { Title = "title " + x });
var model =
from i in data.Select((value, index) => new { value, index })
group i.value by i.index / 3 into g
select new MyViewModel
{
Items = g.Select(x => new ItemViewModel { Title = x.Title })
};
return View(model);
}
}
and finally you write the corresponding view (~/Views/Home/Index.cshtml):
#model IEnumerable<MyViewModel>
<table>
#Html.DisplayForModel()
</table>
and the ~/Views/Home/DisplateTemplates/MyViewModel.cshtml display template:
#model MyViewModel
<tr>
#Html.DisplayFor(x => x.Items)
</tr>
and finally the corresponding ~/Views/Home/DisplateTemplates/ItemViewModel.cshtml display template:
#model ItemViewModel
<td>#Html.DisplayFor(x => x.Title)</td>
and that's pretty much it. Simple, clean, following good practices and conventions.
Obviously to bring this a step further you would introduce AutoMapper to perform the actual mapping between your domain models and view models and you will end up with a very elegant solution that will look like this:
public ActionResult Index()
{
IEnumerable<DomainModel> data = ...
var viewModel = Mapper.Map<IEnumerable<DomainModel>, IEnumerable<MyViewModel>>(data);
return View(viewModel);
}
or a step further:
[AutoMap(typeof(IEnumerable<DomainModel>), typeof(IEnumerable<MyViewModel>))]
public ActionResult Index()
{
IEnumerable<DomainModel> data = ...
return View(data);
}
Now we are starting to get into serious business.
The first thing that comes to mind is Phil Haack's better foreach loop
Using it you gain an index and can use it like
<ol>
#Model.Each(#<li>Item #item.Index of #(Model.Count() - 1): #item.Item.Title</li>)
</ol>
What you're specifically looking for should be something like:
#Model.ArticlesOnFirstSite.Each(#<td><div class="productsFrame"></div></td>#(#item.Index % 3 == 0 ? "</tr><tr>" : ""))
Something like this may work.
#{int i = 0;}
#foreach (var articleOnFirstPage in Model.ArticlesOnFirstSite)
{
#if ((i++ % 3) == 0) {
#if (i != 1) {
#:</tr>
}
#:<tr>
}
#:<td><div class="productsFrame"></div></td>
}
#if (i != 0) {
#:</tr>
}
And this is a brute force solution to your problem.
As other have suggested and as I suggest, you should change your methodology: use view models, group items by 3.
About how to correctly use model-view-controller pattern you can look on the web.
http://msdn.microsoft.com/en-us/library/gg416514(v=vs.98).aspx is a good start.
Like the others said, the best and prettiest solution is probably to do the grouping in the controller, but this might get the job done:
#for (int i = 0; i < Model.ArticlesOnFirstSite.Count; i += 3)
{
<tr>
#foreach (Article article in Model.ArticlesOnFirstSite.Skip(i).Take(3))
{
<td>#article.Title</td>
}
</tr>
}