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++
Based on your HttpPost action signature, no, this isn't possible. You're performing an Edit so it will need the Id of the row that is being updated. Since you're not mapping the Id in the action method signature, and the hidden fields holding that information aren't there, the id will never get mapped to the Department model. The result being that you would try to perform an update without a row id.
EDIT: You could modify your action signature like so: Edit(int id, Department department), but then you'd have to manually set the department.Id to the id passed and that would seem to make the model somewhat disjointed.
Related
StudentModel.
namespace mvcApp.Models
{
public class StudentModel
{
[Display(Name = "First Name")]
public string FirstName { get; set; }
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Display(Name = "Email Address")]
public string EmailAddress { get; set; }
public List<SchoolOrganization> Organizations { get; set; }
}
public class SchoolOrganization
{
public string Name { get; set; }
public bool IsInvolved { get; set; }
}
}
Student is involved in multiple organizations.
Controller
namespace mvcApp.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Modify this template to jump-start your ASP.NET MVC application.";
return View();
}
public ActionResult StudentInformation()
{
// populate student with data
var student = new StudentModel() { FirstName = "Joe", LastName = "Doe", EmailAddress = "jdoe#hotmail.com"};
// Populate with Organizations
student.Organizations = new List<SchoolOrganization>();
student.Organizations.Add(new SchoolOrganization() { Name = "Math Club", IsInvolved = true});
student.Organizations.Add(new SchoolOrganization() { Name = "Chess Club", IsInvolved = false });
student.Organizations.Add(new SchoolOrganization() { Name = "Football", IsInvolved = true });
return View(student);
}
**[HttpPost]
public ActionResult StudentInformation(StudentModel student)
{
Response.Write("Name: " + student.FirstName);
foreach (var o in student.Organizations)
{
Response.Write(o.Name + " : " + o.IsInvolved.ToString());
}
return View();
}**
}
}
Data will be eventually populated from database.
View
#model mvcApp.Models.StudentModel
#{
ViewBag.Title = "StudentInformation";
}
<h2>StudentInformation</h2>
#using (Html.BeginForm()) {
#Html.AntiForgeryToken()
#Html.ValidationSummary(true)
<fieldset>
<legend>StudentModel</legend>
<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.EmailAddress)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.EmailAddress)
#Html.ValidationMessageFor(model => model.EmailAddress)
</div>
<div>
<table>
<tr>
<td>Organization Name</td><td>Is Involved</td>
</tr>
#for (int i = 0; i < Model.Organizations.Count; i++) <== System.NullReferenceException here
{
#Html.HiddenFor(m => m.Organizations[i].IsInvolved)
<tr>
<td>#Html.DisplayFor(m => m.Organizations[i].Name)</td>
<td>#Html.CheckBoxFor(m => m.Organizations[i].IsInvolved)</td>
</tr>
}
</table>
</div>
<p>
<input type="submit" value="Save" />
</p>
</fieldset>
}
The above code displays fine with HttGet. However, when i try to update i get System.NullReferenceException. https://www.dropbox.com/s/dz0bg3hkd0yq8e3/studentInformation.png?dl=0
Can anyone please help figure it what's going on?
Thank you.
In the code sample you have provided; the HomeController's [HttpPost] ActionResult for StudentInformation does not create and pass a new instance of the updated object model to the view, but instead runs a basic debug.writeline routine. As a result the dependent view for the [HttpPost] view of "StudentInformation.cshtml", does not receive a populated instance of the updated model...
Setting a breakpoint on the first line of the model for the "StudentInformation.cshtml" page and running the application will demonstrate that the model referenced at the top of the page, has no data within it...
This is why the [HttpPost] version of the page simply renders the blank model, without any altered data values you may have created, UNTIL it gets to the section where the view is dependent on a count of new data values which must be present within the model that is called at first line of the page... to continue.
An empty data model set results in a null reference because there are no values to count within it.
In order to view an updated group of settings, The [HttpPost] version of the view model must be passed an instance of the model that returns the new information as in "return View(nameOfViewDataSet)" (you build a data set again, and pass it as a new version of the model, with revised form data present).
Until there is data passed via the return View statement relative to the [HttpPost] version of the StudentInformation ActionResult, to the actual view, there will be no display data, and the count function will continue to return a null value.
I hope that is helpful.
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.
Well I've recently come up against an interesting problem that I can't seem to figure out.
The error message I get is:
{"The model item passed into the dictionary is null, but this dictionary requires a non-null model item of type 'System.TimeSpan'."}
This occurs when I try to submit a new entry to the database. So, the details of what is being submitted.
The model class:
public class EventModel
{
[Key]
public int EventID { get; set; }
[DisplayName("Booking title")]
[Required(ErrorMessage="Please provide a title for the booking")]
public string Title { get; set; }
[DataType(DataType.Date)]
[DisplayName("Start date")]
[DisplayFormat(DataFormatString="{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
public DateTime StartDateTime { get; set; }
[DisplayName("End date")]
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:dd/MM/yyyy}", ApplyFormatInEditMode = true)]
[IsDateAfter("StartDateTime", true, ErrorMessage="End date must be on or after the start date")]
public DateTime EndDateTime { get; set; }
public long StartTicks { get; set; }
public long EndTicks { get; set; }
[NotMapped]
[DisplayName("Start Time")]
public TimeSpan StartTime
{
get { return TimeSpan.FromTicks(StartTicks); }
set { StartTicks = value.Ticks; }
}
[NotMapped]
[DisplayName("End Time")]
public TimeSpan EndTime
{
get { return TimeSpan.FromTicks(EndTicks); }
set { EndTicks = value.Ticks; }
}
[DefaultValue(2)]
[DisplayName("Booking is")]
public int BookingStatus { get; set; }
[DisplayName("Set recurrence")]
[DefaultValue(false)]
public bool DoesRecur { get; set; }
[DisplayName("Set recurrence type")]
public string Pattern { get; set; }
[DisplayName("Set the day this happens on ")]
public int DayIndex { get; set; }
[DisplayName("Choose the day instance this recurs on")]
public int DayCount { get; set; }
[DisplayName("Day ")]
[NotMapped]
public string Day { get; set; }
[DisplayName("Instance")]
[NotMapped]
public string Instance { get; set; }
// links resource to a user/member
[DisplayName("Booked by")]
[NotMapped]
public string BookerName { get; set; }
public Guid MemberID { get; set; }
// links resource to a resource type
[DisplayName("Resource required:")]
public int ResourceID { get; set; }
}
The action methods in the controller class:
[HttpGet]
public ActionResult Create(DateTime eventDate)
{
var days = from DayOfWeek d in Enum.GetValues(typeof(DayOfWeek))
select new { ID = (int) d, Name = (DayOfWeek)d };
var instance = from DayInstance i in Enum.GetValues(typeof(DayInstance))
select new { ID = (int) i, Name = (DayInstance)i };
MembershipUser mu = Membership.GetUser(HttpContext.Profile.UserName);
CreateEventViewModel model = new CreateEventViewModel()
{
Event = new EventModel()
{
StartDateTime = eventDate,
EndDateTime = eventDate,
MemberID = (Guid)mu.ProviderUserKey
},
Resources = DBContext.Resources.ToList(),
Patterns = DBContext.Patterns.ToList(),
ResourceTypes = DBContext.ResourceTypes.ToList()
};
ViewData["dayOfWeek"] = new SelectList(days, "ID", "Name", DayOfWeek.Monday);
ViewData["dayInstance"] = new SelectList(instance, "ID", "Name", DayInstance.First);
return View(model);
}
[HttpPost]
public ActionResult Create(CreateEventViewModel em)
{
if (ModelState.IsValid)
{
// get the resource turn aournd time
double turnAround = rc.GetResourceTurnAround(em.Event.ResourceID);
MembershipUser mu = Membership.GetUser(HttpContext.Profile.UserName);
em.Event.MemberID = (Guid) mu.ProviderUserKey;
em.Event.BookingStatus = 2;
// need to get the time added to the date.
DateTime actualStartPoint = new DateTime(em.Event.StartDateTime.Ticks + em.Event.StartTicks);
DateTime actualEndPoint = new DateTime(em.Event.EndDateTime.Ticks + em.Event.EndTicks);
em.Event.StartDateTime = actualStartPoint;
em.Event.EndDateTime = actualEndPoint;
// add turn around time to the end of the event
em.Event.EndDateTime = em.Event.EndDateTime.AddMinutes(turnAround);
// needed becase these are handled slighty differently to the rest of the model
em.Event.DayIndex = int.Parse(Request.Form.GetValues("dayOfWeek").GetValue(0).ToString());
em.Event.DayCount = int.Parse(Request.Form.GetValues("dayInstance").GetValue(0).ToString());
DBContext.Events.Add(em.Event);
DBContext.SaveChanges();
// get the resource owner
MembershipUser resourceOwner = Membership.GetUser(rc.GetResourceOwnerByID(em.Event.ResourceID));
// email the admin team and the user the details of this booking
// get the email address of the user making the booking
StringBuilder message = new StringBuilder();
message.AppendFormat("Thank you for your booking, this is now being reviewed by the team.\nThe details of your booking are included for confirmation.\n");
message.AppendFormat("Booking Title: {0}\nResource: {1}\n Date: {2} {3} (this includes our turn around time added on)\n", em.Event.Title, rc.GetResourceNameByID(em.Event.ResourceID), actualStartPoint, actualEndPoint);
message.AppendFormat("You can log in at any time to review your bookings.\nYou will receive an email when the team have reviewed this request\nMany thanks\n");
EmailHandler eh = new EmailHandler();
eh.SetRecipient(Membership.GetUser().Email);
eh.AddAdminEmail();
eh.AddBcc(resourceOwner.Email);
eh.SetSubject("Booking Requested");
eh.SetBody(message.ToString());
eh.sendMessage();
return RedirectToAction("Index");
}
else
{
return View();
}
}
Now for the view items - the main view:
#model AssetManager.Models.CreateEventViewModel
#{
ViewBag.Title = "Create";
Layout = "~/Views/Shared/_Layout.cshtml";
}
#using (Html.BeginForm())
{
#Html.ValidationSummary(true)
<fieldset>
<legend id="bookingLegend">Place Booking</legend>
<div class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.Title)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Event.Title)
#Html.ValidationMessageFor(model => model.Event.Title)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.StartDateTime)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Event.StartDateTime, new { #class = "date" })
#Html.ValidationMessageFor(model => model.Event.StartDateTime)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label timeSelector">
#Html.LabelFor(model => model.Event.StartTime)
</div>
<div class="editor-field timeSelector">
#Html.EditorFor(model => model.Event.StartTime)
#Html.ValidationMessageFor(model => model.Event.StartTime)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.EndDateTime)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Event.EndDateTime, new { #class = "date" })
#Html.ValidationMessageFor(model => model.Event.EndDateTime)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label timeSelector">
#Html.LabelFor(model => model.Event.EndTime)
</div>
<div class="editor-field timeSelector">
#Html.EditorFor(model => model.Event.EndTime)
#Html.ValidationMessageFor(model => model.Event.EndTime)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label">
#Html.Label("Select Resource Type")
</div>
<div class="editor-field">
#Html.DropDownList("ResourceTypes", new SelectList(Model.ResourceTypes, "ResourceTypeID", "Title"), "-- Select Resource Type --", new { #id = "ddlResourceTypes" })
</div>
</div>
<div class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.ResourceID)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Event.ResourceID, new SelectList(Enumerable.Empty<SelectListItem>(), "ResourceType", "Name"), "-- Select Resource --", new { #id = "ddlResources" })
#Html.ValidationMessageFor(model => model.Event.ResourceID)
</div>
</div>
<div class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.DoesRecur)
</div>
<div class="editor-field">
#Html.EditorFor(model => model.Event.DoesRecur)
#Html.ValidationMessageFor(model => model.Event.DoesRecur)
</div>
</div>
<div id="recurType" class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.Pattern)
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.Event.Pattern, new SelectList(Model.Patterns, "PatternCode", "Pattern"), "-- Select Recurrence Pattern --")
#Html.ValidationMessageFor(model => model.Event.Pattern)
</div>
</div>
<div id="recurDayHappens" class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.DayIndex)
</div>
<div class="editor-field">
#Html.DropDownList("dayOfWeek")
#Html.ValidationMessageFor(model => model.Event.DayIndex)
</div>
</div>
<div id="recurInstance" class="controlcontainer">
<div class="editor-label">
#Html.LabelFor(model => model.Event.DayCount)
</div>
<div class="editor-field">
#Html.DropDownList("dayInstance")
#Html.ValidationMessageFor(model => model.Event.DayCount)
</div>
</div>
<div class="controlcontainer">
<p>
<input class="subButton" type="submit" value="Create" />
<input id="cancelBtn" class="cancelButton" type="button" value="Cancel" onclick="location.href='#Url.Action("Index", "Calendar")'" />
</p>
</div>
</fieldset>
}
Then there is an editor template for the TimeSpan items:
#model TimeSpan
#Html.DropDownList("Hours", Enumerable.Range(0, 24)
.Select(i => new SelectListItem { Value = i.ToString(),
Text = i.ToString(), Selected = Model.Hours == i })) :
#Html.DropDownList("Minutes", Enumerable.Range(0, 60)
.Select(i => new SelectListItem { Value = i.ToString(),
Text = i.ToString(), Selected = Model.Minutes == i }))
And finally a TimeBinder class:
public class TimeBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// Ensure there's incomming data
var key_hours = bindingContext.ModelName + ".Hours";
var valueProviderResult_hours = bindingContext.ValueProvider
.GetValue(key_hours);
var key_minutes = bindingContext.ModelName + ".Minutes";
var valueProviderResult_minutes = bindingContext.ValueProvider
.GetValue(key_minutes);
if (valueProviderResult_hours == null || string.IsNullOrEmpty(valueProviderResult_hours.AttemptedValue)
|| valueProviderResult_minutes == null || string.IsNullOrEmpty(valueProviderResult_minutes.AttemptedValue))
{
return null;
}
// Preserve it in case we need to redisplay the form
bindingContext.ModelState.SetModelValue(key_hours, valueProviderResult_hours);
bindingContext.ModelState.SetModelValue(key_minutes, valueProviderResult_minutes);
// Parse
var hours = ((string[])valueProviderResult_hours.RawValue)[0];
var minutes = ((string[])valueProviderResult_minutes.RawValue)[0];
// A TimeSpan represents the time elapsed since midnight
var time = new TimeSpan(Convert.ToInt32(hours), Convert.ToInt32(minutes), 0);
return time;
}
}
That's it, that is all the code that is involved. I am completely baffled as to why this error occurs. Any ideas or suggestions as to the cause and the solution are greatly appreciated.
Many thanks
nathj07
EDIT
Pk, so I tried something differnt with th TimeSpan editor template:
#model TimeSpan?
#Html.DropDownList("Hours", Enumerable.Range(0, 24)
.Select(i => new SelectListItem
{
Value = i.ToString(),
Text = i.ToString(),
Selected = Model.HasValue ? Model.Value.Hours == i : false
})) :
#Html.DropDownList("Minutes", Enumerable.Range(0, 60)
.Select(i => new SelectListItem
{
Value = i.ToString(),
Text = i.ToString(),
Selected = Model.HasValue ? Model.Value.Minutes == i : false
}))
This seems to have overcome this error but now I get an issue a little further down. In the view there is a DropDownList("ResourceTypes"....) This is essentially a dropdownlist that is used to control what appears in the DropDownListFor(model=>model.Event.ResourceID.....) There is a simple piece of JavaScript:
$(document).ready(function () {
$("#ddlResourceTypes").change(function () {
var idResourceType = $('#ddlResourceTypes').val();
$.getJSON("/Resource/LoadResourcesByType", { id: idResourceType },
function (resourceData) {
var select = $("#ddlResources");
select.empty();
select.append($('<option/>', {
value: 0,
text: "-- Select Resource --"
}));
$.each(resourceData, function (index, itemData) {
select.append($('<option/>', {
value: itemData.Value,
text: itemData.Text
}));
});
});
});
});
Now the issue I get is:
Object reference not set to an instance of an object
On the DropDownList("ResourceTypes".....)
Any ideas on this one?
When you POST an invalid form, you end with the code return View().
So you display the same view, without passing a model, the Model will be null. The first time your code really needs a value is in the editor for TimeSpan. That value is a nullable now, but you never test for the case that it is null.
Change the return to:
return View(em);
to pass the model, or use the code from the GET action, to rebuild and pass the model:
return Create(/* Your create date */);
Edit after comment
The error in the ModelBinder might be caused by the lines:
var hours = ((string[])valueProviderResult_hours.RawValue)[0];
var minutes = ((string[])valueProviderResult_minutes.RawValue)[0];
You convert an array to a string[]. I would do the conversion to string as late as possible, and make it more error proof by:
var hours = Convert.ToString(((object[])valueProviderResult_hours.RawValue).FirstOrDefault()) ?? "00";
This will just cast to an array of object, so there's less change to fail. Take the first element, or return null, and convert that to a string using Convert, and if the result is still null, return "00".
Hit breakpoints in your partial views and check the Model object, somewhere you will find a wrong object for a partial view. that is the cause of this error
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 do i update a domain object with ViewModel with AutoMapper using Entity framework?
I have a View to edit a Question entity.
This is my Edit action:
public ActionResult Edit(int id)
{
var question = db.Question.Single(q => q.question_id == id);
Mapper.CreateMap<Question, EditQuestionViewModel>();
EditQuestionViewModel eqvm = Mapper.Map<Question, EditQuestionViewModel>(question);
eqvm.QuestionTypes = new SelectList(db.Question_Type, "type_code", "type_description", question.type_code);
eqvm.Categories = new SelectList(db.Category, "category_id", "category_name", question.category_id);
eqvm.Visibility = new SelectList(new Dictionary<int, string> {
{ 1, "Ja"},
{ 0, "Nej"}
}, "Key", "Value");
return View(eqvm);
}
And my ViewModel looks like this:
public class EditQuestionViewModel
{
public int question_id { get; set; }
public string question_wording { get; set; }
public bool visible { get; set; }
public int question_number { get; set; }
public string help_text { get; set; }
public Category Category { get; set; }
public Question_Type Question_Type { get; set; }
public string SelectedCategory { get; set; }
public string SelectedQuestionType { get; set; }
public SelectList Categories { get; set; }
public SelectList QuestionTypes { get; set; }
public SelectList Visibility { get; set; }
public string RefUrl { get; set; }
}
This is the View:
#using (Html.BeginForm("Edit", "AdminQuestion", FormMethod.Post))
{
#Html.ValidationSummary(true)
<fieldset>
<legend>Spørgsmål</legend>
<div class="editor-label">
#Html.LabelFor(model => model.question_wording, "Spørgsmål")
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.question_wording, new { #class = "required", rows = 3, cols = 50 })
#Html.ValidationMessageFor(model => model.question_wording)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.SelectedCategory, "Hvilken kategori tilhører dette spørgsmål?")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.SelectedCategory, Model.Categories)
#Html.ValidationMessageFor(model => model.SelectedCategory)
</div>
<div class="editor-label">
#Html.LabelFor(x => x.SelectedQuestionType, "Spørgsmålstype")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.SelectedQuestionType, Model.QuestionTypes)
#Html.ValidationMessageFor(model => model.SelectedQuestionType)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.visible, "Skal dette spørgsmål være synligt?")
</div>
<div class="editor-field">
#Html.DropDownListFor(model => model.visible, Model.Visibility)
#Html.ValidationMessageFor(model => model.visible)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.question_number, "Hvilket nummer har spørgsmålet inden for sin kategori?")
</div>
<div class="editor-field">
#Html.TextBoxFor(model => model.question_number, new { #class = "required digits" })
#Html.ValidationMessageFor(model => model.question_number)
</div>
<div class="editor-label">
#Html.LabelFor(model => model.help_text, "Hjælpetekst som hjælper brugeren med at forstå spørgsmålet:")
</div>
<div class="editor-field">
#Html.TextAreaFor(model => model.help_text, new { rows = 20, cols = 50 })
#Html.ValidationMessageFor(model => model.help_text)
</div>
<br />
<input type="submit" value="Gem" />
</fieldset>
How do i update the entity when i submit the form ?
How should the mapping between the ViewModel and EF Model look like, using AutoMapper?
The properties
public string SelectedCategory { get; set; }
public string SelectedQuestionType { get; set; }
In the ViewModel are supposed to be linked with category_id and type_code in the EF model
Also notice the property
public bool visible { get; set; }
I use BIT in my database. Will this work with the values 0 and 1, which is use in the SelectList?
Thanks!
you would need to get the object from entity framework, and then use automapper like this:
var item = repository.getbyid(model.Id);
_mappingEngine.Map(viewModel, item);
repository.save(item);
When you submit your form, you need to have an action on your controller that will handle the post to the server.
So in addition to the Edit action you currently have, you will need to have another action defined like so:
[HttpPost]
public ActionResult Edit(EditQuestionViewModel model)
{
//Do the mapping to from your ViewModel to the EF model here
return View();
}
What this does is sets up a handler so your form can post the data back to the controller and it will bind your fields on your form to the model parameter.
Once you have done this, you can simply map the model back to EF and persist it to the database.
Also, using a bool is perfectly valid and EF will translate and save it as a 0 or 1 in the database for you.