Incorporating custom view model data from multiple tables into a view - asp.net-mvc-3

I can't understand why I can't find anything that clearly explains how to do this with MVC. Seems like a pretty routine issue:
I have three tables:
PackageName: PackageNameID,PackageName
Food: FoodID,Food
PackageContent: PackageContentID, PackageNameID, FoodID, Qty
The application is supposed to describe packages of food. For example, a package named "A" might contain 4 onions and 3 peppers. Another package, "B", might contain 2 rolls and 2 onions.
Currently I have a custom view model ("PackageNameModel") that collects the data:
public ViewResult Index() {
var viewModel =
from pn in db.PackageNames
from pc in db.PackageContents
.Where(p => p.PackageNameID == pn.PackageNameID).DefaultIfEmpty()
from f in db.Foods
.Where(f => f.FoodID == pc.FoodID).DefaultIfEmpty()
select new PackageFoodModel { PackageName = pn, PackageContent = pc, Food = f };
return View( viewModel );
}
This returns the data correctly, but what do I do with it so that the view is actually one that is useful for the application?
Using this in my view:
#foreach (var item in Model) {
<tr>
<td>
#Html.DisplayFor(modelItem => item.PackageName.PackageName1 ) ,
#Html.DisplayFor(modelItem => item.Food.Food1)=#Html.DisplayFor(modelItem => item.PackageContent.Qty)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id=item.PackageName.PackageNameID }) |
#Html.ActionLink("Details", "Details", new { id = item.PackageName.PackageNameID }) |
#Html.ActionLink("Delete", "Delete", new { id = item.PackageName.PackageNameID })
</td>
</tr>
}
I am able to display the following:
- A , Onions=4 Edit | Details | Delete
- A , Peppers=3 Edit | Details | Delete
- B , Rolls=2 Edit | Details | Delete
- B , Onions=2 Edit | Details | Delete
This is not very useful. What I'm trying to do is display something like this:
- A (Onions=4,Peppers=3) Edit | Details | Delete
- B (Rolls=2,Onions=2) Edit | Details | Delete
Eventually, the next page down after navigating to the "Edit" action would provide an editable name for the package as well as a table of all available foods and an adjacent quantity box so that the foods/quantities within each package may be updated.
Can anyone explain how this is done within the MVC framework?
All the tutorials I have seen on the web/in books deal with data that is much simpler. Can anyone point me to some sample code / tutorials that deal with something like this?
Thanks in advance!

You need to aggregrate your results.
Create a view model like this
public class PackageViewModel
{
public int ID { set;get;}
public string Name { set;get;}
public List<FoodItem> FoodItems { set;get;}
public PackageViewModel()
{
FoodItems=new List<FoodItem>();
}
}
public class FoodItem
{
public string FoodName { set;get;}
public int Quantity { set;get;}
}
and in your Get action, You need to do the aggregate the data from your data which is ccoming from your data access layer and fill to the ViewModel List
public ActionResult IndeX()
{
List<PackageViewModel> listVM=new List<PackageViewModel>();
//Get your data and do aggregation and fill in listVM
return View(listVm)l
}
and in our view,
#model List<PackageViewModel>
#foreach(var item in Model)
{
<tr>
<td>#item.Name</td>
<td>
foreach(var food in item.FoodItems)
{
#food.FoodName - #food.Quantity
}
</td>
<td>
#Html.ActionLink("Edit","Edit",new {#id=item.ID})
</td>
</tr>
}

Related

Selecting a single value using linq if already duplicate values exist in mvc4

