LINQ to Entities error when trying to bind a dropdownlist - entity-framework

I am trying to create a viewmodel and add a SelectListItem to allow me to bind a dropdown list.
I have a very basic viewmodel that looks like this
public class CreatePurchaseViewModel
{
public Product Product { get; set; }
public IEnumerable<SelectListItem> Products { get; set; }
public int SelectedProductId { get; set; }
public DateTime OrderDate { get; set; }
public bool OrderSent { get; set; }
}
My controller looks like this
[HttpGet]
public ActionResult Create()
{
var model = new CreatePurchaseViewModel
{
Products = context.Products.Select(x =>
new SelectListItem()
{
Text = x.ProductName,
Value = x.ProductID
})
};
return View(model);
}
However it complains that Value = x.Product cant convert type int to string. So if I add a .ToString it compiles ok but when I try load the view I get an error
LINQ to Entities does not recognize the method 'System.String ToString()' method, and this method cannot be translated into a store expression.
My View
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>CreatePurchaseViewModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.SelectedProductId)
</div>
<div class="editor-field">
#Html.DropDownListFor(x => x.SelectedProductId,Model.Products)
#Html.ValidationMessageFor(model => model.SelectedProductId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.OrderDate)
</div>
<div class="editor-field">
#Html.TextBoxFor(model=>model.OrderDate)
#Html.ValidationMessageFor(model => model.OrderDate)
</div>
<div>
Sent
#Html.RadioButtonFor(model => model.OrderSent, "Sent", new { #checked = true })
Not Sent
#Html.RadioButtonFor(model=>model.OrderSent,"Not Sent")
Im pretty new to both entity framework and mvc so any help would be great.
Thank you

You haven't specified how does your Product model look like but we can assume that the ProductID property is integer so you might need to convert it to a string:
[HttpGet]
public ActionResult Create()
{
var model = new CreatePurchaseViewModel
{
Products = context.Products.Select(x =>
new SelectListItem
{
Text = x.ProductName,
Value = x.ProductID.ToString()
}
)
};
return View(model);
}

Related

Insert into DB from ASP.Net form

