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.
Related
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.
I am getting the hang of Automapper in an ASP.NET MVC 5 application for the purpose of mapping a domain model to a ViewModel. There is a case that I still don't know how to resolve: when the ViewModel (destination) has a property not in the domain model (source).
The two additional properties in the ViewModel are IEnumerables that I need to populate in the Controller.
As I explain in the comments in the Controller block (shown below), the domain model is the source and will be fed into the View table. The additional two IEnumerables in the ViewModel will fill the DropDownLists in the HTML.BeginForm() block.
The examples I have seen using .CreateMap<>().ForMember() deal with calculations or transformations of properties in the source model, and not this case, where I am defining something in the controller based on the Action parameters.
My question is how to map the remaining IEnumerables, as defined in the controller?
Mapping Config in App_Start
public static class MappingConfig
{
public static void RegisterMaps()
{
AutoMapper.Mapper.Initialize(config =>
{
config.CreateMap<StudentRoster, StudentRosterViewModel>();
});
}
}
Model and ViewModel:
[Table("StudentRoster")]
public partial class StudentRoster
{
public int ID { get; set; }
[Required]
[StringLength(3)]
public string Campus { get; set; }
[Required]
[StringLength(4)]
public string FiscalYear { get; set; }
[Required]
[StringLength(50)]
public string StudentName { get; set; }
public int StudentID { get; set; }
}
// ViewModel
public partial class StudentRosterViewModel
{
// Automapper successfully mappped the first five fields
// to the parent class
public int ID { get; set; }
public string Campus { get; set; }
public string FiscalYear { get; set; }
public string StudentName { get; set; }
public int StudentID { get; set; }
// These two fields are not in the parent class
public IEnumerable<SelectListItem> CampusListSelect { get; set; }
public IEnumerable<SelectListItem> FiscalYearSelect { get; set; }
}
Index Action in Controller:
// GET: StudentRosterViewModels
public ActionResult Index(string campus = "MRA", string fy="FY16")
{
IEnumerable<StudentRoster> query = db.StudentRosters.Where(m=>m.Campus==campus).ToList();
// This successfully maps the domain model to the viewmodel
// This IEnumerable will display in the "Table"
IEnumerable<StudentRosterViewModel> mappedQuery =
AutoMapper.Mapper.Map<IEnumerable<StudentRoster>, IEnumerable<StudentRosterViewModel>>(query);
// The two remaining IEnumerables need to be mapped to 'mappedQuery'
// CampusListSelect and FiscalYearSelect
// These two IEnumerables will populate the dropdownlists in Html.BeginForm()
IEnumerable<SelectListItem> CampusList = new SelectList(new List<string> { "CRA", "DRA", "MRA", "PRA" }, campus);
IEnumerable<SelectListItem> FiscalYearList = new SelectList(new List<string> { "FY12", "FY13", "FY14", "FY15", "FY16" }, fy);
return View(mappedQuery.ToList());
}
You can try to use DynamicMap to populate your items without creating additional classes for mapping.
In case if you're using the old version of AutoMapper (4.1 or below) the you can try something the following:
// GET: StudentRosterViewModels
public ActionResult Index(string campus = "MRA", string fy="FY16")
{
IEnumerable<StudentRoster> query = db.StudentRosters.Where(m=>m.Campus==campus).ToList();
// This successfully maps the domain model to the viewmodel
// This IEnumerable will display in the "Table"
IEnumerable<StudentRosterViewModel> mappedQuery =
AutoMapper.Mapper.Map<IEnumerable<StudentRoster>, IEnumerable<StudentRosterViewModel>>(query);
// The two remaining IEnumerables need to be mapped to 'mappedQuery'
// CampusListSelect and FiscalYearSelect
// These two IEnumerables will populate the dropdownlists in Html.BeginForm()
IEnumerable<SelectListItem> CampusList = new SelectList(new List<string> { "CRA", "DRA", "MRA", "PRA" }, campus);
IEnumerable<SelectListItem> FiscalYearList = new SelectList(new List<string> { "FY12", "FY13", "FY14", "FY15", "FY16" }, fy);
var objForDynamicMapping = new
{
CampusListSelect = CampusList,
FiscalYearListSelect = FiscalYearList
};
foreach(var mappedItem in mappedQuery)
{
// will create the mapping configuration dynamically
AutoMapper.Mapper.DynamicMap(objForDynamicMapping, mappedItem);
}
return View(mappedQuery.ToList());
}
In case if you're using the AutoMapper 4.2 or high.
Then you just need to put this row:
var config = new MapperConfiguration(cfg => cfg.CreateMissingTypeMaps = true);
in place where you create the mapper configuration and then just use method Map like:
mapper.Map(objForDynamicMapping, mappedItem);
instead of DynamicMap.
Hope it will help.
I am getting started with MVC and I have the following Model
public class FormControls
{
//Properties of the FormControls object
public string formCName { get; set; }
public string formCType { get; set; }
public string formCCss { get; set; }
public string formCEnabled { get; set; }
public string formCDefaultVal { get; set; }
}
I also created the following control and I am querying a database using linq to select records. Each record will then have to be added to a list.
public ActionResult Index()
{
var DataContext = new EditProfileFormFieldsDataContext();
var controls = from c in DataContext.Controls
select c;
List<FormControls> Fields = new List<FormControls>();
foreach(var fc in controls)
{
//Create Object for Generic List
FormControls epc = new FormControls();
epc.formCName = fc.Control_Name;
epc.formCType = fc.Control_Type;
epc.formCCss = fc.Control_CSS;
epc.formCEnabled = fc.Control_Enabled;
epc.formCDefaultVal = fc.Control_DefaultVal;
//Add Object to FormControls Generic List
Fields.Add(epc);
}
return View("EditProfile");
}
My question is how would I access the list using RAZOR in the view? I am trying to loop through the list I created in the view. I am fairly new to MVC and I think I am over thinking a lot of this :) Thanks!
You can make the model of your view a List. Put this at the top of your view:
#model List<FormControls>
Change the return of your Index() method:
return View("EditProfile", Fields);
Then you can access it from the view by using #Model. For example, to iterate through it:
#foreach (var field in Model)
{
<p>#field.formCName</p>
}
Btw, there is a more direct way to implement the controller.
public ActionResult Index()
{
var DataContext = new EditProfileFormFieldsDataContext();
return View("EditProfile", DataContext.Controls.ToList());
}
or if you rename the view to "index.cshtml", you can do it like this:
public ActionResult Index()
{
var DataContext = new EditProfileFormFieldsDataContext();
return View(DataContext.Controls.ToList());
}
Suppose you do not have the index.cshtml yet, right click the "View(" and select "Add View", in the pop-up wizard, select "list view" and FormControls, there will be an auto-generated view with #model defined and a well done table demoing how to use it.
I have two models as below
public class Category
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; },
[Required]
public string category { get; set; }
[Required]
public string Desc { get; set; }
}
public class Product
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; },
public int CatID { get; set; },
[ForeignKey("CatID")]
public virtual Category Category { get; set; },
[Required]
public string Desc { get; set; },
public string DisplayName
{
get
{
return string.format("{0} - {1}",this.Category.category,this.Desc);
}
}
}
This is my Edit Action
public ActionResult Edit(int id)
{
ViewBag.PossibleCategories = categoryRepository.All;
return View(productRepository.Find(id));
}
[HttpPost]
public ActionResult Edit(Product product)
{
if (ModelState.IsValid) //<== This becomes false saying category.desc is required
{
productRepository.InsertOrUpdate(product);
productRepository.Save();
return RedirectToAction("Index");
}
else
{
ViewBag.PossibleCategories = categoryRepository.All;
return View();
}
}
I have a scaffolded a Edit view of product and it shows ID and DisplayName as Readonly. All the other fields a editable.
The edit view also has the product -> category -> category has a read-only text field
#Html.TextBoxFor(model => model.Category.category, new Dictionary<string, object>() { { "readonly", "true" } })
The Post back sends this and tries to create a new category. This is not required. The category link will be carried forward using the product.CatID.
How can i display these types of fields??
When the Edit view Post back the Model state appears as invalid because the product's category's desc is null (product -> category -> desc).
if i comment out the DisplayName property in Product this issue doesn't occur.
From my understanding, this is because the DiaplayName property refers to Category property and the view view doesn't have category.desc field so when the model is created back on the POST action, the desc is not populated. Adding the category.desc field to the view is one way of solving this problem.
Is there any other method to solve this?
Note: This is not the only model i'm having this issue. There are many complex models which have the same problem and to me having these fields also included in the view would make for (1) a very cluttered view (2) the amount of data making the round trip will be high.
Simple Solution
Check for null. Really you should be making this a habit anyway.
public string DisplayName
{
get
{
if(this.Category != null)
{
return string.format("{0} - {1}",this.Category.category,this.Desc);
}
else
{
return String.Empty;
}
}
}
Complex Solution
Instead of directly using your database model in your Views another solution is to create ViewModels. These are models meant specifically for your View. As a simplified example, let's take your Product model and create a ViewModel.
Create a folder for your ViewModels
Create ViewModel files that match your Controller
Create a ViewModel that you will use in your View
Say you have a Store Controller. This would be the file structure you would create.
Models
ViewModels
StoreViewModels.cs
Inside the StoreViewModels you would create a ViewModel called ProductViewModel which you would fill in with information from Product.
public class ProductViewModel
{
public int ID { get; set; }
public string Description { get; set; }
public string DisplayName { get; set; }
public ProductViewModel() { }
public ProductViewModel(Product product)
{
this.ID = product.ID;
this.Description = product.Description;
this.DisplayName = product.DisplayName;
}
}
In your View you reference ProductViewModel instead of Product. On the receiving end you then translate the ViewModel fields back to your Model. Let me know if you have any questions.
I have a model like this:
public class ArticleWriter_ViewModel
{
public int MagId { get; set; }
public string MagNo { get; set; }
public string TitleIds { get; set; }
public MultiSelectList Articles { get; set; }
public int[] SelectedArticles { get; set; }
}
i fill the Articles like this:
ArticleWriter_ViewModel viewModel = new ArticleWriter_ViewModel();
Func<IQueryable<NumberTitle>, IOrderedQueryable<NumberTitle>> orderByFunc = null;
Expression<Func<NumberTitle, bool>> filterExpr = null;
if (id > 0)
{
filterExpr = p => p.MagazineId.Equals(id);
}
var wholeTitles = unitOfWork.NumberTitleRepository.Get(filterExpr, orderByFunc, "Magazine,Title").ToList();
then pass it to view. in a few views i show Article in DropDownListFor, but in others want to show it in DisplayFor.how can i iterate through the Articles to show in DisplayFor?
Create a display template in your project's Views\DisplayTemplates directory (create the folder if necessary) called ArticleWriter_ViewModel.cshtml like this (Razor syntax):
#model ArticleWriter_ViewModel
#foreach (NumberTitle article in Model)
{
#Html.DisplayFor(article => article.Title)
}
You can change the property referenced in the DisplayFor expression and/or add an expression to filter the list of articles as required.
If you want to give the display template a different name, use the UIHint annotation on the model name to assign the template name:
[UIHint("MyTemplate")]
public class ArticleWriter_ViewModel
{...}