I have a table
public class Product
public int Id { get; set; }
public string Model_Name { get; set; }
public string Company_Name { get; set; }
public Nullable<int> Price { get; set; }
public string Photo { get; set; }
Now the table contains multipe items so I just want to retrive the name of the company and dispay it. However the column Company_Name contains duplicate values. How do I need to check if it contains duplicate names then also display the value only once.I want to check it within view. Here is the View.
#foreach (var item in Model)
{
<tr>
//Anything extra could be added here to check the duplicate values
<td>#Html.ActionLink(item.Company_Name, "Brand",
new { Company_Name = item.Company_Name })
</td>
</tr
}
Here is how the table looks like
Id Model_Name Company_Name price photo
1 S7862 Samsung 1500 something
2 Galaxy Samsung 1500 something
3 Apple Apple 2000 something
4 Desire HTC 1500 something
5 Desire X2 HTC 1500 something
6 Nokia Lumia Nokia 1500 something
Now in the above table a few records are missing and if I write
#foreach(var item in Model)
{
<tr>
<td>
#Html.ActionLink(item.Company_Name, "Brand", new {Company_Name=item.Company_Name})
</td>
</tr>
}
now here I get the result as
Brand
Samsung
Samsung
Apple
HTC
HTC
Nokia
I want it to be as
Brand
Samsung
Apple
HTC
Nokia
Any ideas will be appreciated. I want the Linq to come within the view.
Try using a Distinct() on that Property:
#foreach (var companyName in Model.Select(m => m.Company_Name).Distinct())
{
<tr>
//Anything extra could be added here to check the duplicate values
<td>#Html.ActionLink(companyName, "Brand",
new { Company_Name = companyName })
</td>
</tr
}
How about Grouping them on Company_Name:
#{
var grouped = Model.GroupBy(x=>x.Company_Name);
}
#foreach (var item in grouped)
{
<tr>
//Anything extra could be added here to check the duplicate values
<td>#Html.ActionLink(item.Key"Brand",
new { Company_Name = item.Key })
</td>
</tr
}

MVC Using relationships Database first