I have an issue when I want to insert an object into the database.
My model is Colis class which has a foreign key to ZoneReserve (ZoneReserveId), which has a foreign key on Reserve (ReserveId).
In my form I choose an existing ZoneReserve and Reserve, but when I post my form, new lines are created in DB, in table ZoneReserve and Reserve. Entity framework do not retrieve the existing line or I don't know...
I don't know if I'm clear enough, sorry for my english ;)
Do you have any advice ? I'm stuck et I tried everything :(
Thank you guys
Colis Model Class :
public class Colis
{
public int ColisId { get; set; }
[Display(Name = "Code barre du colis")]
public string CodeBarreColis { get; set; }
public bool IndAVendreColis { get; set; }
public virtual TypeColis TypeColis { get; set; }
[Display(Name = "Type de colis")]
public int TypeColisId { get; set; }
public ZoneReserve ZoneReserve { get; set; }
[Display(Name = "Emplacement du colis")]
public int ZoneReserveId { get; set;
}
ZoneReserve Model Class :
public class ZoneReserve
{
public int Id { get; set; }
public string NomZoneReserve { get; set; }
public Reserve Reserve { get; set; }
public int ReserveId { get; set; }
}
Reserve Model Class :
public class Reserve
{
[Display(Name = "Réserve")]
public int Id { get; set; }
public string NomReserve { get; set; }
}
My Action in ColisController :
[HttpPost]
public ActionResult CreerColis(Colis colis)
{
_context.Colis.Add(colis);
_context.SaveChanges();
return RedirectToAction("ListeColis");
}
My Form in the view :
#using (Html.BeginForm("CreerColis", "Colis"))
{
<div class="form-group">
#Html.LabelFor(m => m.Colis.CodeBarreColis)
#Html.TextBoxFor(m => m.Colis.CodeBarreColis, new { #class = "form-control" })
</div>
<div class="checkbox">
<label>
#Html.CheckBoxFor(m => m.Colis.IndAVendreColis) A vendre ?
</label>
</div>
<div class="form-group">
#Html.LabelFor(m => m.Colis.TypeColisId)
#Html.DropDownListFor(m => m.Colis.TypeColisId, new SelectList(Model.TypeColis, "Id", "NomTypeColis"), "Selectionner un type", new { #class = "form -control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserve.Reserve.Id)
#Html.DropDownListFor(m => m.Colis.ZoneReserve.Reserve.Id, new SelectList(Model.Reserve, "Id", "NomReserve"), "Selectionner une zone", new { #class = "form -control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserveId)
#Html.DropDownListFor(m => m.Colis.ZoneReserve.Id, new SelectList(Model.ZoneReserve, "Id", "NomZoneReserve"), "Selectionner une zone", new { #class = "form -control" })
</div>
<button type="submit" class="bt, btn-primary">Enregistrer</button>
}
<script src="~/Scripts/jquery-3.4.1.min.js"></script>
<script>
$(document).ready(function () {
$("#Colis_ZoneReserve_Reserve_Id").change(function () {
$.get("/ZoneReserve/ListeZoneReserveParReserve", { ReserveId: $("#Colis_ZoneReserve_Reserve_Id").val() }, function (data) {
$("#Colis_ZoneReserve_Id").empty();
$.each(data, function (index, row) {
$("#Colis_ZoneReserve_Id").append("<option value='" + row.Id + "'>" + row.NomZoneReserve+ "</option>")
});
});
})
});
</script>
It looks like your razor page is posting info about navigation properties of the Colis object to the controller and creating the full objects instead of creating a new Colis object with just the int foreign key specified.
As is, when posted, '''colis.ZoneReserve''' is not null nor is '''colis.ZoneReserve.Reserve''' reference which tells entity framework to create those related object as well when you .Add(colis) to the context.
[HttpPost]
public ActionResult CreerColis(Colis colis)
{
_context.Colis.Add(colis);
_context.SaveChanges();
return RedirectToAction("ListeColis");
}
You are POSTing unintended parameters to your controller, specifically '''Colis.ZoneReserve.Id''' and '''Colis.ZoneReserve.Reserve.Id''' as you BOUND TO in your razor page (see comments in code):
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserve.Reserve.Id)
<!-- DropDownListFor m.Colis.ZoneReserve.ReserveId will send that (navigation path) value to the server. //-->
#Html.DropDownListFor(m => m.Colis.ZoneReserve.Reserve.Id, new SelectList(Model.Reserve, "Id", "NomReserve"), "Selectionner une zone", new { #class = "form -control" })
</div>
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserveId)
<!-- DropDownListFor m.Colis.ZoneResearch.Id will send that navigation property to the server //-->
#Html.DropDownListFor(m => m.Colis.ZoneReserve.Id, new SelectList(Model.ZoneReserve, "Id", "NomZoneReserve"), "Selectionner une zone", new { #class = "form -control" })
</div>
To fix your razor page (and not send unintended values to the server)
change the first drop down list for Reserve to NOT be for anything it'll bind to on the server (you don't even need to POST it's value if you can strip it before submit), one way is to change it's name to something meaningless such as "UnnecessaryData" that won't map in the controller when posted (pseudo-code, not tested)
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserve.Reserve.Id)
#Html.DropDownList(new SelectList(Model.Reserve, "Id", "NomReserve"),
"Selectionner une zone",
new { #class = "form-control", name = "UnnecessaryData" })
</div>
Change the second drop-down-list to map to the correct property on the Colis object, notice all I did was change m => m.Colis.ZoneReserve.Id to the FK property of Colis m => m.Colis.ZoneReserveId:
<div class="form-group">
#Html.LabelFor(m => m.Colis.ZoneReserveId)
#Html.DropDownListFor(m => m.Colis.ZoneReserveId, new SelectList(Model.ZoneReserve, "Id", "NomZoneReserve"), "Selectionner une zone", new { #class = "form -control" })
</div>
When you POST the form, your Colis object in the controller should have a NULL ZoneReserve property and a non-zero ZoneReserveId property - this will prevent the other data records from being created by entity framework.
Note: You can also simply strip the data from the Colis in the POST controller - but that doesn't correct your implementation on the client razor page that's sending unintended structure to the server in the POST method.
Also note: Because you don't validate that the navigation properties of Colis are NULL in the controller, a malicious user COULD create a lot of crap data on the server by POSTing full object tree data that'll be added with the controller method as implemented.

Passing collection data from partial view to controller

I'm need to pass dynamically created data from a partial view to the controller on submit from the main form.
Here's the Action that returns the partial view:
[HttpGet]
public virtual PartialViewResult AddItem()
{
var item = new QuotedItem();
ViewBag.StockID = new SelectList(db.StockItems.OrderBy(s => s.Name), "StockID", "Name");
return PartialView("EditorTemplates/QuotedItem", item);
}
Here's the EditorTemplate for QuotedItem:
#model StockSystem.Models.QuotedItem
<div id="itemRow">
<span>
#Html.DropDownListFor(model => model.StockID, null)
#Html.ValidationMessageFor(model => model.StockID, "", new { #class = "text-danger" })
</span>
<span>
#Html.EditorFor(model => model.Price)
#Html.ValidationMessageFor(model => model.Price, "", new { #class = "text-danger" })
</span>
<span>
#Html.EditorFor(model => model.Quantity)
#Html.ValidationMessageFor(model => model.Quantity, "", new { #class = "text-danger" })
</span>
</div>
Here's the View:
#model StockSystem.Models.Quote
#{
ViewBag.Title = "Create";
}
<h2>New Quote for #ViewBag.Customer</h2>
<hr />
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
#Html.Hidden("CustomerID", (object)ViewBag.CustomerID)
<div class="addItem">
#Ajax.ActionLink(" ", "AddItem",
new AjaxOptions
{
UpdateTargetId = "editorRows",
InsertionMode = InsertionMode.InsertAfter,
HttpMethod = "GET"
})
</div>
<div id="editorRows">
#Html.EditorFor(q => q.QuotedItems)
</div>
<p></p>
<div>
<input class="add" type="submit" value="" />
<a class="back" href="#Url.Action("Index", "Quotes", new { id = ViewBag.CustomerID })"></a>
</div>
}
Here's the Create Action:
public ActionResult Create(int id)
{
var customer = db.Customers.Find(id);
ViewBag.CustomerID = id;
ViewBag.Customer = customer.CustomerName;
var quote = new Quote() { QuotedItems = new List<QuotedItem>() };
return View(quote);
}
Here's the EF model for QuotedItem:
public partial class QuotedItem
{
public int QuotedItemID { get; set; }
public int QuoteID { get; set; }
public int StockID { get; set; }
public int Quantity { get; set; }
public decimal Price { get; set; }
public virtual Quote Quote { get; set; }
public virtual StockItem StockItem { get; set; }
}
And Quote:
public partial class Quote
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Quote()
{
this.QuotedItems = new HashSet<QuotedItem>();
}
public int QuoteID { get; set; }
public int CustomerID { get; set; }
public int UserID { get; set; }
public System.DateTime QuoteDate { get; set; }
public string QuoteRef { get; set; }
public virtual Customer Customer { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<QuotedItem> QuotedItems { get; set; }
public virtual User User { get; set; }
}
I can add items to the page but they are not added to the quote on submit.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "QuoteID,CustomerID,UserID,QuoteDate,QuoteRef,QuotedItems")] Quote quote) //, List<QuotedItem> items
{
if (ModelState.IsValid)
{
//quote.QuotedItems is empty, how do I bind this data and save to database?
db.Quotes.Add(quote);
db.SaveChanges();
return RedirectToAction("Index", new { id = quote.CustomerID });
}
return View(quote);
}
The collection is not being sent to the controller. How is this done?
Thanks
Edit: I was thinking about trying this:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "QuoteID,CustomerID,UserID,QuoteDate,QuoteRef,QuotedItems")] Quote quote, List<QuotedItem> items)
{
if (ModelState.IsValid)
{
quote.QuotedItems = items;
db.Quotes.Add(quote);
db.SaveChanges();
return RedirectToAction("Index", new { id = quote.CustomerID });
}
return View(quote);
}
But I thought there must be a better way. If I were try this approach I'm still not entirely sure how to send this to the controller, I assume as a parameter in Html.BeginForm()? I'm fairly new to MVC and still getting to grips with the conventions.
By using the Bind attribute in your method signature, you are preventing the QuotedItems property from being posted to the controller action.
public ActionResult Create([Bind(Include = "QuoteID,CustomerID,UserID,QuoteDate,QuoteRef")] Quote quote)
Adding the QuotedItems property name to the include list should solve this:
public ActionResult Create([Bind(Include = "QuoteID,CustomerID,UserID,QuoteDate,QuoteRef,QuotedItems")] Quote quote)
EDIT
Without seeing your QuotedItem editor template, it's hard to say for sure, but it sounds like your issue might be that each item added to your list doesn't have a unique id set on the elements that make up that item. Without this, the model binder will struggle to work out which element belongs to which object in the collection.
Take a look at these two articles and see if they help:
http://haacked.com/archive/2008/10/23/model-binding-to-a-list.aspx/
https://www.mattlunn.me.uk/blog/2014/08/how-to-dynamically-via-ajax-add-new-items-to-a-bound-list-model-in-asp-mvc-net/
Edit 2
Each element in your template should be given a unique ID, an index is a good way to do this, so are item GUIDs (if you have them); but as long as each element ID is unique to that item it should work.
#model StockSystem.Models.QuotedItem
<div id="itemRow">
<span>
#Html.DropDownListFor(model => model.StockID, null)
#Html.ValidationMessageFor(model => model.StockID, "", new { #class = "text-danger" })
</span>
<span>
#Html.EditorFor(model => model.Price, new { htmlAttributes = new { id = $"[{YOURINDEX}].Price" } })
#Html.ValidationMessageFor(model => model.Price, "", new { #class = "text-danger" })
</span>
<span>
#Html.EditorFor(model => model.Quantity, new { htmlAttributes = new { id = $"[{YOURINDEX}].Quantity" } })
#Html.ValidationMessageFor(model => model.Quantity, "", new { #class = "text-danger" })
</span>
In the code above the [YOURINDEX] value would need to be incremented for each new item added to the collection.
How you implement and increment that index is up to you, but I would suggest reading the two articles I linked to previously to get an understanding of why this is necessary.

