ASP.NET MVC - ViewModel nested loop - c#

I am new to ASP.NET MVC and trying to list some companies, then all contacts under that each company. I think I am getting close to make it work, so please help if you can.
The model for table and field name:
namespace ERP.Models
{
[Table("ERP_Company")]
public class ERP_Company
{
[Key]
public int CompanyID { get; set; }
public string Name { get; set; }
}
[Table("ERP_CompanyContact")]
public class ERP_Contact
{
[Key]
public int ContactID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int CompanyID { get; set; }
}
}
These are the methods getting the Company and Contact list:
namespace ERP.Models
{
public class Method1
{
public ERPEntities db = new ERPEntities();
public List<ERP_Company> getCompanyList()
{
List<ERP_Company> companyList = (
from c in db.ERP_Company
where c.Name.Contains("Network")
select c).Take(10).ToList();
return companyList;
}
// This below method needs to get the passing CompanyID from getCompanyList for filtering.
public List<ERP_Contact> getContactList()
{
List<ERP_Contact> contactList = (
from cc in db.ERP_CompanyContact
select cc).Take(50).ToList();
return contactList;
}
/* Tried this below, but not work for the Controller, maybe I am doing wrong.
public List<ERP_Contact> getContactList(int CompanyID)
{
List<ERP_Contact> contactList = (
from cc in db.ERP_CompanyContact
where cc.CompanyID == CompanyID
select cc).Take(50).ToList();
return contactList;
}
*/
}
}
Use ViewModel (suggested from other post), combine both models:
namespace ERP.Models
{
public class ViewModelDemoVM
{
public List<ERP_Company> allCompanies { get; set; }
public List<ERP_Contact> allContacts { get; set; }
}
}
This code in the Controller:
Method1 _repository = new Method1();
public ActionResult ViewModelDemo()
{
ViewModelDemoVM vm = new ViewModelDemoVM();
vm.allCompanies = _repository.getCompanyList();
vm.allContacts = _repository.getContactList();
return View(vm);
}
Lastly, the view code:
#model ERP.Models.ViewModelDemoVM
#{
ViewBag.Title = "ViewModelDemo";
}
<h2>ViewModelDemo</h2>
<ul>
#foreach (var company in Model.allCompanies)
{
<li>#company.CompanyID | #company.Name</li>
<ul>
<!-- HERE is I want to put the filtering... foreach contact WHERE CompanyID = Model.allCompanies.CompanyID-->
#foreach (var contact in Model.allContacts)
{
<li>#contact.ContactID | #contact.FirstName</li>
}
</ul>
}
</ul>
How can I filter the contacts (2nd loop) based on the #company.CompanyID? Sample code would be appreciated.
Thanks in advance.

you can apply where clause in second loop. try below code. i hope this helps
#model ERP.Models.ViewModelDemoVM
#{
ViewBag.Title = "ViewModelDemo";
}
<h2>ViewModelDemo</h2>
<ul>
#foreach (var company in Model.allCompanies)
{
<li>#company.CompanyID | #company.Name</li>
<ul>
<!-- HERE is I want to put the filtering... foreach contact WHERE CompanyID = Model.allCompanies.CompanyID-->
#foreach (var contact in Model.allContacts.Where(x=>x.CompanyId ==company.CompanyID)
{
<li>#contact.ContactID | #contact.FirstName</li>
}
</ul>
}
</ul>

You should create a view model with nested structure and use that. Remember, view models are specific to the view. So build it as your view needs it.
public class CompanyVm
{
public string Name { set; get; }
public IEnumerable<ContactVm> Contacts { set;get;}
}
public class ContactVm
{
public string Name { set; get; }
}
public class ViewModelDemoVM
{
public List<CompanyVm> Companies { set; get; }
}
Your Contact table/entity already has a foriegn key/navigational property to Company entity/table. So all you have to do is, get the companies and it's corresponding customers,map it to our view model and use it in the view.
Add a collection type to Company entity class to access it's contacts.
public class ERP_Company
{
[Key]
public int CompanyID { get; set; }
public string Name { get; set; }
public ICollection<Contact> Contacts { set; get; }
}
Now in your action method, you can get the data
public IActionResult ViewModelDemo()
{
var vm = new ViewModelDemoVM();
vm.Companies = db.Companies
.Select(a => new CompanyVm { Name = a.Name,
Contacts = a.Contacts
.Select(c => new ContactVm
{ Name = c.Name })}
).ToList();
return View(vm);
}
Now in your view, just loop through the company and for each company, loop through it's contacts
#model ViewModelDemoVM
#foreach(var company in Model.Companies)
{
<h3>#company.Name</h3>
<h5>Contacts</h5>
#foreach(var contact in company.Contacts)
{
<p>#contact.Name</p>
}
}
Some notes
Create view models as needed by view
Do not mix entity classes (used by ORM) with view models
Keep less logic/C# code in views
I used class generic class names (Contact instead ERP_Contact) and property names. When you use the above code, make the needed changes to use your existing names if needed.

