I've been struggling to research an answer to this question as I cannot come up with the correct search terms.
Basically I have 2 IEnumerable<T>'s in my controller, below is the code for the attempt I made.
Controller:
IEnumerable<Room> allRooms = RoomHelper.FindAllRooms();
foreach (var room in allRooms)
{
IEnumerable<Bunk> associatedBunks = RoomHelper.FindAssociatedBunksByRoom(room);
if (associatedBunks.Count() > 0)
{
ViewData["Room_"+room.RoomId] = associatedBunks;
}
}
And I'm trying to send them to the view in a way that I can do two foreach loops that will cycle through one set of data (in this case the Room objects and will then using the Room.RoomId key cycle through another IEnumerable which contains the associated data.
My view looks like this but is showing parse errors:
#foreach (var room in ViewBag.Rooms)
{
<h2>#room.RoomName</h2>
#if (ViewData["Room_" + room.RoomId].Count() > 0)
{
<ol>
#foreach (var bunk in ViewData["Room_" + room.RoomId])
{
<li>#bunk.BunkId</li>
}
</ol>
}
}
The end result I'm looking for in the HTML is something like:
<h2>Room 1</h2>
<ol>
<li>Bunk 1</li>
<li>Bunk 2</li>
</ol>
<h2>Room 2</h2>
<ol>
<li>Bunk 3</li>
<li>Bunk 4</li>
</ol>
What is the best practice in ASP.NET MVC 4 with EF5 to achieve this kind of result when passing "multidimensional" (is this multidimensional?) data?
Don't rely on ViewData. Store the data that you want to pass on to your view in a proper ViewModel:
public class RoomViewModel
{
List<Room> Rooms { get; set;}
...
}
Store your data in one of those.
Your Controller method then returns an instance of it:
public RoomViewModel GetRooms(int someParameter)
{
RoomViewModel result = new RoomViewModel();
result.Rooms = RoomHelper.Something(someParameter);
...
return result;
}
Your View declares its model on top:
#model MyApplication.ViewModels.RoomViewModel
and hence you use it in your View.
<h2>#Model.Rooms.Count rooms found</h2>
etc.
Use a code block in your view instead of adding an '#' in front of each C# statement:
#{
foreach (var room in ViewBag.Rooms)
{
#Html.Raw("<h2>" + room.RoomName + "</h2>");
if (ViewData["Room_" + room.RoomId].Count() > 0)
{
#Html.Raw("<ol>");
foreach (var bunk in ViewData["Room_" + room.RoomId])
{
#Html.Raw("<li>" + bunk.BunkId + "</li>");
}
#Html.Raw("</ol>");
}
}
}
You should avoid the use of #HtmlRaw("") as far as possible as it has a XSS vulnerability. But this should put you on the right track.
As per description given by you it seems that Bunk is associated with rooms. If that's the case then Bunk may have some id for pointing to room it belongs. Now you can create a ViewModel like this
public class BunkViewModel:Bunk
{
public BunkViewModel()
{
Mapper.CreateMap<Bunk,BunkViewModel>();
}
//I'm assuming that you already have some id in bunk to point to room it belongs.
//But writing it here to make it clear
public int RoomId { get; set; }
public string RoomName { get; set; }
//Use AutoMapper to Map
public static BunkViewModel Map(Bunk source)
{
return Mapper.Map<Bunk,BunkViewModel>(source);
}
}
Now in your controller
public ActionResult ActionName()
{
var result = new List<BunkViewModel>();
var rooms = RoomHelper.FindAllRooms();
var bunks = BunkHelper.GetAllBunks();
foreach(var bunk in bunks)
{
var bunkViewModel = BunkViewModel.Map(bunk);
var room = rooms.FirstorDefault(r=>room.RoomId == bunk.RoomId);
bunkViewModel.RoomId = room.RoomId; //No need to set if you already have this id in bunk
bunkViewModel.RoomName = room.RoomName;
result.Add(bunkViewModel);
}
return View(result.);
}
Now in your view you can do like this
#model List<MyApplication.ViewModels.RoomViewModel>
#foreach(var bunk in Model)
{
//Print it here as you want..
}
Related
I have a project to make an online shop between users (post a product, buy, etc.) using a database. In this project I have a view called "ShoppingCart":
#model IEnumerable<MyFirstProject.Models.Product>
#{
ViewBag.Title = "ShoppingCart";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Your Shopping Cart</h2>
#if (Model == null)
{
<div style="float:left">Your cart is empty.</div>
<div>
Total payment: 0
</div>
}
else
{
decimal tPrice = 0;
<div>
<table style="float:left">
#foreach (var product in Model)
{
tPrice = tPrice + product.Price;
{ Html.RenderPartial("ProductLine", product);}
}
</table>
</div>
<div>
Total payment: #tPrice
</div>
}
It receives a list of products which the user decided to buy and displays them (not the important part). I need to add a button which will send the list to an action result in the "ShoppingController":
[HttpPost]
public ActionResult ShoppingCart(List<Product> bought)
{
if (ModelState.IsValid)
{
foreach (var listP in bought.ToList())
{
foreach (var databaseP in db.Products.ToList())
{
if (listP.ProductID == databaseP.ProductID)
{
databaseP.State = 1;
db.SaveChanges();
break;
}
}
}
return RedirectToAction("Index");
}
else
{
return View(bought);
}
}
"State" indicates if the product was bought or not (0=not bought, 1=bought), db is the database
If you wan't to post any data from a view to an action method, you should keep that data in form elements and keep that in a form. Since you want to post a collection of items, You may use Editor Templates.
Let's start by creating a view model.
public class ShoppingCartViewModel
{
public decimal TotalPrice { set; get; }
public List<Product> CartItems { set; get; }
}
public class Product
{
public int Id { set; get; }
public string Name { set; get; }
}
Now in your GET action, you will create an object of the ShoppingCartViewModel, load the CartItems property and send to the view.
public ActionResult Index()
{
var cart = new ShoppingCartViewModel
{
CartItems = new List<Product>
{
new Product { Id = 1, Name = "Iphone" },
new Product { Id = 3, Name = "MacBookPro" }
},
TotalPrice = 3234.95
};
return View(cart);
}
Now i will create an EditorTemplate. To do that, Go to your ~/Views/YourControllerName folder, and Create a directory called EditorTemplates and add a view with name Product.cshtml
The name of the file should match with the name of the type.
Open this new view and add the below code.
#model YourNamespace.Product
<div>
<h4>#Model.Name</h4>
#Html.HiddenFor(s=>s.Id)
</div>
You can keep the display however you want. But the important thing is, We need to keep a form field for the productId. We are keeping that in a hidden field here.
Now let's go back to our main view. We need to make this view strongly typed to our ShoppingCartViewModel. We will use the EditorFor html helper method in this view to call our editor template
#model ReplaceYourNamespaceHere.ShoppingCartViewModel
#using (Html.BeginForm())
{
#Html.EditorFor(x => x.CartItems)
<p>Total : #Model.TotalPrice</p>
<input type="submit" />
}
And in your HttpPost action method, We will have a paramer of type ShoppingCartViewModel. When the form is submitted, MVC Model binder will map the posted form values to an object of ShoppingCartViewModel.
[HttpPost]
public ActionResult Index(ShoppingCartViewModel model)
{
foreach (var item in model.CartItems)
{
var productId = item.Id;
// to do : Use productId and do something
}
return RedirectToAction("OrderSucessful");
}
You can iterate through the CartItems collection and get the Id of the Products and do whatever you want.
If you wan't to allow the user to edit the items (using a check box) in this page, Take a look at this answer. It is basically same, but you add a boolean property to Product class and use that for rendering a checkbox.
I have a model in my ASP.NET MVC application:
public class SearchArrayModel
{
public long ID { get; set; }
public string Name { get; set; }
public struct AttribStruct
{
public string AttribName { get; set; }
public string[] AttribValues { get; set; }
}
public AttribStruct[] AttribStructTable { get; set; }
}
In controller I'm filling it by some data from WebAPI (filling process is okay), I created an array of SearchArrayModel because I have a lot of items to fill from webAPI (It's a webAPI from site similar to ebay), for example some phones with its names, and attributes which u normally see on the auction site).
SearchArrayModel[] src = new SearchArrayModel[x];
{
//filling the fields
}
And on the end of the ActionResult I have:
return View(src);
//Visual Studio tells me that it is a "SearchArrayModel[] src"
I have also View, where I want to get and display the data:
#model allegrotest.Models.SearchArrayModel
#using (Html.BeginForm())
{
<h2>#Model.AttribStructTable[1].AttribName</h2>
<h3>#Model.AttribStructTable[1].AttribValues[1]</h3>
//indexes of arrays are just for testing, if all will be good I will write a loop
}
But when I'm starting the app I have an error:
The model item passed into the dictionary is of type
'allegrotest.Models.SearchArrayModel[]', but this dictionary requires
a model item of type 'allegrotest.Models.SearchArrayModel
I know that this is a complex array and I don't know how to pass this array from Controller to View.
I tried to write in View:
#model allegrotest.Models.SearchArrayModel[]
but then I can't get into the fields inside the #Model
Starting from assumption that "filling process is okay" is wrong.
Note: I made this assumption because I see that you are not interested in Model[index] and I noticed in SearchArrayModel[x]; { } the ;.
SearchArrayModel src = new SearchArrayModel
{
AttribStructTable = new SearchArrayModel.AttribStruct[]
{
new SearchArrayModel.AttribStruct{AttribName = "0Nume", AttribValues = new []{"0one", "0two"}},
new SearchArrayModel.AttribStruct{AttribName = "1Nume", AttribValues = new []{"1one", "1two"}},
},
Name = "SearchArrayName"
};
Your View is okay and is working
#model allegrotest.Models.SearchArrayModel
#using (Html.BeginForm())
{
#foreach(var attribStruct in #Model.AttribStructTable)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
Another solution will be to make the modelof View to be an IEnumerable and in the Action you can make return View(src.ToList());
Also I noticed, when I tested your code, that you have Model.AttribStructTable which is wrong because your Model is a collection and you have to specify which element from model you want to display Model[index] (not posible with IEnumerable), Model.First() or you can iterate through collection.
#model IEnumerable<allegrotest.Models.SearchArrayModel>
#using (Html.BeginForm())
{
#foreach(var attribStruct in #Model.First().AttribStructTable)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
If you iterate through all items from Model will look like this
#model IEnumerable<allegrotest.Models.SearchArrayModel>
#using (Html.BeginForm())
{
#foreach(var searchArrayModel in Model)
{
#foreach(var attribStruct in #searchArrayModel)
{
<h2>#attribStruct.AttribName</h2>
#foreach(var attribValue in attribStruct.AttribValues)
{
<h3>#attribValues</h3>
}
}
}
}
#model allegrotest.Models.SearchArrayModel[]
which is an array. So you could try
#foreach (SearchArrayModel item in Model)
{
<h2>#item.AttribStructTable[1].AttribName</h2>
<h3>#item.AttribStructTable[1].AttribValues[1]</h3>
..
}
or
#for (int i = 0; i < Model.Length; ++i)
{
<h2>#Model[i].AttribStructTable[1].AttribName</h2>
<h3>#Model[i].AttribStructTable[1].AttribValues[1]</h3>
..
}
I am struggling to determine if a Model passed to a View actually has any records.
The code below loops through the parent recordset and passes a parent parameter to a child recordset. I've tried if (Model.Content != null) but that doesn't seems to work, the code just thinks there are records, when actually there aren't.
Can somebody please review the code below and let me know what I am doing wrong?
<ul class="nav sf-menu clearfix">
#foreach (var navigation in Model.Navigation)
{
if (Model.Content != null)
{
#Html.SubMenuLink(navigation.Title, navigation.Action, navigation.Controller)
#Html.Raw("<ul>")
foreach (var content in Model.Content.Where(c => c.NavigationId == navigation.Id))
{
if (string.IsNullOrEmpty(content.Url))
{
if (string.IsNullOrEmpty(content.Content1))
{
}
else
{
#Html.MenuLink(content.Title, "Home/Article/" + content.Id + "/" + ToFriendlyUrl(content.Title), "Home");
}
}
else
{
#Html.MenuLink(content.Title, content.Url, "Home");
}
}
#Html.Raw("</ul>")
#Html.Raw("</li>")
}
else
{
#Html.MenuLink(navigation.Title, navigation.Action, navigation.Controller)
}
}
</ul>
Any help would be much appreciated :-)
NavigationViewModel
namespace WebApplication1.Models
{
public class NavigationViewModel
{
public List<Navigation> Navigation { get; set; }
public List<Content> Content { get; set; }
}
}
HomeController
public ActionResult Navigation()
{
var navigationModel = new NavigationViewModel();
navigationModel.Navigation = (from m in db.Navigations where (m.Main == true) orderby m.Position select m).ToList();
navigationModel.Content = (from n in db.Contents where (n.Active == true) orderby n.Position select n).ToList();
return View(navigationModel);
}
It may have empty list items. Better check the Count property.
if (Model.Content != null && Model.Content.Count>0)
Assuming Model.Content may be the type of IList or Array
If Content property is of List or Array type then do like this:
if(Model.Content != null && Model.Content.Count > 0)
{
//do something
}
if its a IEnumerable of some type then:
if(Model.Content != null && Model.Content.Count() > 0)
{
//do something
}
and if you are sure that Model.Content will not be passed null from action method, then you can use Any():
if(Model.Content.Any())
{
//do something
}
Simply you can do something like this
In your C# class introduce new property named called Empty.
public class YourClass
{
public bool Empty
{
get
{
return ( ColumnID== 0 )
}
}
}
Then in your Razor view you can use this Empty property for check weather model has values or not
#if (Model.Empty)
{
#*what ever you want*#
}
else
{
#*what ever you want*#
}
You could just use the Linq Method Any()
if (Model.Content.Any())
{
}
Edit: After second look. That if statement may not be right Model.Content inside the loop will always execute the same way. Are you use it isnt something like navigation.Content.Any()
My issue was that the Model.Content was initially being passed all records to the view so records did actually exist before the Where clause foreach (var content in Model.Content.Where(c => c.NavigationId == navigation.Id)) to find related Content records based on NavigationID.
I have amended my code as follows:
<ul class="nav sf-menu clearfix">
#foreach (var navigation in Model.Navigation)
{
int records = Model.Content.Count(c => c.NavigationId == navigation.Id);
if (records > 0)
{
#Html.SubMenuLink(navigation.Title, navigation.Action, navigation.Controller)
#Html.Raw("<ul>")
foreach (var content in Model.Content.Where(c => c.NavigationId == navigation.Id))
{
if (string.IsNullOrEmpty(content.Url))
{
if (string.IsNullOrEmpty(content.Content1))
{
}
else
{
#Html.MenuLink(content.Title, "Home/Article/" + content.Id + "/" + ToFriendlyUrl(content.Title), "Home");
}
}
else
{
#Html.MenuLink(content.Title, content.Url, "Home");
}
}
#Html.Raw("</ul>")
#Html.Raw("</li>")
}
else
{
#Html.MenuLink(navigation.Title, navigation.Action, navigation.Controller)
}
}
</ul>
I'm not sure this is the most elegant or efficient way to achieve the end goal, any suggestions to make the code more efficient I am happy to listen.
#if (!Model.Any())
{
Response.Redirect("Create");
}
If model empty then return empty default content will give you what do you expected
if (!model.Any())
return Content("");
I have there 6 UL and inside each UL i dynamically generate li from serverside. right now for 6 UL i am doing ti 6 times repetadly. Is there a way i can create a funciton to pass ul as an parameter in the function and call same function with different ul elements.
right now i have this
<ul ID="ul_1"></ul>
<ul ID="ul_2"></ul>
<ul id="ul_3"></ul>
<ul id="ul_4"></ul>
server side code to populat is like this
foreach (String li in ListA)
{
HtmlGenericControl uli = new HtmlGenericControl("li");
uli.InnerHtml = li;
ul_1.Controls.Add(uli);
}
I am using this code for each of the UL that is 4 times but i am trying to create a function so that i can use the same function just passing the UL id. I am not having any idea...Any help folks....
If I correctly understood what you want maybe you could try something like this.
Model:
public class ListItem {
public string Content { get; set; }
}
public class ListOfItems {
public int Id { get; set; }
public IList<ListItem> Items { get; set; }
}
View (for example using razor):
#model IEnumerable<ListOfItems>
....
#foreach (var itemList in Model)
{
<ul id="ul_#itemList.Id">
#foreach(var item in itemList.Items) {
<li>#item.Content</li>
}
</ul>
}
Controller:
public ActionResult ViewList(){
var model = new List<ListOfItems>();
var listItem1 = new ListItem
{
Content = "My first list item!"
};
var listItem2 = new ListItem
{
Content = "My second list item!"
};
var listOfItems1 = new ListOfItems
{
Id = 1
};
listOfItems1.Item.Add(listItem1);
listOfItems1.Item.Add(listItem2);
model.Add(listOfItems1);
return View(model);
}
A little bit of refactoring and you're good to go :)
I am batting 1000 here with my questions. So i will try to be as descriptive as possible.
I have multiple views in a layout that come from different models.
When a record is selected from a list it opens this layout. At the top of the layout it displays the record information in a table format. This is a simple ID - /AuditSchedule/1122. This is currently the Body. This works.
In another area of the layout i have a list of action links (side menu) that are generated from a another table. The links, I think should be as follows but not sure /AuditSchedule/1122/1. This was accomplished by using the Global.asax with routes.
Naturally when you open this layout you should get all the above plus the first record of the next area of the layout which is the form. In this form i need it to show a question from a table of questions and have a set of check boxes created with, what i will call scores, to the right of the question. These scores are also in a table called scores. Everything that i have in this is pretty much in a data table so that everything can be edited and changed if needed.
When a user submits the form it will store in another table called MainAnswers the id's of the auditSchedule, mainQuestion, and a string of the score. This table is a blank table so it would insert a new record for every Main Question for that AuditSchedule.
So far i have had no luck with help on this. If anyone could point me to an example of this that they have seen. It would be great. I cannot be the only one that has tried to do this. However i am new to MVC C#. If this were Zend and PHP i would have no issues.
I have used the code first approach. All of my relationships are done. The issue lies in implementing the view and saving the information in the right tables.
Anyone that can help would be much appreciated. I am struggling here.
Updated 08/16/2012 3:12pm
Let me take baby steps first.
I want to be able to select a menu Item from the side and have a list of questions come up from that section. Here is my Code:
#{ Layout = null; }
#model QQAForm.ViewModels.AuditFormEdit
<table width="698" border="2" cellpadding="2">
<tr>
<td align="center"><b>Section</b><br />1.0</td>
<td>
<br />(Title of Section Goes Here - SubcategoryName - Located in Subcategory Model)<br />
<br />
(Child Questions Go here - QuestionText - Located in ChildQuestion Model)
</td>
<td>
(This should be the result of what is written in AuditFormEdit view model - it does not currently work - Nothing shows up)
#for (int index = 0; index < Model.ScoreCardCheckBoxHelperList.Count; index++)
{
#Html.CheckBoxFor(m => m.ScoreCardCheckBoxHelperList[index].Checked)
#Html.LabelFor(m => m.ScoreCardCheckBoxHelperList[index], Model.ScoreCardCheckBoxHelperList[index].ScoreName)
#Html.HiddenFor(m => m.ScoreCardCheckBoxHelperList[index].ScoreID)
#Html.HiddenFor(m => m.ScoreCardCheckBoxHelperList[index].ScoreName)
}
</td>
</tr>
</table>
Here is the View model i am working on:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using QQAForm.Models;
namespace QQAForm.ViewModels
{
public class AuditFormEdit
{
public List<SubcategoryHelper> SubcategoryHelperGet { get; set; }
public class SubcategoryHelper : Models.SubCategory
{
public SubcategoryHelper(Models.SubCategory subCat)
{
this.SubCategoryID = subCat.SubCategoryID;
this.SubcategoryName = subCat.SubcategoryName;
}
}
public Models.MainAnswer ScoreInstance { get; set; }
public List<ScoreCardCheckBoxHelper> ScoreCardCheckBoxHelperList { get; set; }
public void InitializeScoreCheckBoxHelperList(List<Models.Score> ScoreList)
{
if (this.ScoreCardCheckBoxHelperList == null)
this.ScoreCardCheckBoxHelperList = new List<ScoreCardCheckBoxHelper>();
if (ScoreList != null
&& this.ScoreInstance != null)
{
this.ScoreCardCheckBoxHelperList.Clear();
ScoreCardCheckBoxHelper scoreCardCheckBoxHelper;
string scoreTypes =
string.IsNullOrEmpty(this.ScoreInstance.Score) ?
string.Empty : this.ScoreInstance.Score;
foreach (Models.Score scoreType in ScoreList)
{
scoreCardCheckBoxHelper = new ScoreCardCheckBoxHelper(scoreType);
if (scoreTypes.Contains(scoreType.ScoreName))
scoreCardCheckBoxHelper.Checked = true;
this.ScoreCardCheckBoxHelperList.Add(scoreCardCheckBoxHelper);
}
}
}
public void PopulateCheckBoxsToScores()
{
this.ScoreInstance.Score = string.Empty;
var scoreType = this.ScoreCardCheckBoxHelperList.Where(x => x.Checked)
.Select<ScoreCardCheckBoxHelper, string>(x => x.ScoreName)
.AsEnumerable();
this.ScoreInstance.Score = string.Join(", ", scoreType);
}
public class ScoreCardCheckBoxHelper : Models.Score
{
public bool Checked { get; set; }
public ScoreCardCheckBoxHelper() : base() { }
public ScoreCardCheckBoxHelper(Models.Score score)
{
this.ScoreID = score.ScoreID;
this.ScoreName = score.ScoreName;
}
}
}
}
Here is the controller parts:
//get
public ActionResult _Forms(int section)
{
AuditFormEdit viewModel = new AuditFormEdit();
//viewModel.ScoreInstance = _db.MainAnswers.Single(r => r.MainAnswerID == id);
_db.SubCategories.Single(r => r.SubCategoryID == section);
viewModel.InitializeScoreCheckBoxHelperList(_db.Scores.ToList());
return View(viewModel);
}
//post
[HttpPost]
public ActionResult _Forms(AuditFormEdit viewModel)
{
if (ModelState.IsValid)
{
viewModel.PopulateCheckBoxsToScores();
_db.Entry(viewModel.ScoreInstance).State = System.Data.EntityState.Modified;
_db.SaveChanges();
return RedirectToAction("/");
}
else
{
return View(viewModel);
}
}
So if you look at the Layout, where it show the spot for _Forms the Section should change with the link /AuditSchedule/1132/1 - it does not. As well as my check boxes which currently do not show up either.
...my check boxes which currently do not show up either.
That's because the line which sets the viewModel.ScoreInstance in your controller's GET action is commented out:
//viewModel.ScoreInstance = _db.MainAnswers.Single(r => r.MainAnswerID == id);
Hence viewModel.ScoreInstance is null and in InitializeScoreCheckBoxHelperList you fill the ScoreCardCheckBoxHelperList only when viewModel.ScoreInstance is not null:
if (this.ScoreCardCheckBoxHelperList == null)
this.ScoreCardCheckBoxHelperList = new List<ScoreCardCheckBoxHelper>();
if (ScoreList != null
&& this.ScoreInstance != null)
{
//... add elements to ScoreCardCheckBoxHelperList
}
Empty ScoreCardCheckBoxHelperList = no checkboxes.