On my MVC4 application I have a page where an user can be placed in a schedule. The idea is that you can select a person and that they will be entered on the schedule. The Schedule Model looks as follows:
public class Schedule {
[Key]
public int scheduleId { get; set; }
[Column(TypeName = "Date")]
[DataType(DataType.Date)]
public DateTime date { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
}
The User Model where the Schedule Model refers to looks like this:
public class User {
public int UserId { get; set; }
[Display(Name = "Naam")]
public string Name { get; set; }
public string Email { get; set; }
[Display(Name = "Beschikbaar")]
public bool isAvailable { get; set; }
}
An user can be placed on the scheduling page with the following controller:
public ActionResult Create(Schedule schedule)
{
if (ModelState.IsValid)
{
db.Schedules.Add(schedule);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(schedule);
}
The Schedule/Create view looks like this:
and has the following code:
<h2>Schedule a person</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Schedule</legend>
<div class="editor-label">
#Html.LabelFor(model => model.UserId)
</div>
<div class="editor-field">
#Html.DropDownList("UserId", String.Empty)
#Html.ValidationMessageFor(model => model.UserId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.date)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.date)
#Html.ValidationMessageFor(model => model.date)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.User.isAvailable)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.User.isAvailable)
#Html.ValidationMessageFor(model => model.User.isAvailable)
</div>
<p>
<input type="submit" value="Plan" />
</p>
</fieldset>
}
This code does place the user on the schedule on the specified date. But somehow an extra "null" user is created in the database every time an existing user is added to the schedule. See this image:
I have been breaking my head over this unwanted behaviour for the past hours. The Create action of the ScheduleController does not specify to create an extra user. I have no idea why it does do so anyway.
Does anyone here have any idea?
It's because of your .User.IsAvailabe property. This creates a new user when adding the schedule.
You'll have to attach the existing user to your schedule. Perhaps you can even get rid of UserId and use User.Id in your form.
Related
This question already has answers here:
ASP.NET MVC 4 - for loop posts model collection properties but foreach does not
(2 answers)
Closed 8 years ago.
I am new in ASP.MVC4 and I am facing with problem to pass object from view to controller:
Let me explain my problem from beginning:
My class which is used by is for example: UserAndRolesModel
public class UserAndRolesModel
{
public user User { get; set; }
public IEnumerable<UserAndRoles> AsignedRoles { get; set; }
}
As You can see class UserAndRolesModel persists of 2 objects: user and Enumerable
public class user
{
[HiddenInput(DisplayValue=false)]
public virtual int user_id { get; set; }
[Required(ErrorMessage = "Please provide name")]
[Display(Name = "Name")]
public virtual string user_name { get; set; }
[Display(Name = "Is active?")]
public virtual bool user_active { get; set; }
}
public class UserAndRoles
{
public string RoleName { get; set; }
public bool IsAssigned { get; set; }
}
My controller action is simple, it assign user to UserWithRoles.User and creates tempList with UserAndRoles objects.
public ActionResult Edit(int userid=0)
{
//object which will be passed to View
UserAndRolesModel UserWithRoles = new UserAndRolesModel();
//user
UserWithRoles.User = repoUsers.Users.FirstOrDefault(x => x.user_id == userid);
//roles
IList<UserAndRoles> tempList = new List<UserAndRoles>();
UserAndRoles temp1 = new UserAndRoles();
temp1.RoleName="Admin";
temp1.IsAssigned=true;
UserAndRoles temp2 = new UserAndRoles();
temp2.RoleName="User";
temp2.IsAssigned=false;
tempList.Add(temp1);
tempList.Add(temp2);
//assign tempList to model
UserWithRoles.AsignedRoles = tempList;
return View(UserWithRoles);
)
At this stage I am successfully passing to View:
UserWithRoles.User.user_id=1;
UserWithRoles.User.user_name="UserName1";
UserWithRoles.User.user_active=true;
UserWithRoles.AsignedRoles[1].RoleName = "Admin";
UserWithRoles.AsignedRoles[1].IsAssigned = true ;
UserWithRoles.AsignedRoles[2].RoleName = "User";
UserWithRoles.AsignedRoles[2].IsAssigned = false;
I am able to display above View properly:
#model Models.UserAndRolesModel
#using (Html.BeginForm())
{
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-field">
#Html.EditorFor(model => model.User.user_id)
#Html.ValidationMessageFor(model => model.User.user_id)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.User.user_name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.User.user_name)
#Html.ValidationMessageFor(model => model.User.user_name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.User.user_active)
</div>
<div class="editor-field">
#Html.CheckBoxFor(model => model.User.user_active)
#Html.ValidationMessageFor(model => model.User.user_active)
</div>
<br /> ROLES below is piece of code which makes me cry<br />
#foreach (var item in Model.AsignedRoles)
{
<div class="editor-field">
<div class="editor-field">
#Html.LabelFor(model => item.RoleName)
#Html.CheckBoxFor(model => item.IsAssigned)
</div>
</div>
}
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
When i Click submit all data regarding user was passed properly but IEnumerable AsignedRoles is always null.
Here is my controller method on post:
[HttpPost]
public ActionResult Edit(UserAndRolesModel UserWithRoles)
{
if (ModelState.IsValid)
{
if (UserWithRoles.AsignedRoles==null)
Console.WriteLine("I am still crying");
else
Console.WriteLine("Got it!");
}
return View(UserWithRoles);
}
In View I tried to use other loops for example:
#for (int i = 0; i < Model.AsignedRoles.Count(); i++)
{
<div class="editor-field">
#Html.LabelFor(model => item[i].IsAssigned)
#Html.CheckBoxFor(model => item[i].IsAssigned)
</div>
}
But above also does not pass IEnumerable.
Can anyone help me to resolve this issue? How can I pass back to controller UserAndRolesModelwhich contains IEnumerable?
I will be very grateful. Advance thanks for the help!
You do need the for loop, but the one you tried you have referenced item[i], yet item[i] no longer exists. Try this, note that I have also added a HiddenFor for RoleName otherwise that won't get passed back:
#for (int i = 0; i < Model.AsignedRoles.Count(); i++)
{
<div class="editor-field">
#Html.LabelFor(model => model.AssignedRoles[i].IsAssigned)
#Html.CheckBoxFor(model => model.AssignedRoles[i].IsAssigned)
#Html.HiddenFor(model => model.AssignedRoles[i].RoleName)
</div>
}
I am trying to learn ASP.net by making an e-commerce site. I am trying to set up the ability to create Items and assign Images to the item being created via File Upload.
I managed to get the multiple file upload working, but only to the content/Images folder. I cant figure out out to marry this to the creation of Items so you can assign multiple images to an Item all in the creation of the item.
It would be a fair to say I dont know where to go from here and would appreciate any help.
Item Model Class: Table in the database to store each item. Is referenced from the Images Table with a 1 to many relationship.
public class Item
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ItemId { get; set; }
public int CategoryId { get; set; }
public int DesignerId { get; set; }
public int ImageId { get; set; }
[Required]
[MaxLength(250)]
public string ItemName { get; set; }
[Required]
[Range(0,9999)]
public decimal ItemPrice { get; set; }
[MaxLength(1000)]
public string ItemDescription { get; set; }
[Range(4,22)]
public int ItemSize { get; set; }
[Column("CategoryId")]
public virtual List<Category> Category { get; set; }
public virtual List<OrderDetail> OrderDetails { get; set; }
public virtual List<Image> Images { get; set; }
}
Image Model Class: Stores the URL for each Image in the content Directory of the site. Can have many images for each Item.
public class Image
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ImageId { get; set; }
[Required]
public string ImageURL { get; set; }
[Required]
public string ItemId { get; set; }
//Files Being Uploaded by the User
public IEnumerable<HttpPostedFileBase> Files { get; set; }
[Column("ItemId")]
public virtual List<Item> Item { get; set; }
}
Store Manager Controller
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Item item,HttpPostedFileBase file)
{
if (ModelState.IsValid)
{
//The below successfully saves the file to the content folder when separated into the Index Action.
foreach (var f in item.Files)
{
if (file.ContentLength > 0)
{
var fileName = Path.GetFileName(f.FileName);
var path = Path.Combine(Server.MapPath("~/Content/ItemImages/"+item), fileName);
file.SaveAs(path);
}
}
// The below also works when I dont have the Above in the Action.
db.Items.Add(item);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(item);
}
Create Item View
#model Project.Models.Item
#{
ViewBag.Title = "Create";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>Item</legend>
<div class="editor-label">
#Html.LabelFor(model => model.ItemName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ItemName)
#Html.ValidationMessageFor(model => model.ItemName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ItemPrice)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ItemPrice)
#Html.ValidationMessageFor(model => model.ItemPrice)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ItemDescription)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ItemDescription)
#Html.ValidationMessageFor(model => model.ItemDescription)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ItemColour)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ItemColour)
#Html.ValidationMessageFor(model => model.ItemColour)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.ItemSize)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.ItemSize)
#Html.ValidationMessageFor(model => model.ItemSize)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
#using (Html.BeginForm(null, null, FormMethod.Post, new { enctype = "multipart/form-data" }))
{
<div>
<table>
<tr>
<td>Files</td>
<td><input type="file" name="Files" id="Files" multiple/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" name="submit" value="Upload" /></td>
</tr>
</table>
</div>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
So it looks like you're pretty close.
You just have to add the images to the item before adding it to the database.
Since you're using EF, it should be something similar to this
//in your action
//add the images to the item
item.Images.Add(new Image { ImageUrl = ... });
//you should be able to just insert the whole entity graph here
db.Items.Add(item);
db.SaveChanges();
Something like that I think is what you're looking for.
Also in your model constructor normally I think you want to initalize those lists so you don't get null reference execeptions when doing something like the above
public class Item
{
public Item()
{
this.Images = new List<Image>();
}
//...
}
In EF I have two tables (parent is Office- child is Employee), I m adding records to Employee with reference of existing Office record but I guess because of my wrong configuration EF is behaving wearied.
Instead of using Office table's existing record, it add a new records in Office and use new record's id as a foreign key in child records and then create child records.
Here are my Models:
public class Office
{
public int OfficeId{ get; set; }
public string Name { get; set; }
public virtual IEnumerable<Employee> Employees{ get; set; }
}
public class Employee
{
public int EmployeeID{ get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DOB { get; set; }
[NotMapped]
public int Age { get { return DOB.YearsFromUtcDateTillToday(); } }
[NotMapped]
public string FullName { get { return string.Concat(FirstName, ' ', LastName); } }
public virtual Office Office { get; set; }
}
View is strongly typed of Employee here is view code:
#model Employee
#{
ViewBag.Title = "AddEmployee";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm(MVC.CodeTest.AddEmployee(),FormMethod.Post)) {
#Html.ValidationSummary(true)
#Html.HiddenFor(model=>model.Office.OfficID)
<fieldset>
<div class="editor-label">
#Html.LabelFor(model => model.FirstName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.FirstName)
#Html.ValidationMessageFor(model => model.FirstName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.LastName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.LastName)
#Html.ValidationMessageFor(model => model.LastName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DOB)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DOB)
#Html.ValidationMessageFor(model => model.DOB)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.GPA)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.GPA)
#Html.ValidationMessageFor(model => model.GPA)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
My Controller Actions are the following:
public virtual ActionResult AddEmployee(int officeId)
{
Employee st = new Employee();
st.Office=new Office();
st.Office.OfficeID= officeId;
return View(st);
}
[HttpPost]
public virtual ActionResult AddEmployee(Employee objEmployee)
{
bool success= classEmployeeService.AddEmployee(objEmployee);
if (success)
{
return RedirectToAction("Index",objEmployee.Office.OfficeId);
}
else
{
ModelState.AddModelError("", "Error! Business rule violation, can't repeat surname");
}
return View(objEmployee);
}
My Finding:
In first action (without HttpPost) OfficeId is correct, 1 in this case
Checking html I came to know it is correct
But When pressing Save button 2nd action with HttpPost is executed OfficeId value is wrong, it is new Office record id (18, 19 or next record)
Please guide and help me.
As i see you've an problem with your Model Design. The Employee class should has a property to represent the foreign key of OfficeID.
public class Employee
{
public int EmployeeID{ get; set; }
public int OfficeID{ get; set; } // This what you need
// ...... the rest of class properties
}
Then just assign the Office Id to this property not to the Navigation property "Office"
Employee st = new Employee();
st.OfficeID= officeId;
And never use st.Office=new Office(); because this will add this object to the DbContext StateManager as "Added" and it'll add it as new record to the Database.
For registering a new customer I have several partials on my view which refer to corresponding tables in my database.
Primary keys are identity fields, that's why no IDs on the view. To insert a new customer I need to insert 3 occurrences of address(visiting, postal and contact person's), then insert a row for contract, contact_person(with using addressId from one of already inserted addresses) and finally proceed with inserting a new customer which will contain foreign keys referencing to just inserted visiting and postal addresses, contract and contact person.
Could you recommend the best/easiest way to pass the data from these partial views as objects to CustomerController and handle the objects there please?
Links to examples of handling similar situations will be also highly appreciated.
Image of my page:
http://i1345.photobucket.com/albums/p673/swell_daze/customer_registration_zps563341d0.png
Image of tables:
http://i1345.photobucket.com/albums/p673/swell_daze/tables_zpsc60b644a.png
View code:
#model CIM.Models.customer
<h2>Register new customer</h2>
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>New customer registration</legend>
<fieldset class="span3">
<legend>Basic information</legend>
<div class="editor-label">
#Html.LabelFor(model => model.name, "Name")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.name)
#Html.ValidationMessageFor(model => model.name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.groupId, "Customer group")
</div>
<div class="editor-field">
#Html.DropDownList("groupId", String.Empty)
#Html.ValidationMessageFor(model => model.groupId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.status, "Status")
</div>
<div class="editor-field">
#Html.DropDownList("status")
#Html.ValidationMessageFor(model => model.status)
</div>
</fieldset>
<fieldset class="span3">
<legend>Visiting address</legend>
<div>
#{
Html.RenderPartial("AddressPartial");
}
</div>
</fieldset>
<fieldset style="width:270px">
<legend>Postal address</legend>
<div>
#{
Html.RenderPartial("AddressPartial");
}
</div>
</fieldset>
<fieldset class="span3">
<legend>Contract</legend>
<div>
#{
Html.RenderPartial("ContractPartial");
}
</div>
</fieldset>
<fieldset class="span3" style="width:540px">
<legend>Contact person</legend>
<div>
#{
Html.RenderPartial("ContactPersonPartial");
}
</div>
<p>
<input type="submit" class="btn btn-success" value="Submit" style="margin-right:1em"/>
#Html.ActionLink("Cancel", "Index", "Customer", new {#class="btn btn-danger", #type="button"})
</p>
</fieldset>
</fieldset>
}
you can wrap your view data in a viewmodel, then when you submit your data it will be mapped to your viewmodel, something like this:
Create a viewmodel:
public class MyViewModel
{
public string name { get; set; }
public string someprop1 { get; set; }
public string someprop2 { get; set; }
public MyAddress visitingaddress { get; set; }
public MyAddress postaladdress { get; set; }
public MyContactAddress contactaddress { get; set; }
}
public class MyAddress
{
public string town { get; set; }
public string street { get; set; }
public string housenumber { get; set; }
public string postalcode { get; set; }
}
public class MyContactAddress : MyAddress
{
public string firstname { get; set; }
public string lastname { get; set; }
public string email { get; set; }
public string phonenumber { get; set; }
}
Create your view just like you have done but by using your viewmodel instead of the model you are using now (CIM.Models.customer), and then in your partials, for example in the postal address partial view do something like this:
.......
#Html.EditorFor(x => x.postaladdress.town)
......
Create your controller actions by using your viewmodel:
public ActionResult Index()
{
MyViewModel vm = new MyViewModel();
//doing something here....
return View(vm);
}
[HttpPost]
public ActionResult Index(MyViewModel vm)
{
//save your data, here your viewmodel will be correctly filled
return View(vm);
}
hope this helps
I run into an error that says:
The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Comments_dbo.Posts_PostID". The conflict occurred in database "MVCProjectApp.Models.DBPostsContext", table "dbo.Posts", column 'PostID'.
The statement has been terminated.
I am lost and not sure what to do
Here is the create comment
#model MVCProjectApp.Models.Comment
#{
ViewBag.Title = "Create Comment";
}
<h2>Create</h2>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Comments</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Username)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Username)
#Html.ValidationMessageFor(model => model.Username)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Message)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Message)
#Html.ValidationMessageFor(model => model.Message)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Timestamp)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Timestamp)
#Html.ValidationMessageFor(model => model.Timestamp)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "~/FullPost/Index")
</div>
#section Scripts {
#Scripts.Render("~/bundles/jqueryval")
}
This is the Actionlink where I am passing the postid to the comment create
//
// GET: /Comment/Create
public ActionResult Create()
{
ViewBag.PostID = new SelectList(db.Posts, "PostID", "Title");
return View();
}
//
// POST: /Comment/Create
[HttpPost]
public ActionResult Create(Comment comment)
{
if (ModelState.IsValid)
{
db.Comments.Add(comment);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.PostID = new SelectList(db.Posts, "PostID", "Title", comment.PostID);
return View(comment);
}
here is the comment model
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace MVCProjectApp.Models
{
public class Post
{
public int PostID { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public DateTime Timestamp { get; set; }
public virtual ICollection<Comment> Comments { get; set; }
}
public class Comment
{
public int CommentID { get; set; }
public int PostID { get; set; }
public string Username { get; set; }
public string Message { get; set; }
public DateTime Timestamp { get; set; }
public virtual Post Post { get; set; }
}
}
I am lost and not sure what to do
You should take a look at the error you're getting. It says you're trying to insert a Comment. The foreign key constraint check that's ran by the DBMS before an insert tries to make sure the Comment has a PostID that it knows. You don't set it, so it'll default to 0, which does not exist in the Posts table, hence the error.
You should set the Comment's PostID before calling SaveChanges(), which can be done by adding a #Html.HiddenFor(m => m.PostID) field in the view. You then set the PostID of the model in the controller.
you need to associate the comment to the post. the simplest solution is to add a hidden field for the post id to the view.