MVC3 Why is my posted model NULL? - asp.net-mvc-3

Curious issue with MVC3, EF Code First.
I'm passing a model that wraps two models to a view.
public class UserInfoModel
{
[Key]
[Required]
public int Id { get; set; }
/// <summary>
/// <para>Corresponds to ProviderUserKey in ASP Membership</para>
/// <para>Used in Membership.GetUser(ProviderUserKey) to retrieve email and username.</para>
/// </summary>
public Guid MembershipId { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "First name")]
public string FirstName { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Last name")]
public string LastName { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Address 1")]
public string Address1 { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Address 2")]
public string Address2 { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "State")]
public string State { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Country")]
public string Country { get; set; }
[Required]
[DataType(DataType.Text)]
[Display(Name = "Zip code")]
public string ZipCode { get; set; }
[Required]
[DataType(DataType.PhoneNumber)]
[Display(Name = "Phone number")]
public string PhoneNumber { get; set; }
[Required]
[DataType(DataType.Date)]
[Display(Name = "Sign up date")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = #"{0:MM/dd\/yyyy}")]
public DateTime SignUpDate { get; set; }
[Required]
[DataType(DataType.Date)]
[Display(Name = "Birthday")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = #"{0:MM\/dd\/yyyy}")]
public DateTime BirthDate { get; set; }
[DataType(DataType.Date)]
[Display(Name = "Enrollment date")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = #"{0:MM\/dd\/yyyy}")]
public DateTime EnrollmentDate { get; set; }
[DataType(DataType.ImageUrl)]
[Display(Name = "Avatar")]
public string AvatarImage { get; set; }
[Display(Name = "Rank")]
public int RankId { get; set; }
[ForeignKey("RankId")]
public UserRankModel Rank { get; set; }
[Display(Name = "IsActive")]
public bool IsActive { get; set; }
}
public class RegisterModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
}
public class UserInfoAndRegisterModel
{
public UserInfoModel UserInfoModel { get; set; }
public RegisterModel RegisterModel { get; set; }
}
View:
#model K2Calendar.Models.UserInfoAndRegisterModel
#{
ViewBag.Title = "Update User";
}
<div class="container">
<h2>
Update Account Details</h2>
<p>
Use the form below to update the account.
</p>
<p>
#Html.ValidationSummary(true, "Account update was unsuccessful. Please correct the errors and try again.")
</p>
<script src="#Url.Content("~/Scripts/jquery.validate.js")" type="text/javascript"> </script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.js")" type="text/javascript"></script>
#using (Html.BeginForm())
{
#Html.HiddenFor(m => m.UserInfoModel.Id)
#Html.HiddenFor(m => m.UserInfoModel.MembershipId)
#Html.HiddenFor(m => m.UserInfoModel.SignUpDate)
<div class="row-fluid">
<div class="span6">
#Html.LabelFor(m => m.RegisterModel.UserName)
#Html.TextBoxFor(m => m.RegisterModel.UserName, new { tabindex = "1" , disabled = "disabled" } )
#Html.ValidationMessageFor(m => m.RegisterModel.UserName)
</div>
<div class="span6">
#Html.LabelFor(m => m.UserInfoModel.State)
#Html.TextBoxFor(m => m.UserInfoModel.State, new { tabindex = "8" })
#Html.ValidationMessageFor(m => m.UserInfoModel.State)
</div>
</div>
<div class="row-fluid">
<div class="span6">
#Html.LabelFor(m => m.RegisterModel.Email)
#Html.TextBoxFor(m => m.RegisterModel.Email, new { tabindex = "2" , disabled = "disabled" })
#Html.ValidationMessageFor(m => m.RegisterModel.Email)
</div>
<div class="span6">
#Html.LabelFor(m => m.UserInfoModel.ZipCode)
#Html.TextBoxFor(m => m.UserInfoModel.ZipCode, new { tabindex = "9" })
#Html.ValidationMessageFor(m => m.UserInfoModel.ZipCode)
</div>
</div>
<div class="row-fluid">
<div class="span6">
#Html.LabelFor(m => m.UserInfoModel.FirstName)
#Html.TextBoxFor(m => m.UserInfoModel.FirstName, new { tabindex = "3" })
#Html.ValidationMessageFor(m => m.UserInfoModel.FirstName)
</div>
<div class="span6">
#Html.LabelFor(m => m.UserInfoModel.Country)
#Html.TextBoxFor(m => m.UserInfoModel.Country, new { tabindex = "10" })
#Html.ValidationMessageFor(m => m.UserInfoModel.Country)
</div>
</div>
<div class="row-fluid">
<div class="span6">
#Html.LabelFor(m => m.UserInfoModel.LastName)
#Html.TextBoxFor(m => m.UserInfoModel.LastName, new { tabindex = "4" })
#Html.ValidationMessageFor(m => m.UserInfoModel.LastName)
</div>
<div class="span6">
#Html.LabelFor(m => m.UserInfoModel.BirthDate)
#Html.TextBoxFor(m => m.UserInfoModel.BirthDate, new { tabindex = "11", placeholder = "mm/dd/yyyy" })
#Html.ValidationMessageFor(m => m.UserInfoModel.BirthDate)
</div>
</div>
<div class="row-fluid">
<div class="span6">
#Html.LabelFor(m => m.UserInfoModel.Address1)
#Html.TextBoxFor(m => m.UserInfoModel.Address1, new { tabindex = "5" })
#Html.ValidationMessageFor(m => m.UserInfoModel.Address1)
</div>
<div class="span6">
#Html.LabelFor(m => m.UserInfoModel.EnrollmentDate)
#Html.TextBoxFor(m => m.UserInfoModel.EnrollmentDate, new { tabindex = "12", placeholder = "mm/dd/yyyy" })
#Html.ValidationMessageFor(m => m.UserInfoModel.EnrollmentDate)
</div>
</div>
<div class="row-fluid">
<div class="span6">
#Html.LabelFor(m => m.UserInfoModel.Address2)
#Html.TextBoxFor(m => m.UserInfoModel.Address2, new { tabindex = "6" })
#Html.ValidationMessageFor(m => m.UserInfoModel.Address2)
</div>
<div class="span6">
#Html.LabelFor(m => m.UserInfoModel.RankId)
#Html.DropDownListFor(m => m.UserInfoModel.RankId, (SelectList)ViewBag.RankList, new { tabindex = "13" })
</div>
</div>
<div class="row-fluid">
<div class="span6">
#Html.LabelFor(m => m.UserInfoModel.PhoneNumber)
#Html.TextBoxFor(m => m.UserInfoModel.PhoneNumber, new { tabindex = "7" })
#Html.ValidationMessageFor(m => m.UserInfoModel.PhoneNumber)
</div>
<div class="span6">
</div>
</div>
<br />
<p>
<button type="submit" class="btn btn-primary btn-large" tabindex = "14">Update ยป</button>
</p>
}
My controller classes takes the model that is posted and does some db updates.
One of the wrapped models is null:
[Authorize]
[HttpPost]
public ActionResult Edit(UserInfoAndRegisterModel model)
{
//WHY IS model.RegisterModel == null ??//
if (ModelState.IsValid)
{
try
{
dbContext.Entry(model.UserInfoModel).State = System.Data.EntityState.Modified;
dbContext.SaveChanges();
return RedirectToAction("Index", "Home");
}
catch (Exception ex)
{
throw new InvalidOperationException("Failed to update UserInfoModel", ex.InnerException);
}
}
GenerateRanksList();
return View(model);
}
I do the same thing for the account creation and I do not receive a null value for RegisterModel with almost identical view code.
Currently this is a non-issue in that I only update the UserInfoModel currently but in the future I may want to allow a user to change their email address or user name.
Any ideas?

Your RegisterModel is null because you are not posting any values which belongs to the RegisterModel
Although you have inputs which belong to RegisterModel like this in your view
#Html.TextBoxFor(m => m.RegisterModel.UserName,
new { tabindex = "1" , disabled = "disabled" } )
They are all disabled and disabled inputs are not posted:
Form submission - Successful controls
A successful control is "valid" for submission
However:
Controls that are disabled cannot be successful.
So you need remove the disable or add the properties as hidden fields:
#Html.HiddenFor(m => m.RegisterModel.UserName)

Related

How to bind dropdown list from action method in MVC 3 inside create view

I want to populate a dropdownlist on create view with form also. Here is my code
#model CM.Models.TVSerialEpisode
#{
ViewBag.Title = "AddEpisode";
}
#Html.Action("ListAllSeason","TvSerial") //here i want to call this action method so i could bind all tv serial
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>TVSerialEpisode</legend>
<div class="editor-label">
#Html.LabelFor(model => model.Serial_ID)
</div>
<div class="editor-field">
#Html.DropDownList("Serial_ID", new List<SelectListItem>
{
new SelectListItem { Text = "Backstrom", Value="16" },
new SelectListItem{Text="10 Things I hate about You", Value="15"},
new SelectListItem { Text = "Castle", Value="14" }
}, "Select Serial")
#Html.ValidationMessageFor(model => model.Serial_ID)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Episode_No)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Episode_No)
#Html.ValidationMessageFor(model => model.Episode_No)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Episode_Name)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Episode_Name)
#Html.ValidationMessageFor(model => model.Episode_Name)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.File_Url_480p)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.File_Url_480p)
#Html.ValidationMessageFor(model => model.File_Url_480p)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.File_Url_720p)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.File_Url_720p)
#Html.ValidationMessageFor(model => model.File_Url_720p)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Description)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Description)
#Html.ValidationMessageFor(model => model.Description)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.Is_Active)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Is_Active)
#Html.ValidationMessageFor(model => model.Is_Active)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
here is the model class
namespace CM.Models
{
[Table("tbltvserial")]
public class TVSerial
{
[Key]
public Int64 Serial_ID { get; set; }
public string Serial_Name { get; set; }
public string Season_Name { get; set; }
public int Season_No { get; set; }
public int? Release_Year { get; set; }
}
public class TVSerial_VM
{
public Int64 Serial_ID { get; set; }
public string Serial_Name { get; set; }
public int Season_No { get; set; }
}
[Table("tblserialepisode")]
public class TVSerialEpisode
{
[Key]
public Int64 Video_ID { get; set; }
public Int64 Serial_ID { get; set; }
public int Episode_No { get; set; }
public string Episode_Name { get; set; }
public string File_Url_480p { get; set; }
public string File_Url_720p { get; set; }
public string Description { get; set; }
public bool Is_Active { get; set; }
public DateTime Uploaded_Time { get; set; }
}
public class TvSerialContext : DbContext
{
public DbSet<TVSerialEpisode> TvSerialEpisodes { get; set; }
public DbSet<TVSerial> TvSerials { get; set; }
}
}
here are controller class
public class TvSerialController : Controller
{
public List<TVSerial_VM> ListAllSeason()
{
try
{
TvSerialContext tvContext = new TvSerialContext();
List<TVSerial_VM> tv = tvContext.TvSerials
.Select(t => new TVSerial_VM
{
Serial_Name = t.Serial_Name,
Season_No=t.Season_No,
Serial_ID=t.Serial_ID
}).OrderBy(t=> t.Season_Name).
ToList();
return (tv);
}
catch (Exception ex)
{
return null;
}
}
}
public class TvSerialEpisodeController : Controller
{
TvSerialContext tvContext = new TvSerialContext();
public ActionResult AddEpisode()
{
return View();
}
[HttpPost]
public ActionResult AddEpisode(TVSerialEpisode tvEpisode)
{
if (ModelState.IsValid)
{
tvContext.TvSerialEpisodes.Add(tvEpisode);
tvContext.SaveChanges();
return RedirectToAction("AddEpisode");
}
return View(tvEpisode);
}
}
I don't know how to bind the tvserial list inside above view page. basically i want to choose the serial id from dropdown list, is this posible to call a action method inside the create view.
To populate your SelectList from the database, in the controllers AddEpisode() method use
ViewBag.SerialList = db.TvSerials.ToList().Select(x => new SelectListItem(){ Value = x.Serial_ID.ToString(), Text = x.Serial_Name });
and then in the view use
#Html.DropDownListFor(m => m.Serial_ID, (IEnumerable<SelectListItem>)ViewBag.SerialList, "Select Serial")
although it is recommended you use a view model and include a IEnumerable<SelectListItem> SerialList property in the view model so that it is
#Html.DropDownListFor(m => m.Serial_ID, Model.SerialList, "Select Serial")

