I'm new to MVC and I use MVC4 and I'm new to C#. I want to retrieve data from two tables: tblProduct and tblCategory in one View. In that View I want to get from tblCategory the column "Name" and from tblProduct all the columns.
I've defined my tables in code first in class tables.cs:
public class tblCategory
{
//Primary Key
[Key]
[ScaffoldColumn(false)]
public int CategoryId { get; set; }
[MaxLength(160)]
public string Name { get; set; }
etc...
}
public class tblProduct {
//Primary Key
[Key]
[ScaffoldColumn(false)]
public int ProductId { get; set; }
//Foreign Key
public int CategoryId { get; set; }
[ForeignKey("CategoryId")]
public virtual tblCategory tblCategorys { get; set; }
[MaxLength(500)]
public string MainImageFileName { get; set; }
[MaxLength(160)]
public string Name { get; set; }
ect...
}
My model class, bar.cs:
Namespace xx.Models
public class bar {
public tblProduct product { get; set; }
public tblCategory category { get; set; }
}
How do I define the Index class in the Controller right? So that I can send data from model bar into the View.
public ActionResult Index(){
//How to define this?
}
And how should build up the View?
#model xx.Models.bar
But I want to use a Foreach loop in my View for all the columns from tblProduct.
And one column from tblCategory.
Can somebody help me with this? Thanks!
Your Index action should build an instance of your bar class
public ActionResult Index(){
var b = new bar();
b.product = some loading code;
b.category= some loading code;
return View(b);
}
your Index.cshtml need to expect an instance of that same model
#model ProjectName.xx.Models.bar
<div class="editor-label">
#Html.LabelFor(model => model.category.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.category.Name)
#Html.ValidationMessageFor(model => model.category.Name)
</div>
#* just duplicate this once for each property you want from product *#
<div class="editor-label">
#Html.LabelFor(model => model.product.Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.product.Name)
#Html.ValidationMessageFor(model => model.product.Name)
</div>
i wouldn't use a for loop on the properties in product, because then you need reflection, and i doubt that page will function very fast.
I think both of this classes should be model cause clearly they are representation of tables in database. However class Bar i think it's not. I think your structure should be organized like this: product(model class), category(model class) = bar(model collection class). So instead of adding both classes as properties of class Bar you should define dictionary that will contain those class but tie those class with common interface i.e. IViewModel.
public interface IViewModel{
string name {get;set;}
}
public class TblProduct: IVievModel{
/// your code
}
public class TblCategory: IVievModel{
/// your code
}
public class Bar{
private Dictionary<string, IViewModel> viewModels = new Dictionary<string,IViewModel>();
}
Now in your ActionResult method you will just add those two classes to dictionary and return this class Bar to your view.
Related
I'm struggling with dropdownlist tried several methods online and all failed will show the methods that I tried.
Objective
Create a Reciept with date,reference... & country. Country is Required and should be a dropdownlist.
So the Table for Reciept("ID, Name, Date, Address, City, CountryList).
RecieptModel
public class TransactionModel
{
public int ID { get; set; }
public int Name{ get; set; }
public DateTime Date { get; set; }
public string Address { get; set;}
public string City { get; set; }
public CountryList CountryList { get; set; }
}
public class ApplicationDbContext : DbContext
{
public DbSet<RecieptModel> Reciepts { get; set;}
public DbSet<CountryList> coutryList { get; set; }
}
CountryList
public class CountryList
{
public byte Id { get; set; }
public enum Country
{
Germany,
US,
UK
}
}
Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "ID,Name,Date,City,CountryList")] Reciepts reciepts)
{
if (ModelState.IsValid)
{
db.Reciepts.Add(reciepts);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(reciepts);
}
View
<div class="form-group">
#Html.LabelFor(model => model.CountryList, new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EnumDropDownListFor(model => model.CountryList)
#Html.ValidationMessageFor(model => model.CountryList)
</div>
</div>
This failed I looked for several examples I'm trying to do it without the use of javascript. In the End I just want to learn how to implement a Dropdownlist & save it to the database allot of the methods that I tried to implement failed in MVC5.
I would sugest you to add a html extension method that implement dropdownlist for a generic enumarator. Take a look at this answer.
There is slightly change in your view where you bind the Country
this is actually you write
#Html.EnumDropDownListFor(model => model.CountryList)
Change is need to add name property in your country list (it must be ID that is your taken in your model) , so it must be like this to maintain country list value
#Html.DropDownListFor(model => model.CountryList.Id, new SelectList(model => model.CountryList)
Becouse every HTML control need unique indentifier
I want to add a family which has many members and can have many houses. I am trying to make a form and submit the object family with collection of other objects. I have tried few things but I can only get one object to pass to controller and not the collection. What can i do?
should i make member and house partial views and render them in the view ??
Is there any way of doing this with collecting everything in JavaScript and then passing a whole family object to the controller?
I am using MVC 5 with Entity Framework. I am not able to solve this problem. Any help is appreciated.
here is an example of objects
public class family
{
public int id { get; set; }
public int familyname { get; set; }
public List<member> members { get; set; }
public List<house> houses { get; set; }
}
public class member
{
public int id { get; set; }
public string name { get; set; }
public DateTime birthdate { get; set; }
//foreign key
public family Family { get; set; }
public int FamilyID { get; set; }
}
public class house
{
public int id { get; set; }
public int number { get; set; }
public string address { get; set; }
//foreign key
public family Family { get; set; }
public int FamilyID { get; set; }
}
public class FamilyViewModel
{
public family Family { get; set; }
public member Member { get; set; }
public house House { get; set;}
public List<member> Members { get; set; } //??
public List<house> Houses { get; set; } //??
}
View
#model WebApp.ViewModels.FamilyViewModel
#{
ViewBag.Title = "New";
Layout = "~/Views/Shared/_Layout.cshtml";
}
<h2>Family</h2>
#using (Html.BeginForm("Submit", "Family"))
{
<div class="form-group">
#Html.LabelFor(m => m.Family.familyname)
#Html.TextBoxFor(m => m.Family.familyname, new { #class = "form-control"})
</div>
<div id="member">
</div>
<div id="house">
</div>
}
Controller
[HttpPost]
public ActionResult Submit(FamilyViewModel CompleteFamily)
{
//What to do here?
return View();
}
First of all please change the datatype of familyname to string, inside family class. And FamilyViewModel will be same as your family class.
Currently I'm working on a MVC project that have this type of functionality. See, in this case, first you need to save data for family. While saving, members and houses will be null.
For example, for the first time you are saving data, then suppose here family id is 1, familyname is John, members and houses will be null.
Hope you understood till this.
You already render two partial view for members and houses. Provide two buttons in the main view(that is nothing but your family view). 1 is for Add Members and another 1 is for Add Houses. So when user click on Add Members show one popup modal or anything you want, where user can submit family members. Similarly for houses.
Then while saving family members (I mean when they click on SAVE button in members popup modal), just call a jquery/ajax function and post your data to controller method including the current family Id.
See my bellow code,
//This will be inside a popup modal for members,
<div class="row with-forms">
<div class="col-md-12">
<h5>Member Name</h5>
<input class="search-field" type="text" name="name" id="memberName" />
</div>
<div class="col-md-12">
<h5>Birth Date</h5>
<input class="search-field" type="text" name="DOB" id="memberDOB" />
</div>
<div class="col-md-12">
<input class="btn btn-success" type="button" id="saveMembers" />
</div>
</div>
//My jquery/ajax code to save members data
$("#saveMembers").click(function () {
var membersData = [{
name: $("#memberName").val(),
birthdate: $("#memberDOB").val()
})
var CompleteFamily = {
id: $("#hiddenFamilyId").val(), //keep your familyId in a
//hidden field in same page
members: membersData,
//houses: houseData //similarly u can add house data here
}
$.ajax({
"url": "/yourControllerName/FamilyViewModel",
"type": "Post",
"data": CompleteFamily,
success: function (data) {
//show a message that member added to this family
}
})
Thats it. Then you can save your data in Action method. Like this you can post houses data for the same familyId in same ajax method.
In your controller Action method you can validate like this,
public ActionResult Submit(FamilyViewModel CompleteFamily)
{
if(FamilyViewModel.id == 0)
// This is a new entry to family.
// Return your new familyId in a viewBag and keep that in a hidden field
//in your view HTML
else
{
//You just need to update the family. I mean, here you are adding
//members and houses to the respective family Id.
}
}
See, dont forget to return your familyId in a viewBag or what ever you want, and keep that in a hidden field in HTML. So that only you can add members/houses to that respective Id. Like the way I'm passing data in the above ajax.
For example, your familyId in HTML like bellow,
<input type="hidden" id="hiddenFamilyId" value="#ViewBag.familyId"/>
Hope it solve your problem. Cheers...
I have two autogenerated database models (Product and ProductDetails) which I merged into a ViewModel so I can edit all data at once.
What confuses me is the part where I am supposed to iterate through ICollection of Product_ProductCategoryAttributes (within ProductDetail model) inside a view to allow .NET automagically bind properties to the ViewModel. I have tried using for as well as foreach loop but without any success as controls are being created with wrong names (needed for auto binding).
Product model
public partial class Product
{
public Product()
{
this.ProductDetail = new HashSet<ProductDetail>();
}
public int idProduct { get; set; }
public int idProductCategory { get; set; }
public string EAN { get; set; }
public string UID { get; set; }
public bool Active { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
public virtual ICollection<ProductDetail> ProductDetail { get; set; }
}
ProductDetail model
public partial class ProductDetail
{
public ProductDetail()
{
this.Product_ProductCategoryAttribute = new HashSet<Product_ProductCategoryAttribute>();
}
public int idProductDetail { get; set; }
public int idProductCategory { get; set; }
public int idMeta { get; set; }
public int idProduct { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual Meta Meta { get; set; }
public virtual Product Product { get; set; }
public virtual ICollection<Product_ProductCategoryAttribute> Product_ProductCategoryAttribute { get; set; }
public virtual ProductCategory ProductCategory { get; set; }
}
ProductViewModel - One product can have many ProductDetails
public class ProductViewModel
{
public Product Product { get; set; }
public List<ProductDetail> ProductDetails { get; set; }
}
View (some code is intentionally omitted)
#for (int i = 0; i < Model.ProductDetails.Count(); i++)
{
#Html.TextAreaFor(model => model.ProductDetails[i].Description, new { #class = "form-control", #rows = "3" })
#for (int j = 0; j < Model.ProductDetails[i].Product_ProductCategoryAttribute.Count(); j++)
{
#Html.HiddenFor(model => model.ProductDetails[i].Product_ProductCategoryAttribute.ElementAt(j).idProductCategoryAttribute)
#Html.TextBoxFor(model => model.ProductDetails[i].Product_ProductCategoryAttribute.ElementAt(j).Value, new { #class = "form-control" })
}
}
All controls outside the second for loop are being named properly eg. ProductDetails[0].Description, however controls generated within the second for loop get their name by the property value which in this case are Value and idProductCategoryAttribute. If I'm not wrong one solution would be converting ICollection to IList, but having model autogenerated I don't think it would be the best option.
You can't use ElementAt() within the lambda within the HTML helpers. The name that will be generated will just be the name of the field without indexes which allows the posted values to be populated.
You should use the indexes to traverse all the way through your view model so that the names that are generated actually match up.
So this:
#Html.HiddenFor(model => model.ProductDetails[i].Product_ProductCategoryAttribute.ElementAt(j).idProductCategoryAttribute)
Should be this, or similar:
#Html.HiddenFor(model => model.ProductDetails[i].Product_ProductCategoryAttribute[j].idProductCategoryAttribute)
As for changing your model from ICollection to IList, this will be fine as IList inherits from ICollection. But as you say it is auto generated, it would probably be ok if you were using code first entity framework or something like that.
The real solution is to map your incoming model (the view model) to the auto generated ICollection<> lists and back again, depending on whether you're posting or getting.
In the example below, we are taking the posted values and mapping them to the auto generated Product object and manipulating it.
///
/// ProductViewModel incoming model contains IList<> fields, and could be used as the view model for your page
///
[HttpPost]
public ActionResult Index(ProductViewModel requestModel)
{
// Create instance of the auto generated model (with ICollections)
var product = new Product();
// Map your incoming model to your auto generated model
foreach (var productDetailViewModel in requestModel)
{
product.ProductDetail.Add(new ProductDetail()
{
Product_ProductCategoryAttribute = productDetailViewModel.Product_ProductCategoryAttribute;
// Map other fields here
}
}
// Do something with your product
this.MyService.SaveProducts(product);
// Posted values will be retained and passed to view
// Or map the values back to your valid view model with `List<>` fields
// Or pass back the requestModel back to the view
return View();
}
ProductViewModel.cs
public class ProductViewModel
{
// This shouldn't be here, only fields that you need from Product should be here and mapped within your controller action
//public Product Product { get; set; }
// This should be a view model, used for the view only and not used as a database model too!
public List<ProductDetailViewModel> ProductDetails { get; set; }
}
If your model is ICollection<T> (and can't be changed to IList<T> or use in a for loop), then you need to use a custom EditorTemplate for typeof T
In /Views/Shared/EditorTemplates/Product_ProductCategoryAttribute.cshtml
#model yourAssembly.Product_ProductCategoryAttribute
#Html.HiddenFor(m => m.idProductCategoryAttribute)
#Html.TextBoxFor(m => m.Value, new { #class = "form-control" })
In /Views/Shared/EditorTemplates/ProductDetail.cshtml
#model yourAssembly.ProductDetail
#Html.TextAreaFor(m => m.Description, new { #class = "form-control", #rows = "3" })
#Html.EditorFor(m => m.Product_ProductCategoryAttribute)
In the main view
#model yourAssembly.ProductViewModel
#using (Html.BeginForm())
{
...
#Html.EditorFor(m => m.ProductDetails)
...
The EditorFor() method will recognize a collection (IEnumerable<T>) and will render each item in the collection using the corresponding EditorTemplate including adding the indexers in the controls name attributes so that the collection an be bound when you post.
The other advantage of a custom EditorTemplate for complex types is that they can be reused in other views. You can also create multiple EditorTemplate's for a type by locating them in the view folder associated with a controller, for example /Views/YourControllerName/EditorTemplates/ProductDetail.cshtml
Side note. In any case, you should be using view models for each type that includes only those properties you want to edit/display in the view.
Which I'd expect to be implemented in .NET MVC, but trying to figure out how to actually do it. Currently on my ViewModel, I have (for example):
public class GroupPolicyViewModel
{
public int PolicyId { get; set; }
public int HistoryId{ get; set; }
public SelectList ProductList { get; set; } // tried this
public List<Product> ProductList1 { get; set; } // tried this
}
Whenever I try and auto-generate my View from this ViewModel, the ProductList gets ignored. Is there any way to auto-generate a DropDownList at all from the ViewModel?
With model
public class GroupPolicyViewModel
{
public int PolicyId { get; set; }
public int HistoryId{ get; set; }
public int SelectedProductId{ get; set; }
public List<Product> ProductList { get; set; }
}
You can create DropDownList
#Html.DropDownListFor(m => m.SelectedProductId,
new SelectList(Model.ProductList, "ProductId", "ProductName"))
Or if you have SelectList of products in your model
#Html.DropDownListFor(m => m.SelectedProductId, Model.ProductSelectList)
If you want some generated code, you need to use scaffolding option with providing data context class. Here is nice tutorial MVC Music Store
You can (from VS2010) when creating a new Controller and using Entity Framework. Specify in the wizard to include Entity Framework and Read/Write ops and the wizard will create both the controller and the views.
It'll generate code like this [there is more] in the controller:
public ActionResult Create()
{
ViewBag.CostCentre_ID = new SelectList(db.CostCentres, "ID", "Name");
ViewBag.Location_ID = new SelectList(db.Locations, "ID", "Name");
ViewBag.User_ID = new SelectList(db.UCMUsers, "User_ID", "EmployeeNo");
return View();
}
and this in the view:
<div class="editor-field">
#Html.DropDownList("User_ID", String.Empty)
#Html.ValidationMessageFor(model => model.User_ID)
</div>
Hi I'm struggling to find the correct approach on SO for what I am currently doing, so I thought I would ask.
Here is my simplified code:
The entities are nested types based on using them with EF CodeFirst and the ViewModel is being mapped with AutoMapper.
When posting the form the ModelState is not valid due to the dropdownlist being mapped to model.CourseId and displaying my Course data.. i.e. CourseId = 2, CourseList = Null, but also having the [Required] attribute, really only CourseId is required but I also needed a relevant error message.
I then thought that in my Create GET & POST actions the view should probably just have the CourseId but I still need to display it as a dropdown and populate it and I was unsure as how to do that correctly.
I may also not be understanding how this should be used correctly and if I even need CourseName, i.e. since the Course already exists in the database I just want a foreign key to it, which will still let me show the selected course.
I'm also planning to break out all this mapping and data setting in my controller actions into a separate service layer but at the moment its a small prototype.
// Entities
public class Recipe {
public int Id { get; set; }
public string Name { get; set; }
public Course Course { get; set; }
}
public class Course {
public int Id { get; set; }
public string Name { get; set; }
}
// View Model
public class RecipeCreateViewModel {
// Recipe properties
public int Id { get; set; }
public string Name { get; set; }
// Course properties, as primitives via AutoMapper
public int CourseId { get; set; }
public string CourseName { get; set; }
// For a drop down list of courses
[Required(ErrorMessage = "Please select a Course.")]
public SelectList CourseList { get; set; }
}
// Part of my View
#model EatRateShare.WebUI.ViewModels.RecipeCreateViewModel
...
<div class="editor-label">
Course
</div>
<div class="editor-field">
#* The first param for DropDownListFor will make sure the relevant property is selected *#
#Html.DropDownListFor(model => model.CourseId, Model.CourseList, "Choose...")
#Html.ValidationMessageFor(model => model.CourseId)
</div>
...
// Controller actions
public ActionResult Create() {
// map the Recipe to its View Model
var recipeCreateViewModel = Mapper.Map<Recipe, RecipeCreateViewModel>(new Recipe());
recipeCreateViewModel.CourseList = new SelectList(courseRepository.All, "Id", "Name");
return View(recipeCreateViewModel);
}
[HttpPost]
public ActionResult Create(RecipeCreateViewModel recipe) {
if (ModelState.IsValid) {
var recipeEntity = Mapper.Map<RecipeCreateViewModel, Recipe>(recipe);
recipeRepository.InsertOrUpdate(recipeEntity);
recipeRepository.Save();
return RedirectToAction("Index");
} else {
recipe.CourseList = new SelectList(courseRepository.All, "Id", "Name");
return View(recipe);
}
}
I fixed my particular problem just by doing the below.
[Required(ErrorMessage = "Please select a Course.")]
public int CourseId { get; set; }
// public string CourseName { get; set; }
public SelectList CourseList { get; set; }
The view will use the DropDownListFor helper to map the drop down to my CourseId and that's all I really needed.
On to another problem now with AutoMapper and why it is not mapping back to the Recipe entity in the POST Create action.
I probably first need to find a way to store the relevant Course name in the "CourseName" property.