First off I have a list of movies setup from a database working fine on localhost/movies
However I wanted to go a step further so I created an Actors page, I now list all the Actors to ( On their own just their names )
What I want to do is you have for example -
Leonardo DiCaprio (id = 1)
I would like to check the page to see if the id matches him etc ( This is already done as you will see in code below )
Next I would like All his movies he has been in to be shown in another row on the table. As you can see VIA This screenshot
http://gyazo.com/ae193d80e7a39969116f76ab6568f38e.png
Instead of just the movies he has starred in they all show up, as you can see below I made a relationship between tables Actor & Movie & ActorsMovies, just linking the Ids
I have 3 tables setup like the following:
Movies -
Id (PK),
Name
Actor:
Id(PK),
Name
ActorsInMovies:
MovieId(PK),
ActorId(PK)
Here is my controller:
public ActionResult Movies( int id )
{
var model = MoviesViewModel(); //Create our model
model.PageTitle = = actor.Name + "'s' Movies"; // set page title to actors name
model.Actorname = actor.Name; // I do this to ensure the name always matches the id
var items = db.Movies.OrderBy(i => i.Name).AsQueryable(); //Link items to Movies DB and make it queryable ( as I want to use pagedLists later when its working )
if (!String.IsNullOrEmpty(actor.Name)) //if name is not null
{
items = items.Where( a => a.Id == id );//I already know this is wrong but I dont know what the correct process is I think the theory part behind it I understand, here I need to check the relationships to ensure that the actor matches with the movies I just am unsure how to do it.
}
model.MovieList = items.ToList();
return View(model);
}
and my views is just a normal table with foreach loop in ( im gonna remove the Html so it doesnt get to messy:
#foreach (var item in Model.MovieList)
{
<tr>
<td>#Html.DisplayFor(modelItem => Model.Actorname)</td>
<td>
<!-- MS:This part populates the table row-->
#Html.DisplayFor(modelItem => item.Name)
</td>
<td>
#Html.ActionLink("Edit", "Edit", new { id = item.Id }, new { #class = "btn btn-default" })
#Html.ActionLink("Details", "Details", new { id = item.Id }, new { #class = "btn btn-default" })
#Html.ActionLink("Delete", "Delete", new { id = item.Id }, new { #class = "btn btn-danger" })
</td>
</tr>
}
The easiest way to map many-to-many relationships in MVC and to also limit the number of database queries, is like below. This also does pagination, since you mentioned the desire.
MovieActor schema: (many-to-many map of Movie and Actor)
ID (PK)
ActorID (FK to Actor.ID)
MovieID (FK to Movie.ID)
CharacterName
Controller:
const int Rows = 50; // put this wherever appropriate, or customize and save in Session
/// <summary>
/// This shows all the Movies the given Actor was in.
/// </summary>
/// <param name="id">Actor ID</param>
/// <param name="page">Page of Movies to display, starting at 1</param>
public ActionResult Movies(int id, int page = 1)
{
// get the actor
Actor actor = MyDataContext.GetActorByID(id); // one database call
// get the actor's movies
List<Movie> movies = MyDataContext
.GetMoviesByActorID(actor.id) // no database call
.Skip((page - 1) * Rows)
.Take(Rows); // one database call
// build the view model
// NOTE: could move this into a constructor MoviesViewModel(actor, movies)
var viewModel = MoviesViewModel
{
PageTitle = actor.Name + "'s Movies",
ActorName = actor.Name,
Movies = movies
};
// show the view
return View(model);
}
Model:
public partial class MyDataContext
{
public Actor GetActorByID(int id)
{
return Actors.FirstOrDefault(x => x.ID == id)
}
public IQueryable<List<Movie>> GetMoviesByActorID(int actorID)
{
return Movies.Where(x => x.MovieActor.ActorID == actorID);
}
}
And the view is like any normal IEnumerable or List.

asp.net mvc linq I want to fetch two columns from two join table

I have two tables join with PrId column, I have a view which show two columns from both tables, first column from first table and second column from second table. my actionresult is :
public ActionResult extrapoints()
{
ViewBag.dList = (from m in _session.customer
join p in _session.Products on m.PrId equals p.PrId
where m.UserId== 'john'
select new { FName = m.FName, price=p.price});
return View();
}
and in view i want to show both FName and price, I have following view :
#foreach (var item in ViewBag.dList)
{
<tr>
<td>#item.FName </td>
<td> #item.price</td>
</tr>
}
but is show error object' does not contain a definition for FName but when i use without Fname,price like
#foreach (var item in ViewBag.dList)
{
<tr>
<td>#item</td>
<td> #item</td>
</tr>
}
is shows :
{ FName = Shailendra, price= 1000 }
how to solve, please help
You Can Convert the Anonymous class to typed class, first add this class
class Test
{
public string FName { get; set; }
public decimal price { get; set; }
}
and make you linq statment as
ViewBag.dList = (from m in _session.customer
join p in _session.Products on m.PrId equals p.PrId
where m.UserId== 'john'
select new Test{ FName = m.FName, price=p.price});
and finally Cast the viewpage to type "Test"
#foreach (var item in (List<Test>)ViewBag.dList)
{
<tr>
<td>#item.Fname</td>
<td> #item.price</td>
</tr>
}

MVC How to pass a list of objects with List Items POST action method

I want to post a List of items to controller from Razor view , but i am getting a List of objects as null
My class structre is
Model:
List<Subjects> modelItem
class Subjects
{
int SubId{get;set;}
string Name{get;set;}
List<Students> StudentEntires{get;set;}
}
class StudentEntires
{
int StudId{get;set;}
string Name{get;set;}
int Mark{get;set;}
}
The model itself is a list of items and every items contain List of child items as well. Example model is a list of Subjects and every subject contains a List of Students, and i want to input mark for every student
My View is like
#model IList<Subjects>
#{
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
if (Model.Count > 0)
{
#for (int item = 0; item < Model.Count(); item++)
{
<b>#Model[item].Name</b><br />
#foreach (StudentEntires markItem in Model[item].StudentEntires)
{
#Html.TextBoxFor(modelItem => markItem.Mark)
}
}
<p style="text-align:center">
<input type="submit" class="btn btn-primary" value="Update" />
</p>
}
}
And in controller
[HttpPost]
public ActionResult OptionalMarks(int Id,ICollection<Subjects> model)
{
//BUt my model is null. Any idea about this?
}
You're finding this difficult because you're not utilising the full power of the MVC framework, so allow me to provide a working example.
First up, let's create a view model to encapsulate your view's data requirements:
public class SubjectGradesViewModel
{
public SubjectGradesViewModel()
{
Subjects = new List<Subject>();
}
public List<Subject> Subjects { get; set; }
}
Next, create a class to represent your subject model:
public class Subject
{
public int Id { get; set; }
public string Name { get; set; }
public List<Student> StudentEntries { get; set; }
}
Finally, a class to represent a student:
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int Grade { get; set; }
}
At this point, you have all the classes you need to represent your data. Now let's create two controller actions, including some sample data so you can see how this works:
public ActionResult Index()
{
var model = new SubjectGradesViewModel();
// This sample data would normally be fetched
// from your database
var compsci = new Subject
{
Id = 1,
Name = "Computer Science",
StudentEntries = new List<Student>()
{
new Student { Id = 1, Name = "CompSci 1" },
new Student { Id = 2, Name = "CompSci 2" },
}
};
var maths = new Subject
{
Id = 2,
Name = "Mathematics",
StudentEntries = new List<Student>()
{
new Student { Id = 3, Name = "Maths 1" },
new Student { Id = 4, Name = "Maths 2" },
}
};
model.Subjects.Add(compsci);
model.Subjects.Add(maths);
return View(model);
}
[HttpPost]
public ActionResult Index(SubjectGradesViewModel model)
{
if (ModelState.IsValid)
{
return RedirectToAction("Success");
}
// There were validation errors
// so redisplay the form
return View(model);
}
Now it's time to construct the views, and this part is particularly important when it comes to sending data back to a controller. First up is the Index view:
#model SubjectGradesViewModel
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
#Html.EditorFor(m => m.Subjects) <br />
<input type="submit" />
}
You'll notice I'm simply using Html.EditorFor, whilst passing Subjects as the parameter. The reason I'm doing this is because we're going to create an EditorTemplate to represent a Subject. I'll explain more later on. For now, just know that EditorTemplates and DisplayTemplates are special folder names in MVC, so their names, and locations, are important.
We're actually going to create two templates: one for Subject and one for Student. To do that, follow these steps:
Create an EditorTemplates folder inside your view's current folder (e.g. if your view is Home\Index.cshtml, create the folder Home\EditorTemplates).
Create a strongly-typed view in that directory with the name that matches your model (i.e. in this case you would make two views, which would be called Subject.cshtml and Student.cshtml, respectively (again, the naming is important)).
Subject.cshtml should look like this:
#model Subject
<b>#Model.Name</b><br />
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.Name)
#Html.EditorFor(m => m.StudentEntries)
Student.cshtml should look like this:
#model Student
#Html.HiddenFor(m => m.Id)
#Html.HiddenFor(m => m.Name)
#Html.DisplayFor(m => m.Name): #Html.EditorFor(m => m.Grade)
<br />
That's it. If you now build and run this application, putting a breakpoint on the POST index action, you'll see the model is correctly populated.
So, what are EditorTemplates, and their counterparts, DisplayTemplates? They allow you to create reusable portions of views, allowing you to organise your views a little more.
The great thing about them is the templated helpers, that is Html.EditorFor and Html.DisplayFor, are smart enough to know when they're dealing with a template for a collection. That means you no longer have to loop over the items, manually invoking a template each time. You also don't have to perform any null or Count() checking, because the helpers will handle that all for you. You're left with views which are clean and free of logic.
EditorTemplates also generate appropriate names when you want to POST collections to a controller action. That makes model binding to a list much, much simpler than generating those names yourself. There are times where you'd still have to do that, but this is not one of them.
Change the action method signature to
public ActionResult OptionalMarks(ICollection<Subjects> model)
Since in your HTML, it does not look like there is anything named Id in there. This isn't your main issue though.
Next, do the following with the foor loop
#for(int idx = 0; idx < Model[item].StudentEntires.Count();idx++)
{
#Html.TextBoxFor(_ => Model[item].StudentEntries[idx])
}
Possibly due to the use of a foreach loop for the StudentEntries, the model binder is having trouble piecing everything together, and thus a NULL is returned.
EDIT:
Here's an example:
Controller
public class HomeController : Controller
{
public ActionResult Index()
{
var viewModel = new IndexViewModel();
var subjects = new List<Subject>();
var subject1 = new Subject();
subject1.Name = "History";
subject1.StudentEntires.Add(new Student { Mark = 50 });
subjects.Add(subject1);
viewModel.Subjects = subjects;
return View(viewModel);
}
[HttpPost]
public ActionResult Index(IndexViewModel viewModel)
{
return new EmptyResult();
}
}
View
#model SOWorkbench.Controllers.IndexViewModel
#{
ViewBag.Title = "Home Page";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
if (Model.Subjects.Any())
{
int subjectsCount = Model.Subjects.Count();
for (int item = 0; item < subjectsCount; item++)
{
<b>#Model.Subjects[item].Name</b><br />
int studentEntriesCount = Model.Subjects[item].StudentEntires.Count();
for(int idx = 0;idx < studentEntriesCount;idx++)
{
#Html.TextBoxFor(_ => Model.Subjects[item].StudentEntires[idx].Mark);
}
}
<p style="text-align:center">
<input type="submit" class="btn btn-primary" value="Update" />
</p>
}
}
When you post the form, you should see the data come back in the viewModel object.

