I think that using ViewBag is faster than model.
My example is:
In the action:
public ActionResult MyAction()
{
ViewBag.Data = (from m in myDatabase.myTable select m).ToList();
}
In the view:
#foreach(var item in ViewBag.Data)
{
<tr>
<td>#item.myColumn</td>
</tr>
}
By using model:
[Table("tbl_mytable")]
public class MyTable()
{
public int Id { get; set; }
public int Column2 { get; set; }
public int Column3 { get; set; }
// ...
public IEnumerable<MyTable> Data { get; set; }
}
in the model:
public class MainClass
{
public List<MyTable> MyMethod()
{
List<MyTable> list = new List<MyTable>();
// code logic to find data in database and add to list
list.Add(new MyTable
{
Column2 = ...
Column3 = ...
});
return list;
}
}
in the action:
public ActionResult MyAction()
{
MainClass mc = new MainClass();
MyTable model = new MyTable();
model.Data = mc.MyMethod();
return View(model);
}
in the view:
#using MyProjectName.Models
#model MyProjectName.Models.MyTable
#foreach(MyTable item in Model.Data)
{
<tr>
<td>#item.Column1</td>
</tr>
}
Both of them are working, and ViewBag is easier than model.
So, can you tell me: When should I use ViewBag/model?
ViewBag is not faster. In both cases your creating a model of List<MyTable>. All the code you have show for creating your 'MyTable' model is pointless - your just creating a new collection containing duplicates from the existing collection. In the controller it just needs to be
public ActionResult MyAction()
{
var model = (from m in myDatabase.myTable select m).ToList();
return View(model);
}
and in the view
#model List<MyProjectName.Models.MyTable> // a using statement is not required when you use the fully qualified name
#foreach(var item in Model)
{
<tr>
<td>#item.myColumn</td>
</tr>
}
And your MyTable model should not have a property public IEnumerable<MyTable> Data { get; set; } unless your creating a hierarchical model.
Always use models (preferably view model) in you view so you can take advantage of strong typing (vs ViewBag which is dynamic)
use model if you want strongly-typed add Messages to your View Model. Otherwise, stick with ViewBag.
Related
I am trying to send a enumerable customer object in ViewResult to Index view of customers. but i am not able to use foreach with Model on the view page.
The error associated with model says -
"foreach statement cannot operate on variables of type 'Customers' because 'Customers' does not contain a public instance definition for 'GetEnumerator'"
i tried to switch the code with an array but the problem remains
public class Customers
{
public int Id { get; set; }
public string Name { get; set; }
}
//Customer Controller
public class CustomersController : Controller
{
public IActionResult Index()
{
var customers = GetCustomers();
return View(customers));
}
public IActionResult Details(int id)
{
var customer = GetCustomers().SingleOrDefault(c => c.Id == id);
return View(customer);
}
private IEnumerable<Customers> GetCustomers()
{
var customer = new List<Customers>
{
new Customers { Id = 1, Name = "John Smith"},
new Customers { Id = 2, Name = "Mary Williams"}
};
return customer;
}
}
//View page Index
#foreach (var customer in Model)
{
<tr>
<td><a asp-area="" asp-controller="Customers" asp-action="Details">#customer.Name</a></td>
</tr>
}
The error displayed Under Model
when the foreach statements executes, the it should output the name of the customers accordingly
In view you describe model as single object Customer.
Change your view:
#model IEnumerable<Vidly.Model.Customer>
Or:
#model List<Vidly.Model.Customer>
public class StudentViewModel
{
public Student studentVm { get; set; }
public StudentAdditionalInfo studentAdditionalInfoVm { get; set; }
public int rcImgToProcess { get; set; }
}
studentVm and studentAdditionalInfoVm stores the data of my 2 Tables Student and StudentAdditionalInfo, these contains multiple records
rcImgToProcess store the data of the record count that I passed from my controller, so it is only a single data (I can use ViewBag but for some reason I prefer passing it to Model)
<div>
I want the value of [rcImgToProcess] here
</div>
#foreach (var item in Model)
{
<tr>
<td>#item.studentVm.Id </td>
<td>#item.studentVm.StudentCourse</td>
<td>#item.studentAdditionalInfoVm.MotherName</td>
<td>#item.studentAdditionalInfoVm.FatherName</td>
</tr>
}
Controller
int rcImgToProcess = "0";
rcImgToProcess= 1001;
var studentList = from s in student
join st in studentAdditionalInfo on s.Id equals st.Id into st2
from st in st2.DefaultIfEmpty()
select new StudentViewModel {
studentVm = s,
studentAdditionalInfoVm = st,
rcImgToProcess = rcImgToProcess
};
return View(studentList);
How can I call rcImgToProcess to display on the header part of the page
are you referencing the StudentViewModel in the view correctly?
you should have #model <namespaceForStudentViewModel>.StudentViewModel at the top of the file.
eg #model SampleApplication.ViewModels.StudentViewModel
then you can use <div>#Model.rcImgToProcess</div> anywhere in the view
I'am trying to view information from two tables in a view with the view model, but it does not work.
This gives two tables with information.
public class HeatImage
{
[Key]
public int ImageId { get; set; }
public string name { get; set; }
}
public class HeatingArea
{
[Key]
public int ID { get; set; }
public string Area { get; set; }
}
My viewmodel
public class HeatingAreaViewModel
{
public IEnumerable<HeatImage> heatingImage { get; set; }
public IEnumerable<HeatingArea> heatingArea { get; set; }
}
My controller
public ActionResult Index()
{
HeatingAreaViewModel mymodel = new HeatingAreaViewModel();
mymodel.heatingArea = db.HeatingAreas.ToList();
mymodel.heatingImage = db.HeatImages.ToList();
return View(mymodel);
}
And my view
#model IEnumerable<Project.Models.HeatingAreaViewModel>
#foreach (var item in Model)
{
#item.heatingArea
}
Error
The model item passed into the dictionary is of type
'Project.Models.HeatingAreaViewModel', but this dictionary requires a
model item of type
'System.Collections.Generic.IEnumerable`1[Project.Models.HeatingAreaViewModel]'.
The error message tells it: the View expects IEnumerable of HeatingAreaViewModel, while your controller action sends to the view only HeatingAreaViewModel.
So either correct the controller to send a list of HeatingAreaViewModel, or correct the view to expect only a single instance of HeatingAreaViewModel.
You are sending through appels and expecting oranges, that is why you getting the error.
In your controller you are sending through a single (one) instance of HeatingAreaViewModel. In your view you are making provision to except a list of HeatingAreaViewModel instances (more than 1).
Reading through your replies you want to use both HeatImage and HeatingArea in each loop iteration. You will need to change your view model to accommodate this. For example, create a view model that can accommodate both:
public class HeatViewModel
{
public HeatImage HeatImage { get; set; }
public HeatingArea HeatingArea { get; set; }
}
You will pass this HeatViewModel as a list to your view.
public ActionResult Index()
{
// This is just dummy code
HeatingViewModel model1 = new HeatingAreaViewModel();
// Populate the properties
model1.HeatImage = new HeatImage();
// Populate the properties
model1.HeatingArea = new HeatingArea();
HeatingViewModel model2 = new HeatingAreaViewModel();
// Populate the properties
model2.HeatImage = new HeatImage();
// Populate the properties
model2.HeatingArea = new HeatingArea();
// Now pass this list to the view
List<HeatingViewModel> models = new List<HeatingViewModel>();
return View(models);
}
In your view your code would look something like this:
#model List<Project.Models.HeatingViewModel>
#foreach (var item in Model)
{
<p>item.HeatImage.Name</p>
<p>item.HeatingArea.ID (I have used ID because I don't know what your Area looks like)</p>
}
This way you have both objects in a single loop. You will just have to go and figure out how you are going to populate them, this is where the bulk of the work will be done.
I also noticed that you you start your properties in the lower case, best practices start them with caps. For example:
public HeatImage heatImage { get; set; }
...should be...
public HeatImage HeatImage { get; set; }
I hope this helps :)
Your view is expecting an IEnumerable of HeatingAreaViewModel and it seems as though your only passing a single instance.
Change this
#model IEnumerable<Project.Models.HeatingAreaViewModel>
to this
#model Project.Models.HeatingAreaViewModel
Then you'll be able to loop through the heatingArea and heatingImage properties like this
#foreach (var item in model.heatingArea)
{
//what ever you need to do here
}
If your problem is, how to iterate in one loop through the two lists of HeatingImage and HeatingArea, in your view, you have to redo your viewModel:
public class HeatingAreaViewModel
{
public HeatImage heatingImage { get; set; }
public HeatingArea heatingArea { get; set; }
}
and then the controller action:
public ActionResult Index()
{
heatingAreas = db.HeatingAreas.ToList();
heatingImages = db.HeatImages.ToList();
List<HeatingAreaViewModel> myModel = heatingAreas.Zip(heatingImages, (a, b) => new HeatingAreaViewModel {HeatingArea = a, HeatImage = b})
return View(mymodel);
}
Then the View will work, as it is.
However, I strongly advice against doing it this way. You would have to ensure, that corresponding elements in the two lists are in reality corresponding to each other. Which is very prone to errors. If there is an underlying logic that ties these two lists together (some relationship on database tables), I would use that one to join the lists/tables together, instead.
Simply your Model in the view doesn't matches what you sent from controller action, so I think you need to change your view to be like this:
#model HeatingAreaViewModel
#foreach (var item in Model.heatingImage)
{
#item.heatingArea
}
I have two models and I want to insert a row in the database with a foreign key relationship populated in the DropDownList. The Item model's data insert without problems but ManufacturerID does not get inserted (it inserts null). I could not find why.
Update: Uploaded the project to: http://mvcapplication2.codeplex.com/
<div class="editor-label">
#Html.LabelFor(model => model.ManufacturerID,"Manufacturer")
</div>
<div class="editor-field">
#Html.DropDownList("ManufacturerID",string.Empty)
#Html.ValidationMessageFor(model => model.ManufacturerID)
</div>
public class Item
{
public int ItemID { get; set; }
public string Serial { get; set; }
public string ItemName { get; set; }
public int? ManufacturerID { get; set; }
public Manufacturer Manufacturer { get; set; }
}
public class Manufacturer
{
public int ManufacturerID { get; set; }
public string ManufacturerName { get; set; }
public List<Item> Items { get; set; }
}
public ActionResult Create()
{
ViewBag.ManufacturerID = new SelectList(db.Manufacturers, "ManufacturerID", "ManufacturerName");
return View();
}
[HttpPost]
public ActionResult Create(Item ıtem)
{
if (ModelState.IsValid)
{
db.Items.Add(ıtem);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(ıtem);
}
I would prefer to NOT use the domain model in the view. I would create some view models specific to the view. Also, to transfer data from action method (ex : dropdown list), i would use a strongly typed approach, instead of the dynamic viewbag/ viewdata approach.
Create a view model class
public class CreateItemVM
{
public string SerialNumber { set;get;}
public int SelectedManufactureID { set;get;}
public List<SelectListItem> Manufacturers { set;get;}
public CreateItemVM()
{
Manufacturers =new List<SelectListItem>();
}
}
Now in your GET action method, create an object of our viewmodel, initialize the relevant values and send to the view.
public ActionResult Create()
{
var vm=new CreateItemVM();
vm.Manufacturers=GetManufacturerList();
return View(vm);
}
private List<SelectListItem> GetManufacturerList()
{
List<SelectListItem> manuList=new List<SelectListItem>();
manuList=(from p in db.Manufacturers
select new SelectListItem {
Value=p.ID.ToString(),
Text=p.Name}
).ToList();
return manuList;
}
Now in our view, which is strongly typed to our Viewmodel,
#model CreateItemVM
#using(Html.Beginform())
{
#Html.DropDownListfor(x=>x.SelectedManufactureID ,
Model.Manufacturers,"select")
<input type="submit" />
}
And finally in our POST action method, we will read values from our posted viewmodel and assign it as the property values of our domain object and save it. the selected manufacturers ID value will be in the SelectedManufactureID property of our viewmodel.
[HttpPost]
public ActionResult Create(CreateItemVM model)
{
if(ModelState.IsValid)
{
Item domainObject=new Item();
domainObject.ManufacturerID =model.SelectedManufactureID ;
//set other relevant properties also
db.Items.Add(ıtem);
db.SaveChanges();
return RedirectToAction("Index");
}
// reload the dropdown before returning to the view
model.Manufacturers=GetManufacturerList();
return View(model);
}
Try to make the relationship more explicit, making properties virtual and adding an attribute:
public class Item
{
...
[ForeignKey("Manufacturer")]
public int? ManufacturerID { get; set; }
public virtual Manufacturer Manufacturer { get; set; }
}
public class Manufacturer
{
...
public virtual List<Item> Items { get; set; }
}
Edit:
And you can use a more tied way of building the drop down:
#Html.DropDownListfor(x=>x.SelectedManufactureID ,
ViewBag.ManufacturerID as SelectList,"Choose one")
Edit 2:
A better approach is to make a specific model for the view (called ViewModel) to represent data and build the view like #Shyju said.
Found the problem. The Viewbag I was sending to the view should be named as ManufacturerID, I was sending Manufacturers and somehow it was not matching although it populated the dropdown.
God my head is killing me.
Ok I have a controller where i want to pass nested data to the view. Which I am doing like so:
namespace helpme.mvc.Controllers
{
public class CategoryController : Controller
{
private HelpMeContext db = new HelpMeContext();
public ViewResult Index()
{
var model = db.Category.Include(c => c.SubCategories).ToList();
return View(model);
}
}
}
But it is not working. SubCategories come out empty even though there are rows in them. Any suggestions?
Using a break point i see that the model is being correctly populated, but nothing is displayed in the view and no error msg.
Here is the view code:
#model IEnumerable<helpme.mvc.Models.Category>
#{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
#Html.ActionLink("Create New", "Create")
</p>
#Model.First().SubCategories.First().Name // UPDATE, THIS DISPLAYS A VALUE, SO WHY DOES THE CODE BELOW JUST DISPLAY THE FIRST LEVEL (Categories)?
<ul>
#foreach (var c in Model) {
<li>
<ul>
#foreach (var sc in c.SubCategories)
{
#Html.Display(sc.Name)
foreach (var ssc in sc.SubSubCategories)
{
#Html.Display(ssc.Name)
}
}
</ul>
</li>
}
</ul>
For some reason it only displays the first level, as if it did not receive the SubCategories, even though the break-point proves that it did.
And here is the model, just for reference:
public class Category
{
public Category()
{
SubCategories = new List<SubCategory>();
}
public int ID { get; set; }
[StringLength(255, MinimumLength = 1)]
public string Name { get; set; }
public ICollection<SubCategory> SubCategories { get; set; }
}
public class SubCategory
{
public SubCategory()
{
SubSubCategories = new List<SubSubCategory>();
}
public int ID { get; set; }
[Required()]
[StringLength(255, MinimumLength = 1)]
public string Name { get; set; }
public Category Category { get; set; }
public ICollection<SubSubCategory> SubSubCategories { get; set; }
}
Perhaps explicitly type define your model as List<Category>. Suspect it's currently ObjectQuery<Category>? Can you confirm?
As you know, your View is expecting a IEnumerable<helpme.mvc.Models.Category>.
Try explicitly casting your model to the type your View wants.
List<Category> model = db.Category.Include(c => c.SubCategories).ToList();
It's not clear where the root of the problem is: in the data access, or in accessing the objects in the View. Can you load up some dummy Category and SubCat in that method, and pass known good values to your View? That'll help determine where the issue lies.
I would guess that .Include().ToList() is probably not doing what you think it's doing.
SubCategories probably aren't being mapped to the Category objects you pass to the view.
Try mapping them manually with view models:
e.g.
public class PageViewModel
{
public PageViewModel()
{
this.Categories = new List<Category>();
}
public IEnumerable<Category> Categories {get;set;}
}
...etc
And in your code:
var viewModel = new PageViewModel();
foreach (var category in db.Categories)
{
var categoryVM = new CategoryViewModel();
foreach (var subcategory in category.SubCategories)
{
categoryVM.SubCategories.Add(....
}
}
...etc
Do only one level first (to SubCategory) to confirm.
Try to replace
#Html.Display(sc.Name)
with
#sc.Name
Also please remember you can always put breakpoints in view code, and see how the rendering goes.
Hope you find this useful.
-Zs