Related

Paging on View with MVC Paged List

I wanna implement MVC paging so on the Index Action its working.
public ActionResult Index(int? page)
{
using (NorthwindEntities db = new NorthwindEntities())
{
CustomersViewModel model = new CustomersViewModel();
//model.Customers = db.Customers.OrderBy(m => m.CustomerID).OrderByDescending(m=>m.CustomerID).Take(10).ToList();
model.Customers = db.Customers.OrderBy(m => m.CustomerID).OrderByDescending(m=>m.CustomerID).Take(10).ToList().ToPagedList(page ?? 1,5);
model.SelectedCustomer = null;
var list = new List<int>();
for (int i = 1; i <= 20; i++)
{
list.Add(i);
}
SelectList selectedList = new SelectList(list);
ViewBag.DdList = selectedList;
//model.Countries = db.Countries.ToList();
model.CountryList = new SelectList(BLDDLCountry.GetCountry(), "CountryId", "CountryName");
model.DisplayMode = "WriteOnly";
return View(model);
}
}
Now on the View
#Html.PagedListPager(Model, page => Url.Action("Index", new {page, pagesize = 5 }))
Is accepted only if i decorate my View Model with IPagedList
#model PagedList.IPagedList<SingleCRUD.Models.CustomersViewModel>
Now as I am using
public IEnumerable<Customer> Customers { get; set; }
On My ViewModdel
The View is not accepting the Customers
#{
foreach (var item in Model.Customers)
{
if (Model.SelectedCustomer != null)
{
if (item.CustomerID ==
Model.SelectedCustomer.CustomerID)
{
#:<tr class="SelectedCustomer">
}
else
{
#:<tr>
}
}
else
{
#:<tr>
}
<td>#item.CustomerID</td>
<td>#item.CompanyName</td>
#*<td><input type="submit"
formaction="/home/select/#item.CustomerID"
value="Select" /></td>*#
<td><input type="submit"
formaction="/home/Edit/#item.CustomerID"
value="Edit" /></td>
<td></td>
#:</tr>
}
}
And Go to definition has stopped on Customers after changing the name space.
My View Model
public class CustomersViewModel
{
public int CustomerID { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
public string ContactTitle { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string Region { get; set; }
public Nullable<int> PostalCode { get; set; }
public string Country { get; set; }
public Nullable<int> Phone { get; set; }
public Nullable<int> Fax { get; set; }
public IEnumerable<Customer> Customers { get; set; }
public Customer SelectedCustomer { get; set; }
public string DisplayMode { get; set; }
public List<Country> Countries { get; set; }
public SelectList CountryList { get; set; }
}
So I am facing issue at the view level how do I correctly fix it.
Tried these changes
Model
public PagedList<Customer> Customers { get; set; }
View
#model SingleCRUD.Models.CustomersViewModel
#using PagedList;
#using PagedList.Mvc;
#Html.PagedListPager(Model.Customers, page => Url.Action("Index", new { page, pagesize = 5 }))
Action
model.Customers = (PagedList<Customer>)db.Customers.OrderBy(m => m.CustomerID).ToPagedList(page ?? 1, 5);
Had to explicitly convert it to Paged List as there was a conversion error not sure whether its correct.
Run Time error on View.
'System.Web.Mvc.HtmlHelper' does not contain a definition for 'PagedListPager' and the best extension method overload 'PagedList.Mvc.HtmlHelper.PagedListPager(System.Web.Mvc.HtmlHelper, PagedList.IPagedList, System.Func)' has some invalid arguments
Error
Error 1 Cannot implicitly convert type 'PagedList.IPagedList' to 'PagedList.PagedList'. An explicit conversion exists (are you missing a cast?)
Using
#Html.PagedListPager(Model.Customers, page => Url.Action("Index", new { page, pagesize = 5 }))
on View tried writing this in the form tag as well as out side the form tag.
Its a bit unclear what you claiming. #model PagedList.IPagedList<CustomersViewModel> will not work since your model is CustomersViewModel but it will work if your use #model CustomersViewModel.
If you wanting to display a paged list of Customer, then your model property needs to be
public IPagedList<Customer> Customers { get; set; }
and in the view use
#Html.PagedListPager(Model.Customers, page => Url.Action("Index", new {page, pagesize = 5 }))

"Object reference not set to an instance of an object." Nesting Lists ASP.NET MVC LINQ Entity Framework

As the title says, im getting that error when trying to get the value out of an user object to be displayed. Im using Entity Framework Code-First, so all the Models are generated, im trying to combine multiple tables with eachother. "Displaying all users with their project data and flextime" (Now flextime is just a decimal)
Model:
public class ListModel
{
public List<StaffModel> StaffModelList { get; set; }
}
public class StaffModel
{
public FlexModel Flex { get; set; }
public List<string> ProjectName { get; set; }
}
public class FlexModel
{
public User User { get; set; }
public decimal FlexTime { get; set; }
}
View:
#model Aviato.ViewModel.ListModel
<table class="table">
#foreach (var item in Model.StaffModelList)
{
<tr>
<td>#item.Flex.User.UserId</td>
<td>#item.Flex.FlexTime</td>
<td>#item.ProjectName</td>
<td>#item.Flex.User.SocialSecurityNumber</td>
<td>#item.Flex.User.FirstName</td>
<td>#item.Flex.User.LastName</td>
<td>#item.Flex.User.Address1</td>
<td>#item.Flex.User.ZipCode</td>
<td>#item.Flex.User.City</td>
<td>#item.Flex.User.PhoneNumber1</td>
<td>#item.Flex.User.EmploymentStartDate</td>
<td>#item.Flex.User.Password</td>
#foreach (var project in item.ProjectName)
{
<td>#project</td>
}
#Html.ActionLink("Redigera", "Edit", new { id = item.Flex.User.UserId })
#Html.ActionLink("Ta bort", "Delete", new { id = item.Flex.User.UserId })
</tr>
}
</table>
Controller:
private readonly AviatoModel _db = new AviatoModel(); //Database
public ActionResult Index()
{
var projects = _db.Projects.ToList();
var users = _db.Users.ToList();
var model = new ListModel();
model.StaffModelList = new List<StaffModel>();
foreach (var u in users)
{
var flexModel = new StaffModel();
flexModel.Flex.User = u; //This is where the Error occurs.
flexModel.Flex.FlexTime = GetFlex.Flex(u.UserId);
model.StaffModelList.Add(flexModel);
}
return View(model);
}
Please help.
The Flex property of StaffModel is never instantiated so when you try to access a method on it, you get the NullReferenceException. Add a constructor to your class like this to create it as an empty object:
public class StaffModel
{
public StaffModel()
{
Flex = new FlexModel();
}
//snip
}

Insert into two tables with no relationships

I am learning LINQ and was wondering how can I INSERT into two different tables with no relationship on one click using LINQ. Is it even possible?
I am adding into one table like this. How can I add into the second table as well?
Second Table,
GenreId
Name
Description
Code,
[HttpPost]
public ActionResult Create(Artist artist)
{
if (ModelState.IsValid)
{
_db.Artists.Add(artist);
_db.Genres.Add(new Genre { GenreId = 1, Name = "Some Genre", Description = "Trying to add second table" }); // how can i add the genre object here
_db.SaveChanges();
return RedirectToAction("Index", new { id = artist.ArtistId });
}
return View(artist);
}
Note that my View is strongly typed to Artist class.
My View,
#model EntityFrameWorkDBFirst.Models.Artist
#Html.TextBoxFor(model => model.Name)
#Html.TextBoxFor(model =>model.Genres) // how to get this working
My Model Class,
public partial class Artist
{
public Artist()
{
this.Albums = new HashSet<Album>();
this.Genres = new HashSet<Genre>();
}
public int ArtistId { get; set; }
public string Name { get; set; }
public virtual ICollection<Album> Albums { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
}
Tuple related suggestion didn't work. Maybe I am missing something.
#model Tuple<EntityFrameWorkDBFirst.Models.Artist, EntityFrameWorkDBFirst.Models.Genre>
<legend>Create a Artist</legend>
#Html.TextBoxFor(model => model.Item1.Name)
<h2>Genre</h2>
#Html.TextBoxFor(model => model.Item2.Name)
#Html.TextBoxFor(model => model.Item2.Name)
<p>
<input type="submit" value="Create" />
</p>
Controller Code:
[HttpPost]
public ActionResult Create(Artist artist, Genre genre)
{
if (ModelState.IsValid)
{
_db.Artists.Add(artist);
_db.Genres.Add(genre);
_db.SaveChanges();
return RedirectToAction("Index", new { id = artist.ArtistId });
}
return View(artist);
}
You mixed domain model and view model.
It's big mistake, you should work only with viewmodel on view.
You should create view model:
public class CreateArtistViewModel
{
public string ArtistName { get; set; }
public int? Genres { get; set; } // if you provide chooser for user
public string GenresName { get; set; } // if user can enter new genre
public string GenresDecription { get; set; } // if user can enter new genre
public IList<Genre> Genres { get; set; }
}
in the post action you should check view model and create artist and genre if user create new gener.

Showing related entities on MVC 4 partial view using EF

I am working on asp.net MVC 4 application. I have a view model like this:
public class MainViewModel
{
public List<EmailAccount> EmailAccounts { get; set; }
public List<UserContact> Contacts { get; set; }
public List<LinkedInProfile> LinkedInProfiles { get; set; }
public IConfig Config { get; set; }
}
Contacts and LinkedInProfiles have many to many relationship so I have defined junction table:
public class LinkedInAccountConnection
{
[Key]
[Column(Order = 0)]
public Guid LinkedInAccountId { get; set; }
[Key]
[Column(Order = 1)]
public string LinkedInProfileId { get; set; }
}
In view I am using this:
#foreach (var c in Model.Collection.Contacts.OrderByDescending(c => c.LastUpdated).Take(500))
{
#Html.Action("ContactListWidget", "Account", new { contact = c })
}
EF code
var user = dataRepository.GetUserByUsername(username);
Contacts = dataRepository.GetContactsAll(user.Item.UserID).Where(c => c.UserContactEmailAddresses.All(e=> !Cleansing.IsAutomatedEmailAddress(e.EmailAddress))).ToList();
foreach (var c in Contacts)
{
var userContactToLinkedInProfiles = c.UserContactToLinkedInProfiles;
foreach (var uc in userContactToLinkedInProfiles)
{
var profile = uc.LinkedInProfile;
LinkedInProfiles.Add(profile);
}
}
I want to show information in ContactListWidget partial view related to contact as well as related LinkedInProfile. What changes do I need to make to Viewmodel and view ?
Please suggest.
You just need to fill the ViewModel and then bind it to your view.
Controller
[HttpGet]
public ActionResult GetDetails()
{
var mainViewModel = new MainViewModel();
mainViewModel.EmailAccounts=Repository.GetEmailAccounts();
mainViewModel.Contacts=Repository.GetUserAccounts();
mainViewModel.LinkedInProfiles=Repository.GetLinkedInProfiles();
mainViewModel.Config=Repository.GetConfigData();
return PartialView("ContactListWidget", mainViewModel );
}
View
#model YourProject.ViewModels.MainViewModel //set your viewmodel here
After that you can use that ViewModel's list data for bind to your other html elements.
Performance Alert: #foreach (var c in Model.Collection.Contacts.OrderByDescending(c => c.LastUpdated).Take(500))
Please don't bring all the data into view and then filter inside the
view.Just bring the filtered data only (i.e. filter inside the
repository).It'll help to improve the performance of the view.

Child foreach not looping as intended in Partial using ViewModel

I have a List of Recipes and each Recipe has a number of RecipeLines, I want to loop Each Recipe with it's corresponding Recipe Lines foreach Looping inside of the parent Recipe foreach.
Recipe Class
namespace XXX.Models
{
public class Recipe
{
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public string RecipeInstructions { get; set; }
public virtual List<RecipeLine> RecipeLines { get; set; }
}
}
RecipeLine Class
namespace XXX.Models
{
public class RecipeLine
{
public int RecipeLineID { get; set; }
public float Quantity { get; set; }
public int MeasurementID { get; set; }
public int RecipeID { get; set; }
public int IngredientID { get; set; }
public virtual Measurement Measurement { get; set; }
public virtual Recipe Recipe { get; set; }
public virtual Ingredient Ingredient { get; set; }
}
}
No need to list the Ingredient and Measurement Class, but they are structured fine.
Now let's look at the ViewModel
namespace XXX.ViewModels
{
public class RecipeLineViewModel
{
public IEnumerable<Recipe> Recipes { get; set; }
public IEnumerable<RecipeLine> RecipeLines { get; set; }
}
}
And the PartialsController
namespace XXX.Controllers
{
public class PartialsController : Controller
{
private XXXDb db = new XXXDb();
public ActionResult RecipeList()
{
RecipeLineViewModel viewModel;
viewModel = new RecipeLineViewModel();
viewModel.Recipes = db.Recipes.ToList();
viewModel.RecipeLines = db.RecipeLines.Include(r =>
r.Measurement).Include(r => r.Ingredient);
return PartialView("_RecipeList", viewModel);
}
}
}
Now the partial view is Views > Partials > __RecipeList.cshtml
RecipeList Partial View
#model XXX.ViewModels.RecipeLineViewModel
#foreach (Recipe recipe in Model.Recipes)
{
<div>#recipe.RecipeName</div>
<div>#recipe.RecipeInstructions</div>
foreach (RecipeLine recipeLines in Model.RecipeLines)
{
<div class="row">
<div class="large-12 columns">
#recipeLines.Quantity #recipeLines.Measurement.MeasurementEn
#recipeLines.Ingredient.IngredientNameEn
</div>
</div>
}
}
}
(I printed the RecipeID at the end of each recipeLine to show that each recipe is repeating out Recipe Lines for RecipeID = 1 and not their Lines related to their own RecipeID. I'm a newbie to this stuff, I'm assuming that somewhere I should be telling the Recipe Lines about the RecipeID they should be looping for????
Here is the result I'm getting:
In your Recipie model you have have a List<RecipieLine> property which one would assume is to maintain one or more RecipieLine model objects. In your RecipieLineViewModel you have two separate IEnumerable collections exposing your Recipie and RecipieLine objects.
In your actual view, you are iterating over your Recipie collection:
#foreach (Recipe recipe in Model.Recipes)
Looks good, however, the following line is your issue:
foreach (RecipeLine recipeLines in Model.RecipeLines)
This enumerates over the RecipieLine model objects stored in your RecipieLineViewModel RecipieLine collection, however, it currently has no way of knowing which Recipie you are currently enumerating in your outer foreach.
You either want to provide a where LINQ clause of some sort using the data available to you from the current Recipie object enumeration, or alter the inner foreach to use the current Recipie enumeration rather than the Model.
Update
For completeness, here is an example of how to use the current Recipe object enumeration to access its associated RecipieLines:
#forech (var recipie in Model.Recipies)
{
<div>#recipe.RecipeName</div>
<div>#recipe.RecipeInstructions</div>
foreach (var recipieLine in recipie.RecipieLines)
{
<div class="row">
<div class="large-12 columns">
// Do something with recipieLine here
</div>
</div>
}
}
The above would be my preferred method as it is more efficient that performing a LINQ query each enumeration, and removes the IEnumerable<RecipieLine> from your ViewModel which is effectively duplicate code.
Add a where Linq clause to foreach.
#foreach (Recipe recipe in Model.Recipes)
{
<div>#recipe.RecipeName</div>
<div>#recipe.RecipeInstructions</div>
foreach (RecipeLine recipeLines in Model.RecipeLines.Where(rl => rl.RecipeID == recipe.RecipeID))
{
Upd:
Also you can definently include RecipeLine stuff in Recipes and you won't need RecipeLine Collection at all! Just loop through recipe.RecipeLines
Something like this:
viewModel.Recipes = db.Recipes.Include("RecipeLine").Include("RecipeLine.Measurement").
.Include("RecipeLine.Ingredient");

Categories