.net mvc 3 passing related classes from the controller - c#

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

Related

ViewModel not working. Display from two tables

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
}

When should I use ViewBag/model?

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.

Return 2 lists from one model to a view in MVC

I am new to MVC, and I am struggling with viewmodels. I would like to return 2 lists from the same model into one view. I will need 2 foreach loops to show records of a different "status" on one view. Because both lists are coming from one model, is it necessary to create a viewmodel?
I have tried the following, but my view is not finding the item type for each list.
public class PipelineViewModel
{
public int LeadID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Status{ get; set; }
public string LoanAgent{ get; set; }
public List<WebLead> PipeNewLeads { get; set; }
public List<WebLead> PipeDispLeads { get; set; }
}
Note the List is domain model for the table I am pulling the lists from. Is that correct?
Next in my controller:
public ActionResult Index(string LoanAgent)
{
var viewModel = new PipelineViewModel
{
PipeNewLeads = db.WebLeads
.Where(l => l.LoanAgent.Equals(LoanAgent) && l.Status.Equals("New")).ToList(),
PipeDispLeads = db.WebLeads
.Where(l => l.LoanAgent.Equals(LoanAgent) && l.Status.Equals("Disp")).ToList(),
};
return View(viewModel);
I know that the controller is wrong. I need to be referencing the viewmodel somehow, but I have tried a few ways and keep getting errors.
In the view, I used
#model LoanModule.ViewModels.PipelineViewModel
and I tried to call each list like this, but it didn't work.
#foreach (var item in Model.PipeNewLead)
#foreach (var item in Model.DispNewLead)
I think I am almost there, but I am not sure what I am doing wrong in the controller. I would appreciate any help or references!
Consdering you have:
public ActionResult Index(string LoanAgent)
{
var viewModel = new PipelineViewModel
{
PipeNewLeads = ....
PipeDispLeads = ....
};
return View(viewModel);
}
Your view foreachs should be:
#foreach (var item in Model.PipeNewLeads)
#foreach (var item in Model.PipeDispLeads)
Note the spelling ;-)
This works perfectly fine for me:
public ActionResult Contact()
{
var viewModel = new PipelineViewModel
{
PipeNewLeads = new List<WebLead>(),
PipeDispLeads = new List<WebLead>(),
};
return View(viewModel);
}
And view:
#model WebApplication1.Controllers.PipelineViewModel
#foreach (var item in Model.PipeDispLeads)
{
<p>Disp</p>
}
#foreach (var item in Model.PipeNewLeads)
{
<p>New</p>
}
Your issue is somewhere else.
I would:
Look at where you are referencing IEnumerable<PipelineViewModel> and find out why. This is most likely in a completely different Razor view, therefore making that error unrelated.
Check what your entity framework is returning. The EF error you are getting is probably because either:
Database not available
EF Model is different to database
Login issues
Network issues
Obviously your EF error could be something different, but those are some suggestions
Came across this issue...I think it's just a typo.
You have public List<WebLead> PipeNewLeads { get; set; } in model but #foreach (var item in Model.PipeNewLead) in the view. Note the plural, so of course you saw the exceptions your got (not found/does not contain).
You can test to see if you have data from your model in this way:
#if(Model.PipeNewLeads != null && Model.PipeNewLeads.Count > 0)
{
foreach (var item in Model.PipeNewLeads)
{
}
else
{
<p>No PipeNewLeads</p>
}
}
But you really should be creating Unit Tests where you'd be able to determine which models are actually in your view.

Asp net MVC 5 and listbox with linq

After several days of reading on how to fill a listbox with linq i am completely lost on what I have to do to make this work. I know I have to have a viewmodel or something like that but since i cannot reference 2 models in the same view I don t know what to do.
this is the model i have
public class ABC
{
public int a { get; set; }
public int b { get; set; }
public int c { get; set; }
public string d { get; set; }
public string Type { get; set; }
public int ID { get; set; }
public class ABCDBContext : DbContext
{
public DbSet<ABC> ABCs { get; set; }
}
}
public class ABCModel
{
public string type{ set; get; }
public List<ABC> ABCs { set; get; }
}
The controller i know i am missing a lot of things but I don´t know how to fill the list with that linq query nor how to call this controller from the view without using beginform (I will have more than 1 listbox in this form )
public ActionResult GetStuff()
{
var Types = from m in db.ABCs
select m.Type.Distinct();
return View(Types);
}
And then on my view i am required to have
#model IEnumerable so i can show on that page everything that the table abc has.
So, can I create a viewModel that fills the lsitbox (I will need to fill at least 6 with the parameters that abc has so the user can search for abcs with those values, but i suppose that if i can understand how to do 1 i can do it for 6) with what I want and that allows me to show the entries that abc has?
and this is the view ( i know a lot of things are wrong with it but its just to give an example)
#using (Html.BeginForm("getSelectedValues", "ABC", FormMethod.Get))
{
Type:#Html.DropDownList()
a:#Html.DropDownList()
b:#Html.DropDownList()
//and so on
<input type="submit" value="Filter" />
}
Pardon for my ignorance for any aspect i missed but i am new to asp net mvc 5, also if anyone could just guide me or send me a guide on what I have to do i would greatly appreciate it.
What I would recommend is you create a ViewModel named MYPAGENAMEViewModel.cs, then in that ViewModel you have all the 'model' / data that you need for the view.cshtml file.... i.e. something like the following:
public class MYPAGENAMEViewModel
{
public List<Types> MyTypes { get; set; }
public List<OtherTypes> MyOtherTypes { get; set; }
}
then in your action.
public ActionResult GetStuff()
{
MYPAGENAMEViewModel myViewModel = new MYPAGENAMEViewModel();
myViewModel.MyTypes = from m in db.ABCs
select m.Type.Distinct();
myViewModel.MyOtherTypes = from m in db.CBAs
select m.Type.Distinct();
return View(myViewModel);
}
then at the top of your view:
#model MYPAGENAMEViewModel
and then later in the view:
MyTypes<br />
#Html.DropDownListFor(model => model.MyTypes.TypeId, (SelectList)model.MyTypes.TypeName)
MyOtherTypes<br />
#Html.DropDownListFor(model => model.MyOtherTypes.TypeId, (SelectList)model.MyOtherTypes.TypeName)
I'm not totally sure what you're after but this might be the solution.
First off, you'd create a ViewModel, such as:
public class MyViewModel
{
List<ABC> abcs { get; set; } // Your database values that you're going to loop through
List<WhateverALookupShouldBe> aDropDownValues { get; set; }
List<WhateverBLookupShouldBe> bDropDownValues { get; set; }
// etc
}
Populate those values in your Data or Controller.
If you're looping through a list and submitting values back you'll want an editor for, so make a folder under your view folder called "EditorTemplates". Then add a cshtml file with the same name as your model, e.g. ABC.
On your main view you would have:
#model MyViewModel
#Html.EditorFor(model => model.abcs)
Now in your ABC Editor template you would write something like this for your drop down lists:
#model ABC
#Html.DropDownListFor(model => model.a, new SelectList(Model.aDropDownValues, "DataValueFieldName", "DataTextFieldName"));
When you submit it back you should have the data returned to you.

ASP.NET MVC Dropdownlist Data Does Not Insert

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.

Categories

Resources