How to make Optimistic Concurrency editing in Asp.net mvc 3 without using hiddenfields in the view?

I'm studying asp.net mvc 3 right now and I'm following this tutorial Contoso University
http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/handling-concurrency-with-the-entity-framework-in-an-asp-net-mvc-application
I'm in this part where editing a model is handled with Optimistic Concurrency
I'm aware that by using something like
[HttpPost]
public ActionResult Edit(Department department)
the model will automatically be binded, even without a hidden field for the id of the department to be edited, and the edit will not fail.
But whenever I try to remove the two hiddenfields in the view of this one:
#model MvcContosoUniversity.Models.Department
#{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
<script src="#Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script>
<script src="#Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script>
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>Department</legend>
#Html.HiddenFor(model => model.DepartmentID)
#Html.HiddenFor(model => model.Timestamp)
<div class="editor-label">
#Html.LabelFor(model => model.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.Budget)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Budget)
#Html.ValidationMessageFor(model => model.Budget)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.StartDate)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.StartDate)
#Html.ValidationMessageFor(model => model.StartDate)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.InstructorID, "Administrator")
</div>
<div class="editor-field">
#Html.DropDownList("InstructorID", String.Empty)
#Html.ValidationMessageFor(model => model.InstructorID)
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
<div>
#Html.ActionLink("Back to List", "Index")
</div>
I get an error in the controller, here's the code for the controller:
// GET: /Department/Edit/5
public ActionResult Edit(int id)
{
Department department = db.Departments.Find(id);
ViewBag.InstructorID = new SelectList(db.Instructors, "InstructorID", "FullName", department.InstructorID);
return View(department);
}
//
// POST: /Department/Edit/5
[HttpPost]
public ActionResult Edit(Department department)
{
try
{
if (ModelState.IsValid)
{
db.Entry(department).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
//Another option is to put the try-catch inside a function
try
{
var databaseValues = (Department)entry.GetDatabaseValues().ToObject();
var clientValues = (Department)entry.Entity;
if (databaseValues.Name != clientValues.Name)
ModelState.AddModelError("Name", "Current value: "
+ databaseValues.Name);
if (databaseValues.Budget != clientValues.Budget)
ModelState.AddModelError("Budget", "Current value: "
+ String.Format("{0:c}", databaseValues.Budget));
if (databaseValues.StartDate != clientValues.StartDate)
ModelState.AddModelError("StartDate", "Current value: "
+ String.Format("{0:d}", databaseValues.StartDate));
if (databaseValues.InstructorID != clientValues.InstructorID)
ModelState.AddModelError("InstructorID", "Current value: "
+ db.Instructors.Find(databaseValues.InstructorID).FullName);
ModelState.AddModelError(string.Empty, "The record you attempted to edit "
+ "was modified by another user after you got the original value. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again. Otherwise click the Back to List hyperlink.");
department.Timestamp = databaseValues.Timestamp;
}
catch(NullReferenceException e)
{
ModelState.AddModelError("","Error \n "+e.Message);
}
}
catch (DataException)
{
//Log the error (add a variable name after Exception)
ModelState.AddModelError(string.Empty, "Unable to save changes. Try again, and if the problem persists contact your system administrator.");
}
ViewBag.InstructorID = new SelectList(db.Instructors, "InstructorID", "FullName", department.InstructorID);
return View(department);
}
Here is the code for the model:
public class Department
{
public int DepartmentID { get; set; }
[Required(ErrorMessage = "Department name is required.")]
[MaxLength(50)]
public string Name { get; set; }
[DisplayFormat(DataFormatString = "{0:c}")]
[Required(ErrorMessage = "Budget is required.")]
[Column(TypeName = "money")]
public decimal? Budget { get; set; }
[DisplayFormat(DataFormatString = "{0:d}", ApplyFormatInEditMode = true)]
[Required(ErrorMessage = "Start date is required.")]
public DateTime StartDate { get; set; }
[Display(Name = "Administrator")]
public int? InstructorID { get; set; }
public virtual Instructor Administrator { get; set; }
public virtual ICollection<Course> Courses { get; set; }
[Timestamp]
public Byte[] Timestamp { get; set; }
}
Is it possible to make it work without using hiddenfields?
Sir/Ma'am your answers would be of great help. Thank you++
You need to have the id value coming from somewhere, or that entity can't be saved... how does EF know what ID it's saving without it?
It doesn't have to be via hidden field; It's probably there in the query string from the initial GET request for the edit action.

can not add data from dropdown to table

I am using nhibernate and mvc3 in asp.net
I'm trying to add data into table where my table schema is like this:
public class HobbyMasters
{
[Key]
public virtual int HobbyId { get; set; }
[Required(ErrorMessageResourceType = typeof(Resources), ErrorMessageResourceName = "NameRequired")]
public virtual string HobbyName { get; set; }
public virtual HobbyTypes HobbyType { get; set; }
[Required]
public virtual string HobbyDetails { get; set; }
[Required]
public virtual ICollection<HobbyMasters> HobbyNames { get; set; }
}
public class HobbyTypes
{
[Key]
public virtual int HobbyTypeId { get; set; }
[Required]
public virtual string HobbyType { get; set; }
public virtual ICollection<HobbyTypes> Hobby { get; set; }
}
in my Controller
public ActionResult Create()
{
ViewBag.c1 = (ICollection<HobbyTypes>)(new Hobby_MasterService().GetAllHobbyTypes());
return View();
}
//
// POST: /Hobbies/Create
[HttpPost]
public ActionResult Create(HobbyMasters hobby)
{
ViewBag.c1 = (ICollection<HobbyTypes>)new Hobby_MasterService().GetAllHobbyTypes();
try
{
if (ModelState.IsValid)
{
new Hobby_MasterService().SaveOrUpdateHobby(hobby);
return RedirectToAction("Index");
}
}
}
in the view:
#using (Html.BeginForm("Create", "Hobbies", FormMethod.Post))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Hobby Master</legend>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HobbyName)
#Html.ValidationMessageFor(model => model.HobbyName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyType)
</div>
<div class="Editor-field">
#Html.DropDownListFor(model =>model.HobbyType.HobbyTypeId, new SelectList(ViewBag.c1, "HobbyTypeId", "HobbyType"), "-- Select --")
#Html.ValidationMessageFor(model => model.HobbyType)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyDetails)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HobbyDetails)
#Html.ValidationMessageFor(model => model.HobbyDetails)
</div>
</fieldset>
<p><input type="Submit" value="Create" /> </p>
}
Apparently i found that My Modelstate.IsValid is always false.....
since it stores only the HobbyId and getting Hobby Type as null the HobbyMasters hobbytype object.....
dnt knw where i'm going wrong may be in dropdownlist or something else.....
Plaese help me asap:(
There are a couple of issues with your code:
First is the fact that you decorated HobbyNames collection property with the [Required] attribute. You should use this attribute only on simple properties. In fact you could leave the property but it will have absolutely no effect
The second issue with your code is that you have decorated the HobbyType string property of the HobbyTypes model with a [Required] attribute but you never use this property in your view. So no value is sent when you submit the form and your model is invalid.
Another issue with your code is that you bound the dropdown list to the model => model.HobbyType.HobbyTypeId property. But the HobbyTypeId is not a nullable type. And yet you made your dropdown contain a default value: "-- Select --". This is not possible. If you want to have a dropdown list with an optional value you must bind it to a nullable property on your model.
I have tried to clean up your code a little.
Model:
public class HobbyMasters
{
public virtual int HobbyId { get; set; }
[Required]
public virtual string HobbyName { get; set; }
public virtual HobbyTypes HobbyType { get; set; }
[Required]
public virtual string HobbyDetails { get; set; }
public virtual ICollection<HobbyMasters> HobbyNames { get; set; }
}
public class HobbyTypes
{
[Required]
public virtual int? HobbyTypeId { get; set; }
public virtual string HobbyType { get; set; }
public virtual ICollection<HobbyTypes> Hobby { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Create()
{
ViewBag.c1 = (ICollection<HobbyTypes>)(new Hobby_MasterService().GetAllHobbyTypes());
var model = new HobbyMasters();
return View(model);
}
//
// POST: /Hobbies/Create
[HttpPost]
public ActionResult Create(HobbyMasters hobby)
{
if (ModelState.IsValid)
{
try
{
new Hobby_MasterService().SaveOrUpdateHobby(hobby);
return RedirectToAction("Index");
}
catch (Exception ex)
{
ModelState.AddModelError("", ex.Message);
}
}
ViewBag.c1 = (ICollection<HobbyTypes>)new Hobby_MasterService().GetAllHobbyTypes();
return View(hobby);
}
}
View:
#model HobbyMasters
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Hobby Master</legend>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HobbyName)
#Html.ValidationMessageFor(model => model.HobbyName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyType)
</div>
<div class="Editor-field">
#Html.DropDownListFor(model => model.HobbyType.HobbyTypeId, new SelectList(ViewBag.c1, "HobbyTypeId", "HobbyType"), "-- Select --")
#Html.ValidationMessageFor(model => model.HobbyType.HobbyTypeId)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.HobbyDetails)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.HobbyDetails)
#Html.ValidationMessageFor(model => model.HobbyDetails)
</div>
</fieldset>
<p><input type="Submit" value="Create" /></p>
}
Also I would very strongly recommend you to use view models. Don't pass your domain entities to your views. Define view models.