MVC 5 identity how to add to custom user child tables?

I am using a Visual Studio MVC 5 identity user template and am trying to expand the user information by creating a child table with information about Company.
There is a lot to read about how to create this parent/child tables with great examples so that's not the problem. My question is how do I add/remove/change the child tables in a smart and easy way by using the foreign key relationship?
I'm sorry I don't have any code to show right now, but I have used a MVC 5 template and added a virtual ICollection<Company> companies to the applicationUser model and it works great. I just can't figure out how to add the custom data to the child table....
Edit-------------
ApplicationUser model:(Here i use a userData table instead of the Company table i mention in the text)
// You can add profile data for the user by adding more properties to your ApplicationUser class, please visit http://go.microsoft.com/fwlink/?LinkID=317594 to learn more.
public class ApplicationUser : IdentityUser
{
//Create new custom tables
//User information
public virtual ICollection<Table_UserData> UserDatas { get; set; }
public virtual Table_UserData UserData { get; set; }
public async Task<ClaimsIdentity> GenerateUserIdentityAsync(UserManager<ApplicationUser> manager)
{
// Note the authenticationType must match the one defined in CookieAuthenticationOptions.AuthenticationType
var userIdentity = await manager.CreateIdentityAsync(this, DefaultAuthenticationTypes.ApplicationCookie);
// Add custom user claims here
//userIdentity.AddClaim(new Claim("myCustomClaim", "value of claim"));
return userIdentity;
}
}
My table:(Here i use a userData table instead of the Company table i mention in the text)
public class Table_UserData
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
Controller(Here i use a userData table instead of the Company table i mention in the text):
public async Task<PartialViewResult> Register(RegisterUserViewModel model)
{
if (ModelState.IsValid)
{
var user = new ApplicationUser { UserName = model.UserName, Email = model.Email};
var result = await UserManager.CreateAsync(user, model.Password);
var userName = await UserManager.FindByNameAsync(model.UserName);
ApplicationUser userModel = UserManager.FindById(userName.Id);
userModel.UserDatas.Add(new Table_UserData { FirstName = model.FirstName, LastName = model.LastName });
await UserManager.UpdateAsync(userModel);
if (result.Succeeded)
{
ModelState.Clear();
return PartialView(model);
}
AddErrors(result);
}
//Something failed, redisplay form
return PartialView(model);
}
Use a ViewModel.
The following is untested, and just off the top of my head, but something similar should work.
ViewModel:
public class ViewModel
{
public Parent Parent { get; set; }
public Child Child { get; set; }
}
Controller:
public ActionResult Create()
{
return View();
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(
[Bind(Prefix = "Parent", Include = "ID,Field1,Field2")]Parent parent,
[Bind(Prefix = "Child", Include = "ID,ParentID,Field1,Field2")]Child child,
)
{
if(ModelState.IsValid)
{
db.Parents.Add(parent);
db.Childen.Add(child);
db.SaveChanges();
}
}
View:
#using ViewModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
<div>
#* Parent Properties *#
#Html.LabelFor(model => model.Parent.Field1)
#Html.EditorFor(model => model.Parent.Field1)
#Html.LabelFor(model => model.Parent.Field2)
#Html.EditorFor(model => model.Parent.Field2)
#* Child Properties *#
#Html.HiddenFor(model => model.Child.ParentID)
#Html.LabelFor(model => model.Child.Field1)
#Html.EditorFor(model => model.Child.Field1)
#Html.LabelFor(model => model.Child.Field2)
#Html.EditorFor(model => model.Child.Field2)
</div>
<div>
<input type="submit" value="Create" />
</div>
}
Ok, this answer comes late but it might help others who are learning MVC+EF to solve this issue. My code is tested and it is only a documented effort to demonstrate the solution given by Luke works fine.
For this example I am using MS Sql Server 2012, Visual Studio 2013 Professional edition, MVC 5.
To start, these are my example tables:
CREATE TABLE person(
personId int IDENTITY(1,1) NOT NULL,
personName varchar(15) NOT NULL,
personLastName varchar(15) NOT NULL,
personPhone varchar(10) NULL,
CONSTRAINT PK_person
PRIMARY KEY CLUSTERED (personId ASC)
);
CREATE TABLE pet(
petId int IDENTITY(1,1) NOT NULL,
personId int NOT NULL,
petName varchar(20) NOT NULL,
petType varchar(20) NOT NULL,
CONSTRAINT PK_pet PRIMARY KEY CLUSTERED ([petId] ASC, [personId] ASC),
CONSTRAINT FK_pet_person
FOREIGN KEY(personId)
REFERENCES person (personId)
);
It is a one-to-many relationship using automatically generated key columns (identity). The pet table has a foreign key referencing the person table primary key.
In order to add data to the child table (pet table in this example) I will use a ViewModel class, which will act as model in the View.
Here are the models:
namespace WebApp.Models
{
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
// The [Table()] attribute is used to map this class to a database table.
// The table name must be written exactly as it is in the database.
[Table("person")]
public partial class person
{
// This attribute was added manually. The scaffolded class did not include it.
// It MUST be indicated that the key column is identity.
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int personId { get; set; }
[Required(ErrorMessage = "First name is required.")]
[StringLength(15)]
[Display(Name="First name")]
public string personName { get; set; }
[Required(ErrorMessage = "Last name is required.")]
[StringLength(15)]
[Display(Name="Last name")]
public string personLastName { get; set; }
[StringLength(10)]
[Display(Name="Phone number")]
public string personPhone { get; set; }
public virtual ICollection<pet> pets { get; set; }
}
}
namespace WebApp.Models
{
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("pet")]
public partial class pet
{
[Key, Column(Order = 0)]
// Same thing here: DatabaseGeneratedOption was originally "none"
// It was manually changed to "Identity"
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int petId { get; set; }
// This is the foreign key column
[Key, Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int personId { get; set; }
[Required(ErrorMessage = "Pet name is required.")]
[StringLength(20)]
[Display(Name="Pet name")]
public string petName { get; set; }
[Required(ErrorMessage = "Pet type is required.")]
[StringLength(20)]
[Display(Name="Pet type")]
public string petType { get; set; }
public virtual person person { get; set; }
}
}
This is the ViewModel:
using WebApp.Models;
namespace WebApp.ViewModels
{
public class personPetVM
{
public person persons { get; set; }
public pet pets { get; set; }
}
}
It is simply a class containing two properties, where each property type is a class we defined in the models.
For these classes to communicate with the database we have this context:
namespace WebApp.Models
{
using System.Data.Entity;
public partial class petContext : DbContext
{
// DBConn is defined in the Web.Config file
public petContext()
: base("name=DBConn") {
}
public virtual DbSet<person> people { get; set; }
public virtual DbSet<pet> pets { get; set; }
}
}
This is the controller:
using System.Web.Mvc;
using WebApp.Models;
using WebApp.ViewModels;
namespace WebApp.Controllers
{
public class personController : Controller
{
// Create an instance of the context class to get connected to the Database
petContext db = new petContext();
public ActionResult Index() {
// Create an instance of the ViewModel class
// and pass it to the View as its model.
personPetVM person = new personPetVM();
return View(person);
}
[HttpPost]
[ValidateAntiForgeryToken]
// Because the ViewModel contains the definition of two different classes
// it is imperative to differentiate the properties belonging to each class
// by using the [Bind(Prefix="")] attribute in the action method parameters.
// As you can see, the parameters are instances of the model classes and are
// automatically populated with the values posted on the form.
public ActionResult saveData([Bind(Prefix = "persons")] person Person,
[Bind(Prefix = "pets")] pet Pet) {
try {
// ModelState is a dictionary that contains the state of the model
// and its validation rules.
if (ModelState.IsValid) {
db.people.Add(Person);
db.pets.Add(Pet);
db.SaveChanges();
}
return RedirectToAction("Index");
}
catch {
return View();
}
}
}
}
Because our ViewModel class does not have a key column defined, it is not possible for Visual Studio to generate the View automatically for you, so you will have to write it manually:
#model WebApp.ViewModels.personPetVM
#{
ViewBag.Title = "Index";
}
<h2>Pets registry</h2>
#using (Html.BeginForm("saveData", "person", FormMethod.Post)) {
#Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Person</h4>
<hr />
#Html.ValidationSummary(true, "", new { #class = "text-danger" })
<div class="form-group">
#Html.LabelFor(model => model.persons.personName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.persons.personName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.persons.personName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.persons.personLastName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.persons.personLastName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.persons.personLastName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.persons.personPhone, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.persons.personPhone, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.persons.personPhone, "", new { #class = "text-danger" })
</div>
</div>
<h4>Pet</h4>
<hr />
<div class="form-group">
#Html.LabelFor(model => model.pets.petName, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.pets.petName, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.pets.petName, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
#Html.LabelFor(model => model.pets.petType, htmlAttributes: new { #class = "control-label col-md-2" })
<div class="col-md-10">
#Html.EditorFor(model => model.pets.petType, new { htmlAttributes = new { #class = "form-control" } })
#Html.ValidationMessageFor(model => model.pets.petType, "", new { #class = "text-danger" })
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Create" class="btn btn-default" />
</div>
</div>
</div>
}
Please be aware that a prefix is added to all the form generated inputs (id and name). In this particular example, all the inputs have their id composed by the name of the Model name + underscore + input name. For example: "persons_personName".
The input name is generated as Model name + dot + input name. For example: "persons.personName"
The prefix is the way Razor Views assigns values to the database tables participating in the form.
Take this into account if you want to refer to any input id/name using javascript/jQuery, especially if you add a database table field to the form (select, input, etc) by yourself. Make sure to add the respective prefix, as indicated, if you plan to post its value to the controller. If not, the form will not update the database because some input value did not pass the validation rules specified in the model.
So that's it!
I hope this example may help someone wondering how to:
Map model classes to database tables.
Define key columns in the model classes.
Create ViewModels.
Bind values to an action method.
Insert data into single tables.
Insert data into 1-to-many relationships.
Use some common Entity Framework [attributes]
Validate forms in a View.

