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>
}
Related
Hello Mighty Stackoverflowers,
I'm currently working on an ASP.NET MVC 4.5 application. I need to map the input values from my partial view to my main View Model, when I submit the create form.
In my View "Create.cshtml" I call a partial view "_SwotPart.cshtml". I pass a part of my ViewModel to the Partial View, like this:
Create.cshtml:
#model MyCoolApp.BLL.Models.MainVm
#foreach (var swot in Model.Swots)
{
<tr>
#foreach (var swotPart in swot.SwotParts)
{
#Html.Partial("~/Views/Shared/_SwotPart.cshtml", swotPart)
}
</tr>
}
My partial View looks as follows, _SwotPartial.cshtml :
<td class="form-group">
#Html.TextAreaFor(model => model.Label, htmlAttributes: new { Name = nameField, ID = nameField, #class = "form-control", placeholder = Model.SwotTypeId.GetLabel() })
</td>
Anyways, when I submit my form, the values from the partial view never arrive in the controller.
Do you have any ideas how to map this properly?
Thanks!
The problem is in the input names that will be generated the way you're currently trying to achieve this. Razor needs the context of the entire list, or at least the item's position in it, in order to generate correct input names. In other words, the easiest way to solve your issue (with a caveat) is:
#for (var i = 0; i < Model.Swots.Count(); i++)
{
...
#for (var j = 0; j < Model.Swots[i].SwotParts.Count(); j++)
{
if (Model.Swots[i].SwotParts[j].SwotTypeId == SwotType.InternalHelpful || Model.Swots[i].SwotParts[j].SwotTypeId == SwotType.InternalHarmful)
{
#Html.Partial("~/Views/Shared/_SwotPart.cshtml", Model.Swots[i].SwotParts[j])
}
}
...
Then, the partial has the correct context to work with and your inputs will be named like Swots[0].SwotParts[0].Label, which the modelbinder will be able to work with.
However, the caveat here is that you're splitting this list into two loops. That's still not going to work, as you're effectively messing with the overall context of the item(s) position within the model. To fix that, you should split your list in your model, which is better anyways, as you can remove this business logic from your view:
public class SwotVm
{
...
public List<SwotPartVm> InternalSwotParts { get; set; }
public List<SwotPartVm> ExternalSwotParts { get; set; }
}
Then, you can simply iterate over each list individually, and the values will naturally post back to the appropriate list.
Given that you're using a partial to render fields for a particular class type, though, you'd be better served by creating an editor template. If you simply move your partial code to the view: Views\Shared\EditorTemplates\SwotPartVm.cshtml, then in your main view, you can just do:
#for (var i = 0; i < Model.Swots.Count(); i++)
{
...
<tr>
<th class="swot-heading">Internal</th>
#Html.EditorFor(m => m.Swots[i].InternalSwotParts)
</tr>
<tr>
<th class="swot-heading">External</th>
#Html.EditorFor(m => m.Swots[i].ExternalSwotParts)
</tr>
}
That's obvious much cleaner, and you can take this concept even further by adding a SwotVm.cshtml editor template, allowing you replace even this little bit of code with just:
#Html.EditorFor(m => m.Swots)
Note: In your SwotVm.cshtml editor template, you would only include the code for a single SwotVm. In other words, not including the for statement.
In order for your application to parse the posted values and properly and bind it to your view model. Names of posted form data needs to be like.
swots[x1].swotParts[x2].label
Where x1 is a number ranging from 0 and up for each swot.
Where x2 is a number ranging from 0 and up for each swot part in swots.
Now when you are posting, the form data names is just label.
Instead of :
#Html.TextAreaFor(model => model.Label, htmlAttributes: new { Name = nameField, ID = nameField, #class = "form-control", placeholder = Model.SwotTypeId.GetLabel() })
try :
<textarea name="swots[x1].swotParts[x2].label" class="form-control" placeholder="#Model.SwotTypeId.GetLabel()" >#Model.Label</textarea>
Don't forget to replace the x1 and x2 with a number.
You can read more about model bindings to collections here.
http://www.hanselman.com/blog/ASPNETWireFormatForModelBindingToArraysListsCollectionsDictionaries.aspx
I am pulling data from a Warehouse database and displaying on a webpage.
This pulls all data.
#foreach (WarehouseCategoryModel categoryModel in Model.Categories)
{
<tr>
<td>#Html.ActionLink(categoryModel.Name, "Category", "-WarehouseCatalog", new { ide = categoryModel.ID }, null)</td>
<td>#categoryModel.Count</td>
</tr>
}
I need to display each category where the Count is greater than 0.
I tried:
if(#categoryModel.Count > 0)
{
<td>#categoryModel.Count</td>
}
You could explicitly filter out the items that you are iterating through using a Where() clause :
<!-- Only iterate through those with Counts greater than 0 -->
#foreach (WarehouseCategoryModel categoryModel in Model.Categories.Where(c => c.Count > 0))
{
<!-- Do work here -->
}
If you wanted to handle scenarios where your Model or Categories were possibly null, you could add the following explicit check :
<!-- Ensure you have categories to loop through (avoids null exceptions) -->
#if(Model?.Categories != null)
{
<!-- Only iterate through those with Counts greater than 0 -->
foreach (WarehouseCategoryModel categoryModel in Model.Categories.Where(c => c.Count > 0))
{
<!-- Do work here -->
}
}
else
{
<tr>
<td>No Categories Available</td>
</tr>
}
If you were planning on handling this, you would likely want to include this logic within your ViewModel itself as opposed to in the View, but the general idea is the same.
You have got the # in the wrong place. It should come before the if, like this
#if(categoryModel.Count > 0)
{
<td>#categoryModel.Count</td>
}
else
{
<td></td>
}
As mentioned in the comments. This should work:
<td>
if(#categoryModel.Count > 0)
{
#categoryModel.Count
}
</td>
Remove # from IF statement. You have already used in foreach loop.
if(categoryModel.Count > 0)
{
<td>#categoryModel.Count</td>
}
else
{
<td></td>
}
You can do this a couple of different approaches, the approach your taking please keep in mind will be slow with complex iterations. I'll point out the potential code errors:
#if will trigger proper Razor, unless you have no Html inbetween your foreach and the if.
Sample:
// Valid
#foreach(var .... model.Categories)
{
if(...)
{
}
}
// Invalid:
#foreach(var .... model.Categories)
{
<td>Content</td>
if(...)
{
}
}
Your model should be fully qualified, Example.Models.Warehouse.
You should show how you're actually declaring your #model for the View. Which could help us distinguish further, also are you sure that you have more than one? I would structure like this to hold the table row data better:
#if(Model != null)
{
foreach(Example.Models.Warehouse model in Model.Categories)
{
<tr>
<td>#model.Something</td>
</tr>
}
}
There isn't a need for a conditional, the loop won't execute if it is empty.
Got it, thanks for all the feedback. Here is my final solution.
#if(categoryModel.Count > 0)
{
<td>#Html.ActionLink(categoryModel.Name, "Category","~WarehouseCatalog", new { id = categoryModel.Id }, null)</td>
<td>#categoryModel.Count</td>
}
You can try the following. I first checked if there are items in the list and if there are create the HTML table, loop through the items and create a table row for each item:
#if (Model.Categories.Count > 0)
{
<table>
foreach (WarehouseCategoryModel categoryModel in Model.Categories)
{
<tr>
<td>#Html.ActionLink(categoryModel.Name, "Category", "-WarehouseCatalog", new { ide = categoryModel.ID }, null)</td>
#if(categoryModel.Count > 0)
{
<td>#categoryModel.Count</td>
}
else
{
<td> </td>
}
</tr>
}
</table>
}
I hope this helps.
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.
I have the following problem - I am developing an ASP.NET MVC 3 application and I have view which is strongly typed. Because of the complexity of the data the model in the view looks like this :
#model List<List<DataAccess.MCS_DocumentFields>[]>
Then I render the view like this :
#using (Html.BeginForm("ActionMethodName", "Forms"))
{
<table border="1">
<tbody>
#for (int i = 0; i < Model.Count(); i++)
{
if (Model[i][0][0].ContentTypeId == 1)
{
#Html.Partial("_PartialHeader", Model[i])
}
else if (Model[i][0][0].ContentTypeId == 2)
{
#Html.Partial("_PartialDrawing", Model[i])
}
else if (Model[i][0][0].ContentTypeId == 3)
{
#Html.Partial("_PartialBody", Model[i])
}
else if (Model[i][0][0].ContentTypeId == 4)
{
#Html.Partial("_PartialFooter", Model[i])
}
}
</tbody>
</table>
<button type="submit">Save</button>
}
and this is one of my partial views :
if (string.IsNullOrEmpty(item.FieldValue))
{
<td colspan="2">
#Html.DisplayFor(y => y[i][0].QuestionText)
#Html.HiddenFor(y => y[i][0].QuestionText)
</td>
}
else
{
<td colspan="2">
#Html.DisplayFor(y => y[i][0].QuestionText)
#Html.HiddenFor(y => y[i][0].QuestionText)
:
#Html.DisplayFor(y => y[i][0].FieldValue)
#Html.HiddenFor(y => y[i][0].FieldValue)
</td>
}
This is just a snippet, what I want to say is that my table has at least 8-9 rows and almost each row has #Html.HiddenFor so I expect to get data when I'm submiting the form.
In my controller I have this method :
[HttpPost]
public ActionResult ActionMethodName(List<MCS_DocumentFields>[] collection)
{
var test = collection;
List<MCS_Documents> model = DocumentService.All().ToList();
return View("Index", model);
}
I tried a lot of different types for the collection argument. Most of the time I get null, at best I get the first two row (No idea why exactly the first two) but nothing more. If I use FormCollection then I have all my submitted data, but it's not related to my MCS_DocumentFields class in any way.
I don't know what I'm doing wrong. I don't think that the type for the method should be guessed I think it must be determined by something and if there is some problem in code (most possibly in the partial view) because I've posted here almost all of my main view, then any suggestions why my logic is not working and why I can't bind the data to the original type of the model?
I suggest you to look at the BeginCollectionItem package (http://nuget.org/packages/BeginCollectionItem/), allthough I can see that your model is really complex (3 dimensions), sou you'll have to nest them in a really nasty manner.
Also you should consider (due to your model complexity) creating a custom model binder (http://www.codeproject.com/Articles/605595/ASP-NET-MVC-Custom-Model-Binder).
Just one last remark - your model in View differs from your model that you expect in action:
List<MCS_DocumentFields>[] collection != List<List<DataAccess.MCS_DocumentFields>[]>
Ivan