Checkboxes in editor template are not bound to model in post action

I am using an editor template to display a checkbox for each role a user can be assigned to. The model is:
public class UserModel
{
[Required]
[Display(Name = "User name")]
public string UserName { get; set; }
[Required]
[DataType(DataType.EmailAddress)]
[Display(Name = "Email address")]
public string Email { get; set; }
[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
[Display(Name = "Password")]
public string Password { get; set; }
[DataType(DataType.Password)]
[Display(Name = "Confirm password")]
[Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
public string ConfirmPassword { get; set; }
public IEnumerable<string> UserRoles { get; set; }
}
public class UserRoleModel
{
public IEnumerable<RoleViewModel> AllRoles { get; set; }
public UserModel user { get; set; }
public UserRoleModel()
{
this.AllRoles = Roles.GetAllRoles().Select(r => new RoleViewModel
{
Name = r
});
this.user = new UserModel();
}
}
public class RoleViewModel
{
public string Name { get; set; }
public bool Selected { get; set; }
}
The Controller:
public ActionResult Create()
{
return View(new UserRoleModel());
}
[HttpPost]
public ActionResult Create(UserRoleModel model)
{
if (ModelState.IsValid)
{
MembershipCreateStatus createStatus;
Membership.CreateUser(model.user.UserName, model.user.Password, model.user.Email, null, null, true, null, out createStatus);
if (createStatus == MembershipCreateStatus.Success)
{
foreach (var r in model.AllRoles)
{
if (r.Selected)
{
Roles.AddUserToRole(model.user.UserName, r.Name);
}
}
return RedirectToAction("Index", "Home");
}
else
{
ModelState.AddModelError("", ErrorCodeToString(createStatus));
}
}
return View(model);
}
The view:
#model BBmvc.Areas.Tools.Models.UserRoleModel
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<legend>UserModel</legend>
<div class="editor-label">
#Html.LabelFor(model => model.user.UserName)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.user.UserName)
#Html.ValidationMessageFor(model => model.user.UserName)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.user.Email)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.user.Email)
#Html.ValidationMessageFor(model => model.user.Email)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.user.Password)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.user.Password)
#Html.ValidationMessageFor(model => model.user.Password)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.user.ConfirmPassword)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.user.ConfirmPassword)
#Html.ValidationMessageFor(model => model.user.ConfirmPassword)
</div>
<div class="editor-field">
#Html.EditorFor(x => x.AllRoles)
</div>
<p>
<input type="submit" value="Create" />
</p>
</fieldset>
}
and the editor template
#model BBmvc.Areas.Tools.Models.RoleViewModel
#Html.CheckBoxFor(x => x.Selected)
#Html.LabelFor(x => x.Selected, Model.Name)
#Html.HiddenFor(x => x.Name)
<br />
The problem is that no distinction is made in the post action whether any checkbox is checked or not. It seems that it is not getting bound to the model somehow.
Your issue comes from the deferred execution of LINQ queries. You need to eagerly initialize the collection:
public class UserRoleModel
{
public IEnumerable<RoleViewModel> AllRoles { get; set; }
public UserModel user { get; set; }
public UserRoleModel()
{
this.AllRoles = Roles.GetAllRoles().Select(r => new RoleViewModel
{
Name = r
}).ToList();
this.user = new UserModel();
}
}
Notice the .ToList() call:
this.AllRoles = Roles.GetAllRoles().Select(r => new RoleViewModel
{
Name = r
}).ToList();
And here's the explanation. When you write:
this.AllRoles = Roles.GetAllRoles().Select(r => new RoleViewModel
{
Name = r
});
at this moment the query is not executed. Only an expression tree is built but the actual query is executed only when something starts iterating over the collection. And what starts iterating? First it's the view. Inside the view you use an editor template for this collection:
#Html.EditorFor(x => x.AllRoles)
Since AllRoles is a collection property ASP.NET MVC will automatically iterate and render the editor template for each element of the collection. So this works to properly render the view.
Now let's see what happens when the form is POSTed. You post to the Create action and the default model binder kicks in. The constructor is called but since there is nothing to iterate over the AllRoles property this time the query is not executed. In fact it is executed later inside the action and the values are lost.
For this reason I would recommend you to avoid initializing your view models inside constructors. It would be better to do this inside the respective controller actions:
public class UserRoleModel
{
public IEnumerable<RoleViewModel> AllRoles { get; set; }
public UserModel user { get; set; }
}
and then:
public ActionResult Create()
{
var model = new UserRoleModel
{
AllRoles = Roles.GetAllRoles().Select(r => new RoleViewModel
{
Name = r
}).ToList(),
user = new UserModel()
};
return View(model);
}

How to bind a nested model in MVC3?

I've been reading all about UpdateModel() and custom model binders and i still cant figure this out. Seems like theres gotta be a simple answer to this.
I have a class called user that i user all over my MVC Web app.
public class User
{
[Key]
public int ID { get; set; }
[Required]
public string Name { get; set; }
[Required]
[Email]
public string Email { get; set; }
[Required]
public string Password { get; set; }
public virtual ICollection<User> WorkTasks { get; set; }
}
And then a WorkTask that has it in a few places:
public class WorkTask
{
[Key]
public int ID { get; set; }
[Required]
public string Name { get; set; }
public DateTime? DesiredStartDate { get; set; }
public DateTime? DesiredEndDate { get; set; }
public TimeSpan? DesiredTimeSpent { get; set; }
public virtual ICollection<WorkTaskTag> Tags { get; set; }
public virtual ICollection<WorkTaskPeriod> Periods { get; set; }
public virtual ICollection<User> InvolvedUsers { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User AssignedTo { get; set; }
public string UserNameAssignedTo
{
get
{
if(AssignedTo!=null)
return AssignedTo.Name;
return CreatedBy.Name;
}
}
public string TotalTimeSpent {
get
{
var concretePeriods = Periods
.Where(i => i.StartDate.HasValue && i.EndDate.HasValue);
if (concretePeriods != null && concretePeriods.Count() > 0)
{
TimeSpan ts = new TimeSpan();
foreach (var p in concretePeriods)
{
var t=p.EndDate.Value-p.StartDate.Value;
ts.Add(t);
}
TimePeriodHelpers help = new TimePeriodHelpers();
return help.GetTimeFormat(ts);
}
return "0:00";
}
}
}
So how do i make a create template for this WorkTask that allows the User class to be bound to the WorkTask in multiple places?
Here's my very shoddy attempt:
[HttpPost]
public ActionResult Create(WorkTask worktask)
{
LoadUsers();
string assignedto=Request["AssignedTo"];
var user = db.Users.First(i => SqlFunctions.StringConvert((double)i.ID) == assignedto);
UpdateModel<User>(user);
if (ModelState.IsValid)
{
db.WorkTasks.Add(worktask);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(worktask);
}
and the view:
#using (Html.BeginForm()) {
#Html.ValidationSummary(true)
<fieldset>
<div class="editor-label">
#Html.LabelFor(model => model.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.DesiredStartDate,"Desired Start Date")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DesiredStartDate)
#Html.ValidationMessageFor(model => model.DesiredStartDate)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DesiredEndDate,"Desired End Date")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DesiredEndDate)
#Html.ValidationMessageFor(model => model.DesiredEndDate)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.DesiredTimeSpent,"Desired Time Spent")
</div>
<div class="editor-field">
#Html.EditorFor(model => model.DesiredTimeSpent)
#Html.ValidationMessageFor(model => model.DesiredTimeSpent)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.AssignedTo,"User Assigned To")
</div>
<div class="editor-field">
#Html.DropDownListFor(i=>Model.AssignedTo,(IEnumerable<SelectListItem>)ViewBag.Users)
#Html.ValidationMessageFor(model => model.AssignedTo)
</div>
<p>
<input type="submit" value="Create" />
</p>
Instead of trying to bind the nested User object, i made the form and controller based upon a ViewModel which just had the UserID. I then assumed that if the ViewModel validated correctly, than i can go ahead and persist the WorkTask and nested User objects.

Resources