ASP.NET MVC2: Update of a model for further modifications in a POST handler.

Model:
public class Model
{
public ItemType Type { get; set; }
public int Value { get; set; }
}
public enum ItemType { Type1, Type2 }
Controller:
public ActionResult Edit()
{
return View(new Model());
}
[HttpPost]
public ActionResult Edit(Model model, bool typeChanged = false)
{
if (typeChanged)
{
model.Value = 0; // I need to update model here and pass for further editing
return View(model);
}
return RedirectToAction("Index");
}
And of course View:
<div class="editor-label"><%: Html.LabelFor(model => model.Type) %></div>
<div class="editor-field">
<%: Html.DropDownListFor(
model => model.Type,
Enum.GetNames(typeof(MvcApplication1.Models.ItemType))
.Select(x => new SelectListItem { Text = x, Value = x }),
new { #onchange = "$(\'#typeChanged\').val(true); this.form.submit();" }
)
%>
<%: Html.Hidden("typeChanged") %>
</div>
<div class="editor-label"><%: Html.LabelFor(model => model.Value) %></div>
<div class="editor-field"><%: Html.TextBoxFor(model => model.Value) %></div>
<input type="submit" value="Create" onclick="$('#typeChanged').val(false); this.form.submit();" />
The code in controller (with the comment) doesn't work as I expect. How could I achieve the needed behavior?
As I wrote here multiple times, that's how HTML helpers work and it is by design: when generating the input they will first look at the POSTed value and only after that use the value from the model. This basically means that changes made to the model in the controller action will be completely ignored.
A possible workaround is to remove the value from the modelstate:
if (typeChanged)
{
ModelState.Remove("Value");
model.Value = 0; // I need to update model here and pass for futher editing
return View(model);
}

Data is empty in Post method asp.net mvc 2

I`m trying to display classes that have properties of type some custom class inside it.
Model:
public class ComplexParent
{
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
public class SimpleChild
{
public int Id { get; set; }
public string ChildName { get; set; }
public string ChildDescription { get; set; }
}
Controller:
public ActionResult Testify(int id)
{
ComplexParent par = new ComplexParent();
par.First = new SimpleChild() { Id = id };
par.Second = new SimpleChild()
{
Id = id + 1,
ChildName = "Bob",
ChildDescription = "Second"
};
return View("Testify", par);
}
[HttpPost]
public ActionResult Testify(ComplexParent pComplexParent)
{
return View("Testify", pComplexParent);
}
View:
<% using (Html.BeginForm())
{%>
<fieldset>
<legend>Fields</legend>
<%: Html.EditorFor(x => x.First) %>
<br />
<%: Html.EditorFor(x => x.Second.ChildName)%>
<br/>
<br/>
<br/>
<% Html.RenderPartial("SimpleChild", Model.First); %>
<p>
<input type="submit" value="Watch me :-)" />
</p>
</fieldset>
<% } %>
When it comes to Get it works just fine, I can see all the data. But on post pComplexParent parameter is empty (both properties of a complex class are nulls). Probably Im missing something here, but could not get this to work ...
Small addition: view part that only shows editor for name makes Second child not null and name is set to Bob. But I dont understand how to make it just with EditorFor or DisplayFor methods.
UPDATE: Thanks to Darin Dimitrov, who kindly went throught all my code and found what caused this problem. The exact problem was that if you are using display template, asp.net mvc 2 doesnt post any values back and if whole template is has nothing to post back object is null. I still thinking of the way how to get the data, even if you don`t want to edit it. But using editor template does the thing and I have all objects filled with proper data now.
Your view is a bit of a mess. You are using editor templates along with partials for the first child. It is not very clear what fields are included inside the form. I would recommend you using only editor templates:
Model:
public class ComplexParent
{
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
public class SimpleChild
{
public int Id { get; set; }
public string ChildName { get; set; }
public string ChildDescription { get; set; }
}
Controller:
[HandleError]
public class HomeController : Controller
{
public ActionResult Testify(int id)
{
var par = new ComplexParent();
par.First = new SimpleChild() { Id = id };
par.Second = new SimpleChild()
{
Id = id + 1,
ChildName = "Bob",
ChildDescription = "Second"
};
return View(par);
}
[HttpPost]
public ActionResult Testify(ComplexParent pComplexParent)
{
return View(pComplexParent);
}
}
View:
<% using (Html.BeginForm()) { %>
<%: Html.EditorFor(x => x.First) %>
<%: Html.EditorFor(x => x.Second) %>
<input type="submit" value="Watch me :-)" />
<% } %>
Editor template for SimpleChild (~/Views/Home/EditorTemplates/SimpleChild.ascx):
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<SomeNs.Models.SimpleChild>" %>
<%: Html.HiddenFor(x => x.Id) %>
<%: Html.EditorFor(x => x.ChildName) %>
<%: Html.EditorFor(x => x.ChildDescription) %>
Now if you want to have different editor templates for the two child properties you could either specify the editor template name when including it:
<%: Html.EditorFor(x => x.First, "FirstChildEditor") %>
which corresponds to ~/Views/Home/EditorTemplates/FirstChildEditor.ascx or use an [UIHint] attribute at your model:
public class ComplexParent
{
[UIHint("FirstChildEditor")]
public SimpleChild First { get; set; }
public SimpleChild Second { get; set; }
}
My recommendation is not to use Html.RenderPartial for generating input fields because their names will be hardcoded and won't bind properly depending on your objects hierarchy.