How can I do model binding and data display at the same time using the same model.list?

I have a code similar to this:
My View
#model MyCode.Models.RemarksModel
foreach (var row in Model.List)
{
<tr>
<td align="center"><font class="font_table_content">#row.Id</font></td>
<td align="center"><font class="font_table_content">#row.Full_Name</font></td>
<td align="center"><font class="font_table_content">#Html.TextBox("row.Remarks")</font></td>
}
My Model
public class RemarksModel
{
public IList<UserRemarks> List { get; set; }
}
My UserRemarks Object
public class UserRemarks
{
public virtual string Id { get; set; }
public virtual string Full_Name { get; set; }
public virtual string Remarks { get; set; }
}
Next in my controller, I will have some code that will load the records into a IList from the DB via Nhibernate, and then return the view with the list inside the model, something like this:
[HttpGet]
public ActionResult RemarksTest()
{
RemarksModel model = new RemarksModel();
model.List = LoadTheList();
return View(model);
}
Now, what I want to ask is, how am I able to receive the list back, i.e. get the remarks value back?
[HttpPost]
public ActionResult RemarksTest(RemarksModel model)
{
var list = model.list;
foreach (var remarks in list)
{
//do something to save the input data into code.
}
return View(model);
}
The actual code is more complex, and I've read about those IDictionary methods to receive the values. However implementing them, will cause the values not to be displayed instead, since the code no longer refers to the model.list.
Any ideas how can I display, and yet also, receive the data using the same 'list' inside my model above?
I think you need change your view as follows:
#model IEnumerable<UserRemarks>
#using(Html.BeginForm())
{
for(int i=0; i<#Model.Count; i++)
{
<table>
<tr>
<td align="center"><font class="font_table_content">#Html.TextboxFor(m => m[i].Id)</font></td>
<td align="center"><font class="font_table_content">#Html.TextboxFor(m => m[i].Full_Name</font></td>
<td align="center"><font class="font_table_content">#Html.TextboxFor(m => m[i].Remarks")</font></td>
</tr>
</table>
}
<input type="submit" value="submit"/>
}
Then the get action should be changed to:
[HttpGet]
public ActionResult RemarksTest()
{
List<UserRermarks> model = LoadTheList();
return View(model);
}
And the post action changed to:
[HttpPost]
public ActionResult RemarksTest(IEnumerable<UserRemarks> model)
{
foreach (var remarks in model)
{
//do something to save the input data into code.
}
return View(model);
}
Unfortunately I don't have visual studio on the computer I am working on so I am unable to test the above but the logic should be correct so if a problem occurs it may just be that I have typed something slightly a miss.
Hope this works for you.
In your GET controller action you could store the list in TempData:
[HttpGet]
public ActionResult RemarksTest()
{
RemarksModel model = new RemarksModel();
model.List = LoadTheList();
TempData['List'] = model.List;
return View(model);
}
Then in your POST action retrieve it with:
var myList = TempData['List']
If your app is stateless (i.e. no sessions), then you can use a cookie-based TempData provider.
The problem is you are just displaying the values.
To post values to server back, you need to have some inputs (like, textbox,checkbox,radiobutton,hiddenfields). In your case you can define hidden fields ,so that model binder will bind them to List of UserRemarks.
Follow http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx to bind a list to model.

Resources